From d2273ecea5d540f4dacc89772870722355f2492f Mon Sep 17 00:00:00 2001 From: Huanxuan Ao <huanxuan.ao@easystack.cn> Date: Mon, 8 Aug 2016 12:09:19 +0800 Subject: [PATCH] Implement "volume transfer request delete" command Add "volume transfer request delete" command in volume v1 and v2. Also add the unit tests, docs, release note and functional tests Change-Id: Ic3d375bc8df3312fac53c1800d75f48376b8c91c Implements: bp cinder-command-support Co-Authored-By: Sheel Rana <ranasheel2000@gmail.com> --- .../volume-transfer-request.rst | 16 ++++ .../volume/v1/test_transfer_request.py | 53 ++++++++++++ .../volume/v2/test_transfer_request.py | 53 ++++++++++++ openstackclient/tests/unit/volume/v1/fakes.py | 37 ++++++++ .../unit/volume/v1/test_transfer_request.py | 84 +++++++++++++++++++ openstackclient/tests/unit/volume/v2/fakes.py | 37 ++++++++ .../unit/volume/v2/test_transfer_request.py | 84 +++++++++++++++++++ .../volume/v1/volume_transfer_request.py | 41 +++++++++ .../volume/v2/volume_transfer_request.py | 41 +++++++++ ...nder-command-support-cc8708c4395ce467.yaml | 3 +- setup.cfg | 2 + 11 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 openstackclient/tests/functional/volume/v1/test_transfer_request.py create mode 100644 openstackclient/tests/functional/volume/v2/test_transfer_request.py diff --git a/doc/source/command-objects/volume-transfer-request.rst b/doc/source/command-objects/volume-transfer-request.rst index f9efb979fd..4729999574 100644 --- a/doc/source/command-objects/volume-transfer-request.rst +++ b/doc/source/command-objects/volume-transfer-request.rst @@ -25,6 +25,22 @@ Create volume transfer request Volume to transfer (name or ID) +volume transfer request delete +------------------------------ + +Delete volume transfer request(s) + +.. program:: volume transfer request delete +.. code:: bash + + os volume transfer request delete + <transfer-request> [<transfer-request> ...] + +.. _volume_transfer_request_delete-transfer-request: +.. describe:: <transfer-request> + + Volume transfer request(s) to delete (name or ID) + volume transfer request list ---------------------------- 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 0000000000..d8406b0295 --- /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 0000000000..4f02238bc9 --- /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 f63553fe71..c765a3c730 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -166,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.""" diff --git a/openstackclient/tests/unit/volume/v1/test_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py index a5c31f8d27..c3b8dbcea2 100644 --- a/openstackclient/tests/unit/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -12,6 +12,12 @@ # 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 @@ -97,6 +103,84 @@ class TestTransferCreate(TestTransfer): 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): # The Transfers to be listed diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 51c952cdc6..e79a9407f9 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -59,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_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py index 8cc76bef4c..a18e4c52dd 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -12,6 +12,12 @@ # 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 @@ -97,6 +103,84 @@ class TestTransferCreate(TestTransfer): 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): # The Transfers to be listed diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index b1167340fb..8c5055282a 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -14,13 +14,19 @@ """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.""" @@ -50,6 +56,41 @@ class CreateTransferRequest(command.ShowOne): 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/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 45581586e6..f5de8d7ae0 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -14,13 +14,19 @@ """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.""" @@ -50,6 +56,41 @@ class CreateTransferRequest(command.ShowOne): 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/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml b/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml index 686ff25e4e..5f94fd6709 100644 --- a/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml +++ b/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml @@ -1,4 +1,5 @@ --- features: - - Add ``volume transfer request create`` command in volume v1 and v2 + - Add ``volume transfer request create`` and ``volume transfer request delete`` + commands in volume v1 and v2. [Blueprint `cinder-command-support <https://blueprints.launchpad.net/python-openstackclient/+spec/cinder-command-support>`_] diff --git a/setup.cfg b/setup.cfg index 262c276a7f..81232ef183 100644 --- a/setup.cfg +++ b/setup.cfg @@ -484,6 +484,7 @@ openstack.volume.v1 = volume_service_set = openstackclient.volume.v1.service:SetService volume_transfer_request_create = openstackclient.volume.v1.volume_transfer_request:CreateTransferRequest + volume_transfer_request_delete = openstackclient.volume.v1.volume_transfer_request:DeleteTransferRequest volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequests openstack.volume.v2 = @@ -533,6 +534,7 @@ openstack.volume.v2 = volume_service_set = openstackclient.volume.v2.service:SetService volume_transfer_request_create = openstackclient.volume.v2.volume_transfer_request:CreateTransferRequest + volume_transfer_request_delete = openstackclient.volume.v2.volume_transfer_request:DeleteTransferRequest volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequests [build_sphinx]