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]