summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/api/auth.py5
-rw-r--r--openstackclient/common/command.py5
-rw-r--r--openstackclient/common/exceptions.py5
-rw-r--r--openstackclient/common/logs.py5
-rw-r--r--openstackclient/common/parseractions.py5
-rw-r--r--openstackclient/common/timing.py5
-rw-r--r--openstackclient/common/utils.py5
-rw-r--r--openstackclient/tests/functional/volume/v1/test_transfer_request.py53
-rw-r--r--openstackclient/tests/functional/volume/v2/test_transfer_request.py53
-rw-r--r--openstackclient/tests/unit/volume/v1/fakes.py274
-rw-r--r--openstackclient/tests/unit/volume/v1/test_qos_specs.py77
-rw-r--r--openstackclient/tests/unit/volume/v1/test_transfer_request.py154
-rw-r--r--openstackclient/tests/unit/volume/v1/test_type.py347
-rw-r--r--openstackclient/tests/unit/volume/v1/test_volume.py144
-rw-r--r--openstackclient/tests/unit/volume/v2/fakes.py44
-rw-r--r--openstackclient/tests/unit/volume/v2/test_snapshot.py49
-rw-r--r--openstackclient/tests/unit/volume/v2/test_transfer_request.py154
-rw-r--r--openstackclient/tests/unit/volume/v2/test_volume.py71
-rw-r--r--openstackclient/volume/v1/backup.py26
-rw-r--r--openstackclient/volume/v1/qos_specs.py26
-rw-r--r--openstackclient/volume/v1/snapshot.py49
-rw-r--r--openstackclient/volume/v1/volume.py41
-rw-r--r--openstackclient/volume/v1/volume_transfer_request.py73
-rw-r--r--openstackclient/volume/v2/snapshot.py35
-rw-r--r--openstackclient/volume/v2/volume.py18
-rw-r--r--openstackclient/volume/v2/volume_transfer_request.py71
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."""