diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index ef52e4b008..ced7b3b6b5 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -449,6 +449,8 @@ class FakeVolumev1Client(object): self.volume_types.resource_class = fakes.FakeResource(None, {}) self.transfers = mock.Mock() self.transfers.resource_class = fakes.FakeResource(None, {}) + self.volume_snapshots = mock.Mock() + self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -546,3 +548,79 @@ class FakeType(object): types = FakeType.create_types(count) return mock.Mock(side_effect=types) + + +class FakeSnapshot(object): + """Fake one or more snapshot.""" + + @staticmethod + def create_one_snapshot(attrs=None): + """Create a fake snapshot. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + + # Set default attributes. + snapshot_info = { + "id": 'snapshot-id-' + uuid.uuid4().hex, + "display_name": 'snapshot-name-' + uuid.uuid4().hex, + "display_description": 'snapshot-description-' + uuid.uuid4().hex, + "size": 10, + "status": "available", + "metadata": {"foo": "bar"}, + "created_at": "2015-06-03T18:49:19.000000", + "volume_id": 'vloume-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + snapshot_info.update(attrs) + + snapshot_method = {'update': None} + + snapshot = fakes.FakeResource( + info=copy.deepcopy(snapshot_info), + methods=copy.deepcopy(snapshot_method), + loaded=True) + return snapshot + + @staticmethod + def create_snapshots(attrs=None, count=2): + """Create multiple fake snapshots. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of snapshots to fake + :return: + A list of FakeResource objects faking the snapshots + """ + snapshots = [] + for i in range(0, count): + snapshot = FakeSnapshot.create_one_snapshot(attrs) + snapshots.append(snapshot) + + return snapshots + + @staticmethod + def get_snapshots(snapshots=None, count=2): + """Get an iterable MagicMock object with a list of faked snapshots. + + If snapshots list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking snapshots + :param Integer count: + The number of snapshots to be faked + :return + An iterable Mock object with side_effect set to a list of faked + snapshots + """ + if snapshots is None: + snapshots = FakeSnapshot.create_snapshots(count) + + return mock.Mock(side_effect=snapshots) diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 81680ab4ef..1982980a32 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -517,3 +517,16 @@ class TestQosUnset(TestQos): ['iops', 'foo'] ) self.assertIsNone(result) + + def test_qos_unset_nothing(self): + arglist = [ + volume_fakes.qos_id, + ] + + verifylist = [ + ('qos_spec', volume_fakes.qos_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v1/test_snapshot.py b/openstackclient/tests/unit/volume/v1/test_snapshot.py new file mode 100644 index 0000000000..edfbdc190c --- /dev/null +++ b/openstackclient/tests/unit/volume/v1/test_snapshot.py @@ -0,0 +1,467 @@ +# +# 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.volume.v1 import fakes as volume_fakes +from openstackclient.volume.v1 import snapshot + + +class TestSnapshot(volume_fakes.TestVolumev1): + + def setUp(self): + super(TestSnapshot, self).setUp() + + self.snapshots_mock = self.app.client_manager.volume.volume_snapshots + self.snapshots_mock.reset_mock() + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + +class TestSnapshotCreate(TestSnapshot): + + columns = ( + 'created_at', + 'display_description', + 'display_name', + 'id', + 'properties', + 'size', + 'status', + 'volume_id', + ) + + def setUp(self): + super(TestSnapshotCreate, self).setUp() + + self.volume = volume_fakes.FakeVolume.create_one_volume() + self.new_snapshot = volume_fakes.FakeSnapshot.create_one_snapshot( + attrs={'volume_id': self.volume.id}) + + self.data = ( + self.new_snapshot.created_at, + self.new_snapshot.display_description, + self.new_snapshot.display_name, + self.new_snapshot.id, + utils.format_dict(self.new_snapshot.metadata), + self.new_snapshot.size, + self.new_snapshot.status, + self.new_snapshot.volume_id, + ) + + self.volumes_mock.get.return_value = self.volume + self.snapshots_mock.create.return_value = self.new_snapshot + # Get the command object to test + self.cmd = snapshot.CreateSnapshot(self.app, None) + + def test_snapshot_create(self): + arglist = [ + "--name", self.new_snapshot.display_name, + "--description", self.new_snapshot.display_description, + "--force", + self.new_snapshot.volume_id, + ] + verifylist = [ + ("name", self.new_snapshot.display_name), + ("description", self.new_snapshot.display_description), + ("force", True), + ("volume", self.new_snapshot.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.create.assert_called_with( + self.new_snapshot.volume_id, + True, + self.new_snapshot.display_name, + self.new_snapshot.display_description, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_snapshot_create_without_name(self): + arglist = [ + self.new_snapshot.volume_id, + "--description", self.new_snapshot.display_description, + "--force" + ] + verifylist = [ + ("volume", self.new_snapshot.volume_id), + ("description", self.new_snapshot.display_description), + ("force", True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.create.assert_called_with( + self.new_snapshot.volume_id, + True, + None, + self.new_snapshot.display_description, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestSnapshotDelete(TestSnapshot): + + snapshots = volume_fakes.FakeSnapshot.create_snapshots(count=2) + + def setUp(self): + super(TestSnapshotDelete, self).setUp() + + self.snapshots_mock.get = ( + volume_fakes.FakeSnapshot.get_snapshots(self.snapshots)) + self.snapshots_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = snapshot.DeleteSnapshot(self.app, None) + + def test_snapshot_delete(self): + arglist = [ + self.snapshots[0].id + ] + verifylist = [ + ("snapshots", [self.snapshots[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.delete.assert_called_with( + self.snapshots[0].id) + self.assertIsNone(result) + + def test_delete_multiple_snapshots(self): + arglist = [] + for s in self.snapshots: + arglist.append(s.id) + verifylist = [ + ('snapshots', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self.snapshots: + calls.append(call(s.id)) + self.snapshots_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_snapshots_with_exception(self): + arglist = [ + self.snapshots[0].id, + 'unexist_snapshot', + ] + verifylist = [ + ('snapshots', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.snapshots[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 snapshots failed to delete.', + str(e)) + + find_mock.assert_any_call( + self.snapshots_mock, self.snapshots[0].id) + find_mock.assert_any_call(self.snapshots_mock, 'unexist_snapshot') + + self.assertEqual(2, find_mock.call_count) + self.snapshots_mock.delete.assert_called_once_with( + self.snapshots[0].id + ) + + +class TestSnapshotList(TestSnapshot): + + volume = volume_fakes.FakeVolume.create_one_volume() + snapshots = volume_fakes.FakeSnapshot.create_snapshots( + attrs={'volume_id': volume.display_name}, count=3) + + columns = [ + "ID", + "Name", + "Description", + "Status", + "Size" + ] + columns_long = columns + [ + "Created At", + "Volume", + "Properties" + ] + + data = [] + for s in snapshots: + data.append(( + s.id, + s.display_name, + s.display_description, + s.status, + s.size, + )) + data_long = [] + for s in snapshots: + data_long.append(( + s.id, + s.display_name, + s.display_description, + s.status, + s.size, + s.created_at, + s.volume_id, + utils.format_dict(s.metadata), + )) + + def setUp(self): + super(TestSnapshotList, self).setUp() + + self.volumes_mock.list.return_value = [self.volume] + self.snapshots_mock.list.return_value = self.snapshots + # Get the command to test + self.cmd = snapshot.ListSnapshot(self.app, None) + + def test_snapshot_list_without_options(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ("long", False) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={'all_tenants': False}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_with_long(self): + arglist = [ + "--long", + ] + verifylist = [ + ("long", True), + ('all_projects', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={'all_tenants': False} + ) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + def test_snapshot_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={'all_tenants': True}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestSnapshotSet(TestSnapshot): + + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + def setUp(self): + super(TestSnapshotSet, self).setUp() + + self.snapshots_mock.get.return_value = self.snapshot + self.snapshots_mock.set_metadata.return_value = None + # Get the command object to mock + self.cmd = snapshot.SetSnapshot(self.app, None) + + def test_snapshot_set_all(self): + arglist = [ + "--name", "new_snapshot", + "--description", "new_description", + "--property", "x=y", + "--property", "foo=foo", + self.snapshot.id, + ] + new_property = {"x": "y", "foo": "foo"} + verifylist = [ + ("name", "new_snapshot"), + ("description", "new_description"), + ("property", new_property), + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + "display_name": "new_snapshot", + "display_description": "new_description", + } + self.snapshot.update.assert_called_with(**kwargs) + self.snapshots_mock.set_metadata.assert_called_with( + self.snapshot.id, new_property + ) + self.assertIsNone(result) + + def test_snapshot_set_nothing(self): + arglist = [ + self.snapshot.id, + ] + verifylist = [ + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + def test_snapshot_set_fail(self): + self.snapshots_mock.set_metadata.side_effect = ( + exceptions.CommandError()) + arglist = [ + "--name", "new_snapshot", + "--description", "new_description", + "--property", "x=y", + "--property", "foo=foo", + self.snapshot.id, + ] + new_property = {"x": "y", "foo": "foo"} + verifylist = [ + ("name", "new_snapshot"), + ("description", "new_description"), + ("property", new_property), + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + + +class TestSnapshotShow(TestSnapshot): + + columns = ( + 'created_at', + 'display_description', + 'display_name', + 'id', + 'properties', + 'size', + 'status', + 'volume_id', + ) + + def setUp(self): + super(TestSnapshotShow, self).setUp() + + self.snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + self.data = ( + self.snapshot.created_at, + self.snapshot.display_description, + self.snapshot.display_name, + self.snapshot.id, + utils.format_dict(self.snapshot.metadata), + self.snapshot.size, + self.snapshot.status, + self.snapshot.volume_id, + ) + + self.snapshots_mock.get.return_value = self.snapshot + # Get the command object to test + self.cmd = snapshot.ShowSnapshot(self.app, None) + + def test_snapshot_show(self): + arglist = [ + self.snapshot.id + ] + verifylist = [ + ("snapshot", self.snapshot.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_with(self.snapshot.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestSnapshotUnset(TestSnapshot): + + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + def setUp(self): + super(TestSnapshotUnset, self).setUp() + + self.snapshots_mock.get.return_value = self.snapshot + self.snapshots_mock.delete_metadata.return_value = None + # Get the command object to mock + self.cmd = snapshot.UnsetSnapshot(self.app, None) + + def test_snapshot_unset(self): + arglist = [ + "--property", "foo", + self.snapshot.id, + ] + verifylist = [ + ("property", ["foo"]), + ("snapshot", self.snapshot.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.delete_metadata.assert_called_with( + self.snapshot.id, ["foo"] + ) + self.assertIsNone(result) + + def test_snapshot_unset_nothing(self): + arglist = [ + self.snapshot.id, + ] + verifylist = [ + ("snapshot", self.snapshot.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index 35016dc6b6..23a1186dad 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -345,3 +345,16 @@ class TestTypeUnset(TestType): self.cmd, arglist, verifylist) + + def test_type_unset_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) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index b982c0e604..93c24a212a 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -273,7 +273,6 @@ class UnsetQos(command.Command): '--property', metavar='', action='append', - default=[], help=_('Property to remove from the QoS specification. ' '(repeat option to unset multiple properties)'), ) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index c4d113a31a..bc92c0f56d 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -283,8 +283,6 @@ class UnsetSnapshot(command.Command): '--property', metavar='', action='append', - default=[], - required=True, help=_('Property to remove from snapshot ' '(repeat option to remove multiple properties)'), ) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 625b34dc43..61e9f7fca1 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -188,10 +188,8 @@ class UnsetVolumeType(command.Command): '--property', metavar='', action='append', - default=[], help=_('Remove a property from this volume type ' '(repeat option to remove multiple properties)'), - required=True, ) return parser