diff options
Diffstat (limited to 'openstackclient')
26 files changed, 1736 insertions, 58 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/functional/volume/v1/test_transfer_request.py b/openstackclient/tests/functional/volume/v1/test_transfer_request.py new file mode 100644 index 00000000..d8406b02 --- /dev/null +++ b/openstackclient/tests/functional/volume/v1/test_transfer_request.py @@ -0,0 +1,53 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from openstackclient.tests.functional.volume.v1 import common + + +class TransferRequestTests(common.BaseVolumeTests): + """Functional tests for transfer request. """ + + NAME = uuid.uuid4().hex + VOLUME_NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + super(TransferRequestTests, cls).setUpClass() + opts = cls.get_opts(['display_name']) + raw_output = cls.openstack( + 'volume create --size 1 ' + cls.VOLUME_NAME + opts) + cls.assertOutput(cls.VOLUME_NAME + '\n', raw_output) + + opts = cls.get_opts(cls.FIELDS) + raw_output = cls.openstack( + 'volume transfer request create ' + + cls.VOLUME_NAME + + ' --name ' + cls.NAME + opts) + cls.assertOutput(cls.NAME + '\n', raw_output) + + @classmethod + def tearDownClass(cls): + raw_output_transfer = cls.openstack( + 'volume transfer request delete ' + cls.NAME) + raw_output_volume = cls.openstack( + 'volume delete ' + cls.VOLUME_NAME) + cls.assertOutput('', raw_output_transfer) + cls.assertOutput('', raw_output_volume) + + def test_volume_transfer_request_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('volume transfer request list' + opts) + self.assertIn(self.NAME, raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py new file mode 100644 index 00000000..4f02238b --- /dev/null +++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py @@ -0,0 +1,53 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from openstackclient.tests.functional.volume.v2 import common + + +class TransferRequestTests(common.BaseVolumeTests): + """Functional tests for transfer request. """ + + NAME = uuid.uuid4().hex + VOLUME_NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + super(TransferRequestTests, cls).setUpClass() + opts = cls.get_opts(cls.FIELDS) + + raw_output = cls.openstack( + 'volume create --size 1 ' + cls.VOLUME_NAME + opts) + cls.assertOutput(cls.VOLUME_NAME + '\n', raw_output) + + raw_output = cls.openstack( + 'volume transfer request create ' + + cls.VOLUME_NAME + + ' --name ' + cls.NAME + opts) + cls.assertOutput(cls.NAME + '\n', raw_output) + + @classmethod + def tearDownClass(cls): + raw_output_transfer = cls.openstack( + 'volume transfer request delete ' + cls.NAME) + raw_output_volume = cls.openstack( + 'volume delete ' + cls.VOLUME_NAME) + cls.assertOutput('', raw_output_transfer) + cls.assertOutput('', raw_output_volume) + + def test_volume_transfer_request_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('volume transfer request list' + opts) + self.assertIn(self.NAME, raw_output) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index c6fee7d1..9fb6fb9b 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 @@ -143,9 +146,12 @@ class FakeTransfer(object): """ # Set default attribute transfer_info = { - 'volume_id': 'ce26708d-a7f8-4b4b-9861-4a80256615a7', + 'auth_key': 'key-' + uuid.uuid4().hex, + 'created_at': 'time-' + uuid.uuid4().hex, + 'volume_id': 'volume-id-' + uuid.uuid4().hex, 'name': 'fake_transfer_name', - 'id': '731a7f53-aa92-4fbd-9de3-6f7d729c926b' + 'id': 'id-' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, } # Overwrite default attributes if there are some attributes set @@ -160,6 +166,43 @@ class FakeTransfer(object): return transfer + @staticmethod + def create_transfers(attrs=None, count=2): + """Create multiple fake transfers. + + :param Dictionary attrs: + A dictionary with all attributes of transfer + :param Integer count: + The number of transfers to be faked + :return: + A list of FakeResource objects + """ + transfers = [] + for n in range(0, count): + transfers.append(FakeTransfer.create_one_transfer(attrs)) + + return transfers + + @staticmethod + def get_transfers(transfers=None, count=2): + """Get an iterable MagicMock object with a list of faked transfers. + + If transfers list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List transfers: + A list of FakeResource objects faking transfers + :param Integer count: + The number of transfers to be faked + :return + An iterable Mock object with side_effect set to a list of faked + transfers + """ + if transfers is None: + transfers = FakeTransfer.create_transfers(count) + + return mock.MagicMock(side_effect=transfers) + class FakeService(object): """Fake one or more Services.""" @@ -234,6 +277,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): @@ -278,3 +474,77 @@ class TestVolumev1(utils.TestCommand): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + +class FakeType(object): + """Fake one or more type.""" + + @staticmethod + def create_one_type(attrs=None, methods=None): + """Create a fake type. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + type_info = { + "id": 'type-id-' + uuid.uuid4().hex, + "name": 'type-name-' + uuid.uuid4().hex, + "description": 'type-description-' + uuid.uuid4().hex, + "extra_specs": {"foo": "bar"}, + "is_public": True, + } + + # Overwrite default attributes. + type_info.update(attrs) + + volume_type = fakes.FakeResource( + info=copy.deepcopy(type_info), + methods=methods, + loaded=True) + return volume_type + + @staticmethod + def create_types(attrs=None, count=2): + """Create multiple fake types. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of types to fake + :return: + A list of FakeResource objects faking the types + """ + volume_types = [] + for i in range(0, count): + volume_type = FakeType.create_one_type(attrs) + volume_types.append(volume_type) + + return volume_types + + @staticmethod + def get_types(types=None, count=2): + """Get an iterable MagicMock object with a list of faked types. + + If types list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List types: + A list of FakeResource objects faking types + :param Integer count: + The number of types to be faked + :return + An iterable Mock object with side_effect set to a list of faked + types + """ + if types is None: + types = FakeType.create_types(count) + + return mock.MagicMock(side_effect=types) 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_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py index f7980c34..c3b8dbce 100644 --- a/openstackclient/tests/unit/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -12,6 +12,11 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.tests.unit.volume.v1 import fakes as transfer_fakes from openstackclient.volume.v1 import volume_transfer_request @@ -26,6 +31,155 @@ class TestTransfer(transfer_fakes.TestVolumev1): self.transfer_mock = self.app.client_manager.volume.transfers self.transfer_mock.reset_mock() + # Get a shortcut to the VolumeManager Mock + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + +class TestTransferCreate(TestTransfer): + + volume = transfer_fakes.FakeVolume.create_one_volume() + + columns = ( + 'auth_key', + 'created_at', + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferCreate, self).setUp() + + self.volume_transfer = transfer_fakes.FakeTransfer.create_one_transfer( + attrs={'volume_id': self.volume.id}) + self.data = ( + self.volume_transfer.auth_key, + self.volume_transfer.created_at, + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.create.return_value = self.volume_transfer + self.volumes_mock.get.return_value = self.volume + + # Get the command object to test + self.cmd = volume_transfer_request.CreateTransferRequest( + self.app, None) + + def test_transfer_create_without_name(self): + arglist = [ + self.volume.id, + ] + verifylist = [ + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, None) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_create_with_name(self): + arglist = [ + '--name', self.volume_transfer.name, + self.volume.id, + ] + verifylist = [ + ('name', self.volume_transfer.name), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, self.volume_transfer.name,) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestTransferDelete(TestTransfer): + + volume_transfers = transfer_fakes.FakeTransfer.create_transfers(count=2) + + def setUp(self): + super(TestTransferDelete, self).setUp() + + self.transfer_mock.get = ( + transfer_fakes.FakeTransfer.get_transfers(self.volume_transfers)) + self.transfer_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_transfer_request.DeleteTransferRequest( + self.app, None) + + def test_transfer_delete(self): + arglist = [ + self.volume_transfers[0].id + ] + verifylist = [ + ("transfer_request", [self.volume_transfers[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.transfer_mock.delete.assert_called_with( + self.volume_transfers[0].id) + self.assertIsNone(result) + + def test_delete_multiple_transfers(self): + arglist = [] + for v in self.volume_transfers: + arglist.append(v.id) + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for v in self.volume_transfers: + calls.append(call(v.id)) + self.transfer_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_transfers_with_exception(self): + arglist = [ + self.volume_transfers[0].id, + 'unexist_transfer', + ] + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.volume_transfers[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 volume transfer requests failed ' + 'to delete.', str(e)) + + find_mock.assert_any_call( + self.transfer_mock, self.volume_transfers[0].id) + find_mock.assert_any_call(self.transfer_mock, 'unexist_transfer') + + self.assertEqual(2, find_mock.call_count) + self.transfer_mock.delete.assert_called_once_with( + self.volume_transfers[0].id, + ) + class TestTransferList(TestTransfer): diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py new file mode 100644 index 00000000..35016dc6 --- /dev/null +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -0,0 +1,347 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.tests.unit import utils as tests_utils +from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes +from openstackclient.volume.v1 import volume_type + + +class TestType(volume_fakes.TestVolumev1): + + def setUp(self): + super(TestType, self).setUp() + + self.types_mock = self.app.client_manager.volume.volume_types + self.types_mock.reset_mock() + + +class TestTypeCreate(TestType): + + columns = ( + 'description', + 'id', + 'is_public', + 'name', + ) + + def setUp(self): + super(TestTypeCreate, self).setUp() + + self.new_volume_type = volume_fakes.FakeType.create_one_type( + methods={'set_keys': {'myprop': 'myvalue'}} + ) + self.data = ( + self.new_volume_type.description, + self.new_volume_type.id, + True, + self.new_volume_type.name, + ) + + self.types_mock.create.return_value = self.new_volume_type + # Get the command object to test + self.cmd = volume_type.CreateVolumeType(self.app, None) + + def test_type_create(self): + arglist = [ + self.new_volume_type.name, + ] + verifylist = [ + ("name", self.new_volume_type.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.create.assert_called_with( + self.new_volume_type.name, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestTypeDelete(TestType): + + volume_types = volume_fakes.FakeType.create_types(count=2) + + def setUp(self): + super(TestTypeDelete, self).setUp() + + self.types_mock.get = volume_fakes.FakeType.get_types( + self.volume_types) + self.types_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_type.DeleteVolumeType(self.app, None) + + def test_type_delete(self): + arglist = [ + self.volume_types[0].id + ] + verifylist = [ + ("volume_types", [self.volume_types[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.types_mock.delete.assert_called_with(self.volume_types[0]) + self.assertIsNone(result) + + def test_delete_multiple_types(self): + arglist = [] + for t in self.volume_types: + arglist.append(t.id) + verifylist = [ + ('volume_types', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for t in self.volume_types: + calls.append(call(t)) + self.types_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_types_with_exception(self): + arglist = [ + self.volume_types[0].id, + 'unexist_type', + ] + verifylist = [ + ('volume_types', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.volume_types[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 volume types failed to delete.', + str(e)) + + find_mock.assert_any_call( + self.types_mock, self.volume_types[0].id) + find_mock.assert_any_call(self.types_mock, 'unexist_type') + + self.assertEqual(2, find_mock.call_count) + self.types_mock.delete.assert_called_once_with( + self.volume_types[0] + ) + + +class TestTypeList(TestType): + + volume_types = volume_fakes.FakeType.create_types() + + columns = ( + "ID", + "Name" + ) + columns_long = ( + "ID", + "Name", + "Properties" + ) + + data = [] + for t in volume_types: + data.append(( + t.id, + t.name, + )) + data_long = [] + for t in volume_types: + data_long.append(( + t.id, + t.name, + utils.format_dict(t.extra_specs), + )) + + def setUp(self): + super(TestTypeList, self).setUp() + + self.types_mock.list.return_value = self.volume_types + # get the command to test + self.cmd = volume_type.ListVolumeType(self.app, None) + + def test_type_list_without_options(self): + arglist = [] + verifylist = [ + ("long", False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_type_list_with_options(self): + arglist = [ + "--long", + ] + verifylist = [ + ("long", True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.list.assert_called_once_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + +class TestTypeSet(TestType): + + volume_type = volume_fakes.FakeType.create_one_type( + methods={'set_keys': None}) + + def setUp(self): + super(TestTypeSet, self).setUp() + + self.types_mock.get.return_value = self.volume_type + + # Get the command object to test + self.cmd = volume_type.SetVolumeType(self.app, None) + + def test_type_set_nothing(self): + arglist = [ + self.volume_type.id, + ] + verifylist = [ + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + + def test_type_set_property(self): + arglist = [ + '--property', 'myprop=myvalue', + self.volume_type.id, + ] + verifylist = [ + ('property', {'myprop': 'myvalue'}), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volume_type.set_keys.assert_called_once_with( + {'myprop': 'myvalue'}) + self.assertIsNone(result) + + +class TestTypeShow(TestType): + + columns = ( + 'description', + 'id', + 'is_public', + 'name', + 'properties', + ) + + def setUp(self): + super(TestTypeShow, self).setUp() + + self.volume_type = volume_fakes.FakeType.create_one_type() + self.data = ( + self.volume_type.description, + self.volume_type.id, + True, + self.volume_type.name, + utils.format_dict(self.volume_type.extra_specs) + ) + + self.types_mock.get.return_value = self.volume_type + + # Get the command object to test + self.cmd = volume_type.ShowVolumeType(self.app, None) + + def test_type_show(self): + arglist = [ + self.volume_type.id + ] + verifylist = [ + ("volume_type", self.volume_type.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.get.assert_called_with(self.volume_type.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestTypeUnset(TestType): + + volume_type = volume_fakes.FakeType.create_one_type( + methods={'unset_keys': None}) + + def setUp(self): + super(TestTypeUnset, self).setUp() + + self.types_mock.get.return_value = self.volume_type + + # Get the command object to test + self.cmd = volume_type.UnsetVolumeType(self.app, None) + + def test_type_unset(self): + arglist = [ + '--property', 'property', + '--property', 'multi_property', + self.volume_type.id, + ] + verifylist = [ + ('property', ['property', 'multi_property']), + ('volume_type', self.volume_type.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volume_type.unset_keys.assert_called_once_with( + ['property', 'multi_property']) + self.assertIsNone(result) + + def test_type_unset_failed_with_missing_volume_type_argument(self): + arglist = [ + '--property', 'property', + '--property', 'multi_property', + ] + verifylist = [ + ('property', ['property', 'multi_property']), + ] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) 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/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index a958c468..e79a9407 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -39,9 +39,12 @@ class FakeTransfer(object): """ # Set default attribute transfer_info = { - 'volume_id': 'ce26708d-a7f8-4b4b-9861-4a80256615a7', + 'auth_key': 'key-' + uuid.uuid4().hex, + 'created_at': 'time-' + uuid.uuid4().hex, + 'volume_id': 'volume-id-' + uuid.uuid4().hex, 'name': 'fake_transfer_name', - 'id': '731a7f53-aa92-4fbd-9de3-6f7d729c926b' + 'id': 'id-' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, } # Overwrite default attributes if there are some attributes set @@ -56,6 +59,43 @@ class FakeTransfer(object): return transfer + @staticmethod + def create_transfers(attrs=None, count=2): + """Create multiple fake transfers. + + :param Dictionary attrs: + A dictionary with all attributes of transfer + :param Integer count: + The number of transfers to be faked + :return: + A list of FakeResource objects + """ + transfers = [] + for n in range(0, count): + transfers.append(FakeTransfer.create_one_transfer(attrs)) + + return transfers + + @staticmethod + def get_transfers(transfers=None, count=2): + """Get an iterable MagicMock object with a list of faked transfers. + + If transfers list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List transfers: + A list of FakeResource objects faking transfers + :param Integer count: + The number of transfers to be faked + :return + An iterable Mock object with side_effect set to a list of faked + transfers + """ + if transfers is None: + transfers = FakeTransfer.create_transfers(count) + + return mock.MagicMock(side_effect=transfers) + class FakeTypeAccess(object): """Fake one or more volume type access.""" diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index 333d8d72..d355662d 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -376,6 +376,55 @@ class TestSnapshotSet(TestSnapshot): self.snapshot.id, "error") self.assertIsNone(result) + def test_volume_set_state_failed(self): + self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() + arglist = [ + '--state', 'error', + self.snapshot.id + ] + verifylist = [ + ('state', 'error'), + ('snapshot', self.snapshot.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the set operations failed', + str(e)) + self.snapshots_mock.reset_state.assert_called_once_with( + self.snapshot.id, 'error') + + def test_volume_set_name_and_state_failed(self): + self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() + arglist = [ + '--state', 'error', + "--name", "new_snapshot", + self.snapshot.id + ] + verifylist = [ + ('state', 'error'), + ("name", "new_snapshot"), + ('snapshot', self.snapshot.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the set operations failed', + str(e)) + kwargs = { + "name": "new_snapshot", + } + self.snapshots_mock.update.assert_called_once_with( + self.snapshot.id, **kwargs) + self.snapshots_mock.reset_state.assert_called_once_with( + self.snapshot.id, 'error') + class TestSnapshotShow(TestSnapshot): diff --git a/openstackclient/tests/unit/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py index 32108c02..a18e4c52 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -12,6 +12,11 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.tests.unit.volume.v2 import fakes as transfer_fakes from openstackclient.volume.v2 import volume_transfer_request @@ -26,6 +31,155 @@ class TestTransfer(transfer_fakes.TestVolume): self.transfer_mock = self.app.client_manager.volume.transfers self.transfer_mock.reset_mock() + # Get a shortcut to the VolumeManager Mock + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + +class TestTransferCreate(TestTransfer): + + volume = transfer_fakes.FakeVolume.create_one_volume() + + columns = ( + 'auth_key', + 'created_at', + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferCreate, self).setUp() + + self.volume_transfer = transfer_fakes.FakeTransfer.create_one_transfer( + attrs={'volume_id': self.volume.id}) + self.data = ( + self.volume_transfer.auth_key, + self.volume_transfer.created_at, + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.create.return_value = self.volume_transfer + self.volumes_mock.get.return_value = self.volume + + # Get the command object to test + self.cmd = volume_transfer_request.CreateTransferRequest( + self.app, None) + + def test_transfer_create_without_name(self): + arglist = [ + self.volume.id, + ] + verifylist = [ + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, None) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_create_with_name(self): + arglist = [ + '--name', self.volume_transfer.name, + self.volume.id, + ] + verifylist = [ + ('name', self.volume_transfer.name), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, self.volume_transfer.name,) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestTransferDelete(TestTransfer): + + volume_transfers = transfer_fakes.FakeTransfer.create_transfers(count=2) + + def setUp(self): + super(TestTransferDelete, self).setUp() + + self.transfer_mock.get = ( + transfer_fakes.FakeTransfer.get_transfers(self.volume_transfers)) + self.transfer_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_transfer_request.DeleteTransferRequest( + self.app, None) + + def test_transfer_delete(self): + arglist = [ + self.volume_transfers[0].id + ] + verifylist = [ + ("transfer_request", [self.volume_transfers[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.transfer_mock.delete.assert_called_with( + self.volume_transfers[0].id) + self.assertIsNone(result) + + def test_delete_multiple_transfers(self): + arglist = [] + for v in self.volume_transfers: + arglist.append(v.id) + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for v in self.volume_transfers: + calls.append(call(v.id)) + self.transfer_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_transfers_with_exception(self): + arglist = [ + self.volume_transfers[0].id, + 'unexist_transfer', + ] + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.volume_transfers[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 volume transfer requests failed ' + 'to delete.', str(e)) + + find_mock.assert_any_call( + self.transfer_mock, self.volume_transfers[0].id) + find_mock.assert_any_call(self.transfer_mock, 'unexist_transfer') + + self.assertEqual(2, find_mock.call_count) + self.transfer_mock.delete.assert_called_once_with( + self.volume_transfers[0].id, + ) + class TestTransferList(TestTransfer): 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 bb3a1fc3..c4d113a3 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -16,15 +16,20 @@ """Volume v1 Snapshot action implementations""" import copy +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 CreateSnapshot(command.ShowOne): """Create new snapshot""" @@ -88,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): @@ -199,17 +218,31 @@ class SetSnapshot(command.Command): snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) + result = 0 if parsed_args.property: - volume_client.volume_snapshots.set_metadata(snapshot.id, - parsed_args.property) + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set snapshot property: %s"), e) + result += 1 kwargs = {} if parsed_args.name: kwargs['display_name'] = parsed_args.name if parsed_args.description: kwargs['display_description'] = parsed_args.description - - snapshot.update(**kwargs) + if kwargs: + try: + snapshot.update(**kwargs) + except Exception as e: + LOG.error(_("Failed to update snapshot display name " + "or display description: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) class ShowSnapshot(command.ShowOne): 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/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index 5d8ff683..8c505528 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -12,14 +12,85 @@ # under the License. # -"""Volume v2 transfer action implementations""" +"""Volume v1 transfer action implementations""" + +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 CreateTransferRequest(command.ShowOne): + """Create volume transfer request.""" + + def get_parser(self, prog_name): + parser = super(CreateTransferRequest, self).get_parser(prog_name) + parser.add_argument( + '--name', + metavar="<name>", + help=_('New transfer request name (default to None)') + ) + parser.add_argument( + 'volume', + metavar="<volume>", + help=_('Volume to transfer (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + volume_transfer_request = volume_client.transfers.create( + volume_id, parsed_args.name, + ) + volume_transfer_request._info.pop("links", None) + + return zip(*sorted(six.iteritems(volume_transfer_request._info))) + + +class DeleteTransferRequest(command.Command): + """Delete volume transfer request(s).""" + + def get_parser(self, prog_name): + parser = super(DeleteTransferRequest, self).get_parser(prog_name) + parser.add_argument( + 'transfer_request', + metavar="<transfer-request>", + nargs="+", + help=_('Volume transfer request(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for t in parsed_args.transfer_request: + try: + transfer_request_id = utils.find_resource( + volume_client.transfers, t).id + volume_client.transfers.delete(transfer_request_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume transfer request " + "with name or ID '%(transfer)s': %(e)s") + % {'transfer': t, 'e': e}) + + if result > 0: + total = len(parsed_args.transfer_request) + msg = (_("%(result)s of %(total)s volume transfer requests failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + class ListTransferRequests(command.Lister): """Lists all volume transfer requests.""" diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 8304a5eb..0d826551 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -240,19 +240,40 @@ class SetSnapshot(command.Command): snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) + result = 0 + if parsed_args.property: + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set snapshot property: %s"), e) + result += 1 + + if parsed_args.state: + try: + volume_client.volume_snapshots.reset_state( + snapshot.id, parsed_args.state) + except Exception as e: + LOG.error(_("Failed to set snapshot state: %s"), e) + result += 1 + kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name if parsed_args.description: kwargs['description'] = parsed_args.description + if kwargs: + try: + volume_client.volume_snapshots.update( + snapshot.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update snapshot name " + "or description: %s"), e) + result += 1 - if parsed_args.property: - volume_client.volume_snapshots.set_metadata(snapshot.id, - parsed_args.property) - if parsed_args.state: - volume_client.volume_snapshots.reset_state(snapshot.id, - parsed_args.state) - volume_client.volume_snapshots.update(snapshot.id, **kwargs) + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) class ShowSnapshot(command.ShowOne): 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( diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 5d8ff683..f5de8d7a 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -14,12 +14,83 @@ """Volume v2 transfer action implementations""" +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 CreateTransferRequest(command.ShowOne): + """Create volume transfer request.""" + + def get_parser(self, prog_name): + parser = super(CreateTransferRequest, self).get_parser(prog_name) + parser.add_argument( + '--name', + metavar="<name>", + help=_('New transfer request name (default to None)'), + ) + parser.add_argument( + 'volume', + metavar="<volume>", + help=_('Volume to transfer (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + volume_transfer_request = volume_client.transfers.create( + volume_id, parsed_args.name, + ) + volume_transfer_request._info.pop("links", None) + + return zip(*sorted(six.iteritems(volume_transfer_request._info))) + + +class DeleteTransferRequest(command.Command): + """Delete volume transfer request(s).""" + + def get_parser(self, prog_name): + parser = super(DeleteTransferRequest, self).get_parser(prog_name) + parser.add_argument( + 'transfer_request', + metavar="<transfer-request>", + nargs="+", + help=_('Volume transfer request(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for t in parsed_args.transfer_request: + try: + transfer_request_id = utils.find_resource( + volume_client.transfers, t).id + volume_client.transfers.delete(transfer_request_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume transfer request " + "with name or ID '%(transfer)s': %(e)s") + % {'transfer': t, 'e': e}) + + if result > 0: + total = len(parsed_args.transfer_request) + msg = (_("%(result)s of %(total)s volume transfer requests failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + class ListTransferRequests(command.Lister): """Lists all volume transfer requests.""" |
