diff --git a/doc/source/cli/osc/v2/index.rst b/doc/source/cli/osc/v2/index.rst index 288b8c3b4..f5ebbcf5b 100644 --- a/doc/source/cli/osc/v2/index.rst +++ b/doc/source/cli/osc/v2/index.rst @@ -49,3 +49,10 @@ share quotas .. autoprogram-cliff:: openstack.share.v2 :command: share quota * + +=============== +share snapshots +=============== + +.. autoprogram-cliff:: openstack.share.v2 + :command: share snapshot * diff --git a/manilaclient/osc/v2/share_snapshots.py b/manilaclient/osc/v2/share_snapshots.py new file mode 100644 index 000000000..5676b604a --- /dev/null +++ b/manilaclient/osc/v2/share_snapshots.py @@ -0,0 +1,427 @@ +# 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 logging + +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from manilaclient import api_versions +from manilaclient.common._i18n import _ +from manilaclient.common import cliutils + +LOG = logging.getLogger(__name__) + + +class CreateShareSnapshot(command.ShowOne): + """Create a share snapshot.""" + _description = _( + "Create a snapshot of the given share") + + def get_parser(self, prog_name): + parser = super(CreateShareSnapshot, self).get_parser(prog_name) + parser.add_argument( + "share", + metavar="", + help=_("Name or ID of the share to create snapshot of") + ) + parser.add_argument( + "--force", + action='store_true', + default=False, + help=_("Optional flag to indicate whether to snapshot " + "a share even if it's busy. (Default=False)") + ) + parser.add_argument( + "--name", + metavar="", + default=None, + help=_("Add a name to the snapshot (Optional).") + ) + parser.add_argument( + "--description", + metavar="", + default=None, + help=_("Add a description to the snapshot (Optional).") + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + share = utils.find_resource(share_client.shares, + parsed_args.share) + + share_snapshot = share_client.share_snapshots.create( + share=share, + force=parsed_args.force, + name=parsed_args.name or None, + description=parsed_args.description or None + ) + share_snapshot._info.pop('links', None) + + return self.dict2columns(share_snapshot._info) + + +class DeleteShareSnapshot(command.Command): + """Delete one or more share snapshots""" + _description = _( + "Delete one or more share snapshots") + + def get_parser(self, prog_name): + parser = super(DeleteShareSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshot", + metavar="", + nargs="+", + help=_("Name or ID of the snapshot(s) to delete") + ) + parser.add_argument( + "--force", + action='store_true', + default=False, + help=_("Delete the snapshot(s) ignoring the current state.") + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + result = 0 + + for snapshot in parsed_args.snapshot: + try: + snapshot_obj = utils.find_resource( + share_client.share_snapshots, + snapshot) + if parsed_args.force: + share_client.share_snapshots.force_delete( + snapshot_obj) + else: + share_client.share_snapshots.delete( + snapshot_obj) + except Exception as e: + result += 1 + LOG.error(_( + "Failed to delete snapshot with " + "name or ID '%(snapshot)s': %(e)s"), + {'snapshot': snapshot, 'e': e}) + + if result > 0: + total = len(parsed_args.snapshot) + msg = (_("%(result)s of %(total)s snapshots failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ShowShareSnapshot(command.ShowOne): + """Display a share snapshot""" + _description = _( + "Show details about a share snapshot") + + def get_parser(self, prog_name): + parser = super(ShowShareSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshot", + metavar="", + help=_("Name or ID of the snapshot to display") + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + share_snapshot = utils.find_resource( + share_client.share_snapshots, + parsed_args.snapshot) + + export_locations = ( + share_client.share_snapshot_export_locations.list( + share_snapshot)) + export_locations = ( + cliutils.transform_export_locations_to_string_view( + export_locations)) + + data = share_snapshot._info + data['export_locations'] = export_locations + data.pop('links', None) + + return self.dict2columns(data) + + +class SetShareSnapshot(command.Command): + """Set share snapshot properties.""" + _description = _("Set share snapshot properties") + + def get_parser(self, prog_name): + parser = super(SetShareSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshot", + metavar="", + help=_('Name or ID of the snapshot to set a property for') + ) + parser.add_argument( + "--name", + metavar="", + default=None, + help=_("Set a name to the snapshot.") + ) + parser.add_argument( + "--description", + metavar="", + default=None, + help=_("Set a description to the snapshot.") + ) + parser.add_argument( + "--status", + metavar="", + choices=['available', 'error', 'creating', 'deleting', + 'manage_starting', 'manage_error', + 'unmanage_starting', 'unmanage_error', + 'error_deleting'], + help=_("Assign a status to the snapshot (Admin only). " + "Options include : available, error, creating, " + "deleting, manage_starting, manage_error, " + "unmanage_starting, unmanage_error, error_deleting.") + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + result = 0 + + share_snapshot = utils.find_resource( + share_client.share_snapshots, + parsed_args.snapshot) + + kwargs = {} + + if parsed_args.name is not None: + kwargs['display_name'] = parsed_args.name + if parsed_args.description is not None: + kwargs['display_description'] = parsed_args.description + + try: + share_client.share_snapshots.update( + share_snapshot, + **kwargs + ) + except Exception as e: + result += 1 + LOG.error(_( + "Failed to set share snapshot properties " + "'%(properties)s': %(exception)s"), + {'properties': kwargs, + 'exception': e}) + + if parsed_args.status: + try: + share_client.share_snapshots.reset_state( + share_snapshot, + parsed_args.status + ) + except Exception as e: + result += 1 + LOG.error(_( + "Failed to update snapshot status to " + "'%(status)s': %(e)s"), + {'status': parsed_args.status, 'e': e}) + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) + + +class UnsetShareSnapshot(command.Command): + """Unset a share snapshot property.""" + _description = _("Unset a share snapshot property") + + def get_parser(self, prog_name): + parser = super(UnsetShareSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshot", + metavar="", + help=_("Name or ID of the snapshot to set a property for") + ) + parser.add_argument( + "--name", + action='store_true', + help=_("Unset snapshot name."), + ) + parser.add_argument( + "--description", + action='store_true', + help=_("Unset snapshot description."), + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + share_snapshot = utils.find_resource( + share_client.share_snapshots, + parsed_args.snapshot) + + kwargs = {} + if parsed_args.name: + kwargs['display_name'] = None + if parsed_args.description: + kwargs['display_description'] = None + if kwargs: + try: + share_client.share_snapshots.update( + share_snapshot, + **kwargs + ) + except Exception as e: + raise exceptions.CommandError(_( + "Failed to unset snapshot display name " + "or display description : %s" % e)) + + +class ListShareSnapshot(command.Lister): + """List snapshots.""" + _description = _("List snapshots") + + def get_parser(self, prog_name): + parser = super(ListShareSnapshot, self).get_parser(prog_name) + parser.add_argument( + "--all-projects", + action='store_true', + default=False, + help=_("Display snapshots from all projects (Admin only).") + ) + parser.add_argument( + "--name", + metavar="", + default=None, + help=_("Filter results by name.") + ) + parser.add_argument( + '--description', + metavar="", + default=None, + help=_("Filter results by description. Available only for " + "microversion >= 2.36.") + ) + parser.add_argument( + '--status', + metavar="", + default=None, + help=_('Filter results by status') + ) + parser.add_argument( + '--share', + metavar='', + default=None, + help=_('Name or ID of a share to filter results by.') + ) + parser.add_argument( + '--usage', + metavar='', + default=None, + choices=['used', 'unused'], + help=_("Option to filter snapshots by usage.") + ) + parser.add_argument( + "--limit", + metavar="", + type=int, + default=None, + action=parseractions.NonNegativeAction, + help=_("Limit the number of snapshots returned") + ) + parser.add_argument( + "--marker", + metavar="", + help=_("The last share ID of the previous page") + ) + parser.add_argument( + '--sort', + metavar="[:]", + default='name:asc', + help=_("Sort output by selected keys and directions(asc or desc) " + "(default: name:asc), multiple keys and directions can be " + "specified separated by comma") + ) + parser.add_argument( + "--name~", + metavar="", + default=None, + help=_("Filter results matching a share snapshot name pattern. " + "Available only for microversion >= 2.36.") + ) + parser.add_argument( + '--description~', + metavar="", + default=None, + help=_("Filter results matching a share snapshot description " + "pattern. Available only for microversion >= 2.36.") + ) + parser.add_argument( + '--detail', + action='store_true', + default=False, + help=_("List share snapshots with details") + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + share_id = None + if parsed_args.share: + share_id = utils.find_resource(share_client.shares, + parsed_args.share).id + columns = ['ID', 'Name'] + + search_opts = { + 'offset': parsed_args.marker, + 'limit': parsed_args.limit, + 'all_tenants': parsed_args.all_projects, + 'name': parsed_args.name, + 'status': parsed_args.status, + 'share_id': share_id, + 'usage': parsed_args.usage, + } + + if share_client.api_version >= api_versions.APIVersion("2.36"): + search_opts['name~'] = getattr(parsed_args, 'name~') + search_opts['description~'] = getattr(parsed_args, 'description~') + search_opts['description'] = parsed_args.description + elif (parsed_args.description or getattr(parsed_args, 'name~') or + getattr(parsed_args, 'description~')): + raise exceptions.CommandError( + "Pattern based filtering (name~, description~ and description)" + " is only available with manila API version >= 2.36") + + if parsed_args.detail: + columns.extend([ + 'Status', + 'Description', + 'Created At', + 'Size', + 'Share ID', + 'Share Proto', + 'Share Size', + 'User ID' + ]) + + if parsed_args.all_projects: + columns.append('Project ID') + snapshots = share_client.share_snapshots.list(search_opts=search_opts) + + snapshots = utils.sort_items(snapshots, parsed_args.sort, str) + + return (columns, + (utils.get_item_properties(s, columns) for s in snapshots)) diff --git a/manilaclient/tests/unit/osc/v2/fakes.py b/manilaclient/tests/unit/osc/v2/fakes.py index fb72ee6f2..e2c6f16e9 100644 --- a/manilaclient/tests/unit/osc/v2/fakes.py +++ b/manilaclient/tests/unit/osc/v2/fakes.py @@ -34,6 +34,8 @@ class FakeShareClient(object): self.share_types = mock.Mock() self.share_type_access = mock.Mock() self.quotas = mock.Mock() + self.share_snapshots = mock.Mock() + self.share_snapshot_export_locations = mock.Mock() self.shares.resource_class = osc_fakes.FakeResource(None, {}) self.share_export_locations = mock.Mock() self.share_export_locations.resource_class = ( @@ -375,3 +377,61 @@ class FakeQuotaSet(object): quotas_info), loaded=True) return quotas + + +class FakeShareSnapshot(object): + """Fake a share snapshot""" + + @staticmethod + def create_one_snapshot(attrs=None, methods=None): + """Create a fake share snapshot + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with project_id, resource and so on + """ + + attrs = attrs or {} + methods = methods or {} + + share_snapshot = { + 'created_at': datetime.datetime.now().isoformat(), + 'description': 'description-' + uuid.uuid4().hex, + 'id': 'snapshot-id-' + uuid.uuid4().hex, + 'name': 'name-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'provider_location': None, + 'share_id': 'share-id-' + uuid.uuid4().hex, + 'share_proto': 'NFS', + 'share_size': 1, + 'size': 1, + 'status': None, + 'user_id': 'user-id-' + uuid.uuid4().hex + } + + share_snapshot.update(attrs) + share_snapshot = osc_fakes.FakeResource(info=copy.deepcopy( + share_snapshot), + methods=methods, + loaded=True) + return share_snapshot + + @staticmethod + def create_share_snapshots(attrs=None, count=2): + """Create multiple fake snapshots. + + :param Dictionary attrs: + A dictionary with all attributes + :param Integer count: + The number of share types to be faked + :return: + A list of FakeResource objects + """ + + share_snapshots = [] + for n in range(0, count): + share_snapshots.append( + FakeShareSnapshot.create_one_snapshot(attrs)) + + return share_snapshots diff --git a/manilaclient/tests/unit/osc/v2/test_share_snapshots.py b/manilaclient/tests/unit/osc/v2/test_share_snapshots.py new file mode 100644 index 000000000..051070dde --- /dev/null +++ b/manilaclient/tests/unit/osc/v2/test_share_snapshots.py @@ -0,0 +1,632 @@ +# Copyright 2019 Red Hat Inc. All rights reserved. +# +# 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. +# + + +from unittest import mock +import uuid + +from osc_lib import exceptions +from osc_lib import utils as oscutils + +from manilaclient import api_versions +from manilaclient.common import cliutils +from manilaclient.osc.v2 import share_snapshots as osc_share_snapshots +from manilaclient.tests.unit.osc import osc_utils +from manilaclient.tests.unit.osc.v2 import fakes as manila_fakes + +COLUMNS = ['ID', 'Name'] + +COLUMNS_DETAIL = [ + 'ID', + 'Name', + 'Status', + 'Description', + 'Created At', + 'Size', + 'Share ID', + 'Share Proto', + 'Share Size', + 'User ID' +] + + +class TestShareSnapshot(manila_fakes.TestShare): + + def setUp(self): + super(TestShareSnapshot, self).setUp() + + self.shares_mock = self.app.client_manager.share.shares + self.shares_mock.reset_mock() + + self.snapshots_mock = self.app.client_manager.share.share_snapshots + self.snapshots_mock.reset_mock() + self.app.client_manager.share.api_version = api_versions.APIVersion( + "2.51") + + +class TestShareSnapshotCreate(TestShareSnapshot): + + def setUp(self): + super(TestShareSnapshotCreate, self).setUp() + + self.share = manila_fakes.FakeShare.create_one_share() + self.shares_mock.create.return_value = self.share + + self.shares_mock.get.return_value = self.share + + self.share_snapshot = ( + manila_fakes.FakeShareSnapshot.create_one_snapshot()) + self.snapshots_mock.create.return_value = self.share_snapshot + + self.cmd = osc_share_snapshots.CreateShareSnapshot(self.app, None) + + self.data = tuple(self.share_snapshot._info.values()) + self.columns = tuple(self.share_snapshot._info.keys()) + + def test_share_snapshot_create_missing_args(self): + arglist = [] + verifylist = [] + + self.assertRaises(osc_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_share_snapshot_create_required_args(self): + arglist = [ + self.share.id + ] + verifylist = [ + ('share', self.share.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( + share=self.share, + force=False, + name=None, + description=None + ) + + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_share_snapshot_create_force(self): + arglist = [ + self.share.id, + '--force' + ] + verifylist = [ + ('share', self.share.id), + ('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( + share=self.share, + force=True, + name=None, + description=None + ) + + self.assertCountEqual(columns, columns) + self.assertCountEqual(self.data, data) + + def test_share_snapshot_create(self): + arglist = [ + self.share.id, + '--name', self.share_snapshot.name, + '--description', self.share_snapshot.description + ] + verifylist = [ + ('share', self.share.id), + ('name', self.share_snapshot.name), + ('description', self.share_snapshot.description) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.create.assert_called_with( + share=self.share, + force=False, + name=self.share_snapshot.name, + description=self.share_snapshot.description + ) + + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + +class TestShareSnapshotDelete(TestShareSnapshot): + + def setUp(self): + super(TestShareSnapshotDelete, self).setUp() + + self.share_snapshot = ( + manila_fakes.FakeShareSnapshot.create_one_snapshot()) + + self.snapshots_mock.get.return_value = self.share_snapshot + + self.cmd = osc_share_snapshots.DeleteShareSnapshot(self.app, None) + + def test_share_snapshot_delete_missing_args(self): + arglist = [] + verifylist = [] + + self.assertRaises(osc_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_share_snapshot_delete(self): + arglist = [ + self.share_snapshot.id + ] + verifylist = [ + ('snapshot', [self.share_snapshot.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.share_snapshot) + self.assertIsNone(result) + + def test_share_snapshot_delete_force(self): + arglist = [ + self.share_snapshot.id, + '--force' + ] + verifylist = [ + ('snapshot', [self.share_snapshot.id]), + ('force', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.force_delete.assert_called_with( + self.share_snapshot) + self.assertIsNone(result) + + def test_share_snapshot_delete_multiple(self): + share_snapshots = ( + manila_fakes.FakeShareSnapshot.create_share_snapshots( + count=2)) + arglist = [ + share_snapshots[0].id, + share_snapshots[1].id + ] + verifylist = [ + ('snapshot', [share_snapshots[0].id, share_snapshots[1].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertEqual(self.snapshots_mock.delete.call_count, + len(share_snapshots)) + self.assertIsNone(result) + + def test_share_snapshot_delete_exception(self): + arglist = [ + self.share_snapshot.id + ] + verifylist = [ + ('snapshot', [self.share_snapshot.id]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.snapshots_mock.delete.side_effect = exceptions.CommandError() + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + +class TestShareSnapshotShow(TestShareSnapshot): + + def setUp(self): + super(TestShareSnapshotShow, self).setUp() + + self.export_location = ( + manila_fakes.FakeShareExportLocation.create_one_export_location()) + + self.share_snapshot = ( + manila_fakes.FakeShareSnapshot.create_one_snapshot( + attrs={ + 'export_locations': self.export_location + } + )) + self.snapshots_mock.get.return_value = self.share_snapshot + + self.cmd = osc_share_snapshots.ShowShareSnapshot(self.app, None) + + self.data = self.share_snapshot._info.values() + self.columns = self.share_snapshot._info.keys() + + def test_share_snapshot_show_missing_args(self): + arglist = [] + verifylist = [] + + self.assertRaises(osc_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_share_snapshot_show(self): + arglist = [ + self.share_snapshot.id + ] + verifylist = [ + ('snapshot', self.share_snapshot.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + cliutils.transform_export_locations_to_string_view = mock.Mock() + cliutils.transform_export_locations_to_string_view.return_value = ( + self.export_location) + + columns, data = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_with(self.share_snapshot.id) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + +class TestShareSnapshotSet(TestShareSnapshot): + + def setUp(self): + super(TestShareSnapshotSet, self).setUp() + + self.share_snapshot = ( + manila_fakes.FakeShareSnapshot.create_one_snapshot()) + + self.snapshots_mock.get.return_value = self.share_snapshot + + self.cmd = osc_share_snapshots.SetShareSnapshot(self.app, None) + + def test_set_snapshot_name(self): + snapshot_name = 'snapshot-name-' + uuid.uuid4().hex + arglist = [ + self.share_snapshot.id, + '--name', snapshot_name + ] + verifylist = [ + ('snapshot', self.share_snapshot.id), + ('name', snapshot_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.update.assert_called_with( + self.share_snapshot, + display_name=parsed_args.name) + self.assertIsNone(result) + + def test_set_snapshot_description(self): + description = 'snapshot-description-' + uuid.uuid4().hex + arglist = [ + self.share_snapshot.id, + '--description', description + ] + verifylist = [ + ('snapshot', self.share_snapshot.id), + ('description', description) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.update.assert_called_with( + self.share_snapshot, + display_description=parsed_args.description) + self.assertIsNone(result) + + def test_set_snapshot_status(self): + arglist = [ + self.share_snapshot.id, + '--status', 'available' + ] + verifylist = [ + ('snapshot', self.share_snapshot.id), + ('status', 'available') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.reset_state.assert_called_with( + self.share_snapshot, + parsed_args.status) + self.assertIsNone(result) + + def test_set_snapshot_update_exception(self): + snapshot_name = 'snapshot-name-' + uuid.uuid4().hex + arglist = [ + self.share_snapshot.id, + '--name', snapshot_name + ] + verifylist = [ + ('snapshot', self.share_snapshot.id), + ('name', snapshot_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.snapshots_mock.update.side_effect = Exception() + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_set_snapshot_status_exception(self): + arglist = [ + self.share_snapshot.id, + '--status', 'available' + ] + verifylist = [ + ('snapshot', self.share_snapshot.id), + ('status', 'available') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.snapshots_mock.reset_state.side_effect = Exception() + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + +class TestShareSnapshotUnset(TestShareSnapshot): + + def setUp(self): + super(TestShareSnapshotUnset, self).setUp() + + self.share_snapshot = ( + manila_fakes.FakeShareSnapshot.create_one_snapshot()) + + self.snapshots_mock.get.return_value = self.share_snapshot + + self.cmd = osc_share_snapshots.UnsetShareSnapshot(self.app, None) + + def test_unset_snapshot_name(self): + arglist = [ + self.share_snapshot.id, + '--name' + ] + verifylist = [ + ('snapshot', self.share_snapshot.id), + ('name', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.update.assert_called_with( + self.share_snapshot, + display_name=None) + self.assertIsNone(result) + + def test_unset_snapshot_description(self): + arglist = [ + self.share_snapshot.id, + '--description' + ] + verifylist = [ + ('snapshot', self.share_snapshot.id), + ('description', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.update.assert_called_with( + self.share_snapshot, + display_description=None) + self.assertIsNone(result) + + def test_unset_snapshot_name_exception(self): + arglist = [ + self.share_snapshot.id, + '--name' + ] + verifylist = [ + ('snapshot', self.share_snapshot.id), + ('name', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.snapshots_mock.update.side_effect = Exception() + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + +class TestShareSnapshotList(TestShareSnapshot): + + def setUp(self): + super(TestShareSnapshotList, self).setUp() + + self.share_snapshots = ( + manila_fakes.FakeShareSnapshot.create_share_snapshots( + count=2)) + self.snapshots_list = oscutils.sort_items( + self.share_snapshots, + 'name:asc', + str) + + self.snapshots_mock.list.return_value = self.snapshots_list + + self.values = (oscutils.get_dict_properties( + s._info, COLUMNS) for s in self.snapshots_list) + + self.cmd = osc_share_snapshots.ListShareSnapshot(self.app, None) + + def test_list_snapshots(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_with( + search_opts={ + 'offset': None, + 'limit': None, + 'all_tenants': False, + 'name': None, + 'status': None, + 'share_id': None, + 'usage': None, + 'name~': None, + 'description~': None, + 'description': None + }) + + self.assertEqual(COLUMNS, columns) + self.assertEqual(list(self.values), list(data)) + + def test_list_snapshots_all_projects(self): + all_tenants_list = COLUMNS.copy() + all_tenants_list.append('Project ID') + list_values = (oscutils.get_dict_properties( + s._info, all_tenants_list) for s in self.snapshots_list) + + arglist = [ + '--all-projects' + ] + + verifylist = [ + ('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_with( + search_opts={ + 'offset': None, + 'limit': None, + 'all_tenants': True, + 'name': None, + 'status': None, + 'share_id': None, + 'usage': None, + 'name~': None, + 'description~': None, + 'description': None + }) + + self.assertEqual(all_tenants_list, columns) + self.assertEqual(list(list_values), list(data)) + + def test_list_snapshots_detail(self): + values = (oscutils.get_dict_properties( + s._info, COLUMNS_DETAIL) for s in self.snapshots_list) + + arglist = [ + '--detail' + ] + + verifylist = [ + ('detail', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_with( + search_opts={ + 'offset': None, + 'limit': None, + 'all_tenants': False, + 'name': None, + 'status': None, + 'share_id': None, + 'usage': None, + 'name~': None, + 'description~': None, + 'description': None + }) + + self.assertEqual(COLUMNS_DETAIL, columns) + self.assertEqual(list(values), list(data)) + + def test_list_snapshots_api_version_exception(self): + self.app.client_manager.share.api_version = api_versions.APIVersion( + "2.35") + + arglist = [ + '--description', 'Description' + ] + verifylist = [ + ('description', 'Description') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_list_snapshots_share_id(self): + self.share = manila_fakes.FakeShare.create_one_share( + attrs={'id': self.snapshots_list[0].id}) + + self.shares_mock.get.return_value = self.share + self.snapshots_mock.list.return_value = [self.snapshots_list[0]] + + values = (oscutils.get_dict_properties( + s._info, COLUMNS) for s in [self.snapshots_list[0]]) + + arglist = [ + '--share', self.share.id + ] + verifylist = [ + ('share', self.share.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_with( + search_opts={ + 'offset': None, + 'limit': None, + 'all_tenants': False, + 'name': None, + 'status': None, + 'share_id': self.share.id, + 'usage': None, + 'name~': None, + 'description~': None, + 'description': None + }) + + self.assertEqual(COLUMNS, columns) + self.assertEqual(list(values), list(data)) diff --git a/setup.cfg b/setup.cfg index 288944b6e..77ea44baf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,6 +60,12 @@ openstack.share.v2 = share_quota_set = manilaclient.osc.v2.quotas:QuotaSet share_quota_show = manilaclient.osc.v2.quotas:QuotaShow share_quota_delete = manilaclient.osc.v2.quotas:QuotaDelete + share_snapshot_create = manilaclient.osc.v2.share_snapshots:CreateShareSnapshot + share_snapshot_delete = manilaclient.osc.v2.share_snapshots:DeleteShareSnapshot + share_snapshot_show = manilaclient.osc.v2.share_snapshots:ShowShareSnapshot + share_snapshot_set = manilaclient.osc.v2.share_snapshots:SetShareSnapshot + share_snapshot_unset = manilaclient.osc.v2.share_snapshots:UnsetShareSnapshot + share_snapshot_list = manilaclient.osc.v2.share_snapshots:ListShareSnapshot [coverage:run] omit = manilaclient/tests/*