From fa8a4b03f806676968fccd467f21770cccb73968 Mon Sep 17 00:00:00 2001 From: Victoria Martinez de la Cruz Date: Wed, 18 Aug 2021 16:45:20 +0000 Subject: [PATCH] [OSC] Implement Share Group Type Commands In this patch we add the following openstack share commands: share group type create share group type delete share group type list share group type set share group type unset share group type show share group type access create share group type access delete share group type access list Partially-implements: bp openstack-client-support Co-Authored-By: Ashley Rodriguez Change-Id: I31b22013b60560c17bc95c41a51efc5e6c7a80ca --- doc/source/cli/osc/v2/index.rst | 9 +- manilaclient/base.py | 1 - manilaclient/common/constants.py | 8 +- manilaclient/osc/utils.py | 66 ++- .../osc/v2/share_group_type_access.py | 163 ++++++ manilaclient/osc/v2/share_group_types.py | 341 ++++++++++++ manilaclient/tests/unit/osc/v2/fakes.py | 80 ++- .../unit/osc/v2/test_share_group_type.py | 520 ++++++++++++++++++ .../osc/v2/test_share_group_type_access.py | 204 +++++++ manilaclient/v2/share_group_types.py | 5 +- setup.cfg | 9 + 11 files changed, 1385 insertions(+), 21 deletions(-) create mode 100644 manilaclient/osc/v2/share_group_type_access.py create mode 100644 manilaclient/osc/v2/share_group_types.py create mode 100644 manilaclient/tests/unit/osc/v2/test_share_group_type.py create mode 100644 manilaclient/tests/unit/osc/v2/test_share_group_type_access.py diff --git a/doc/source/cli/osc/v2/index.rst b/doc/source/cli/osc/v2/index.rst index db80e5654..c13ccfd37 100644 --- a/doc/source/cli/osc/v2/index.rst +++ b/doc/source/cli/osc/v2/index.rst @@ -163,4 +163,11 @@ share groups ============ .. autoprogram-cliff:: openstack.share.v2 - :command: share group * + :command: share group [!t]* + +================= +share group types +================= + +.. autoprogram-cliff:: openstack.share.v2 + :command: share group type * \ No newline at end of file diff --git a/manilaclient/base.py b/manilaclient/base.py index a8ca16056..32f436f35 100644 --- a/manilaclient/base.py +++ b/manilaclient/base.py @@ -70,7 +70,6 @@ class Manager(utils.HookableMixin): data = data['values'] except KeyError: pass - with self.completion_cache('human_id', obj_class, mode="w"): with self.completion_cache('uuid', obj_class, mode="w"): resource = [obj_class(self, res, loaded=True) diff --git a/manilaclient/common/constants.py b/manilaclient/common/constants.py index 2754c22f8..634b83721 100644 --- a/manilaclient/common/constants.py +++ b/manilaclient/common/constants.py @@ -103,12 +103,18 @@ SNAPSHOT_SUPPORT = 'snapshot_support' CREATE_SHARE_FROM_SNAPSHOT_SUPPORT = 'create_share_from_snapshot_support' REVERT_TO_SNAPSHOT_SUPPORT = 'revert_to_snapshot_support' MOUNT_SNAPSHOT_SUPPORT = 'mount_snapshot_support' +CONSISTENT_SNAPSHOT_SUPPORT = 'consistent_snapshot_support' BOOL_SPECS = ( SNAPSHOT_SUPPORT, CREATE_SHARE_FROM_SNAPSHOT_SUPPORT, REVERT_TO_SNAPSHOT_SUPPORT, - MOUNT_SNAPSHOT_SUPPORT + MOUNT_SNAPSHOT_SUPPORT, +) + +# share group types +GROUP_BOOL_SPECS = ( + CONSISTENT_SNAPSHOT_SUPPORT, ) REPLICA_GRADUATION_VERSION = '2.56' diff --git a/manilaclient/osc/utils.py b/manilaclient/osc/utils.py index b18768311..abf36c4ff 100644 --- a/manilaclient/osc/utils.py +++ b/manilaclient/osc/utils.py @@ -12,11 +12,16 @@ # License for the specific language governing permissions and limitations # under the License. +import logging + from oslo_utils import strutils +from manilaclient.common._i18n import _ from manilaclient.common import constants from manilaclient import exceptions +LOG = logging.getLogger(__name__) + def extract_key_value_options(pairs): result_dict = {} @@ -62,29 +67,58 @@ def extract_properties(properties): return result_dict -def extract_extra_specs(extra_specs, specs_to_add): - for item in specs_to_add: - (key, value) = item.split('=', 1) - if key in extra_specs: - msg = ("Argument '%s' value specified twice." % key) - raise exceptions.CommandError(msg) - elif key in constants.BOOL_SPECS: - if strutils.is_valid_boolstr(value): - extra_specs[key] = value.capitalize() - else: - msg = ( - "Argument '%s' is of boolean " - "type and has invalid value: %s" - % (key, str(value))) +def extract_extra_specs(extra_specs, specs_to_add, + bool_specs=constants.BOOL_SPECS): + try: + for item in specs_to_add: + (key, value) = item.split('=', 1) + if key in extra_specs: + msg = ("Argument '%s' value specified twice." % key) raise exceptions.CommandError(msg) - else: - extra_specs[key] = value + elif key in bool_specs: + if strutils.is_valid_boolstr(value): + extra_specs[key] = value.capitalize() + else: + msg = ( + "Argument '%s' is of boolean " + "type and has invalid value: %s" + % (key, str(value))) + raise exceptions.CommandError(msg) + else: + extra_specs[key] = value + except ValueError: + msg = LOG.error(_( + "Wrong format: specs should be key=value pairs.")) + raise exceptions.CommandError(msg) return extra_specs +def extract_group_specs(extra_specs, specs_to_add): + return extract_extra_specs(extra_specs, + specs_to_add, constants.GROUP_BOOL_SPECS) + + def format_column_headers(columns): column_headers = [] for column in columns: column_headers.append( column.replace('_', ' ').title().replace('Id', 'ID')) return column_headers + + +def format_share_group_type(share_group_type, formatter='table'): + printable_share_group_type = share_group_type._info + + is_public = printable_share_group_type.pop('is_public') + + printable_share_group_type['visibility'] = ( + 'public' if is_public else 'private') + + if formatter == 'table': + printable_share_group_type['group_specs'] = ( + format_properties(share_group_type.group_specs)) + printable_share_group_type['share_types'] = ( + "\n".join(printable_share_group_type['share_types']) + ) + + return printable_share_group_type diff --git a/manilaclient/osc/v2/share_group_type_access.py b/manilaclient/osc/v2/share_group_type_access.py new file mode 100644 index 000000000..1f46139e0 --- /dev/null +++ b/manilaclient/osc/v2/share_group_type_access.py @@ -0,0 +1,163 @@ +# 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 openstackclient.identity import common as identity_common +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils as oscutils + +from manilaclient.common._i18n import _ +from manilaclient.common.apiclient import utils as apiutils + +LOG = logging.getLogger(__name__) + + +class ShareGroupTypeAccessAllow(command.Command): + """Allow a project to access a share group type.""" + _description = _("Allow a project to access a share group type " + "(Admin only).") + + def get_parser(self, prog_name): + parser = super(ShareGroupTypeAccessAllow, self).get_parser(prog_name) + parser.add_argument( + 'share_group_type', + metavar="", + help=_("Share group type name or ID to allow access to.") + ) + parser.add_argument( + 'projects', + metavar="", + nargs="+", + help=_("Project Name or ID to add share group type access for.") + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + identity_client = self.app.client_manager.identity + result = 0 + + share_group_type = apiutils.find_resource( + share_client.share_group_types, parsed_args.share_group_type) + + for project in parsed_args.projects: + try: + project_obj = identity_common.find_project( + identity_client, project, parsed_args.project_domain) + + share_client.share_group_type_access.add_project_access( + share_group_type, project_obj.id) + except Exception as e: + result += 1 + LOG.error(_( + "Failed to allow access for project '%(project)s' " + "to share group type with name or ID " + "'%(share_group_type)s': %(e)s"), + {'project': project, + 'share_group_type': share_group_type, 'e': e}) + + if result > 0: + total = len(parsed_args.projects) + msg = (_("Failed to allow access to " + "%(result)s of %(total)s projects") % {'result': result, + 'total': total}) + raise exceptions.CommandError(msg) + + +class ListShareGroupTypeAccess(command.Lister): + """Get access list for share group type.""" + _description = _("Get access list for share group type (Admin only).") + + def get_parser(self, prog_name): + parser = super(ListShareGroupTypeAccess, self).get_parser(prog_name) + parser.add_argument( + 'share_group_type', + metavar="", + help=_("Filter results by share group type name or ID.") + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + share_group_type = apiutils.find_resource( + share_client.share_group_types, parsed_args.share_group_type) + + if share_group_type._info.get('is_public'): + raise exceptions.CommandError( + 'Forbidden to get access list for public share group type.') + + data = share_client.share_group_type_access.list(share_group_type) + + columns = ['Project ID'] + values = (oscutils.get_item_properties(s, columns) for s in data) + + return (columns, values) + + +class ShareGroupTypeAccessDeny(command.Command): + """Deny a project to access a share group type.""" + _description = _("Deny a project to access a share group type " + "(Admin only).") + + def get_parser(self, prog_name): + parser = super(ShareGroupTypeAccessDeny, self).get_parser(prog_name) + parser.add_argument( + 'share_group_type', + metavar="", + help=_("Share group type name or ID to deny access from") + ) + parser.add_argument( + 'projects', + metavar="", + nargs="+", + help=_("Project Name(s) or ID(s) " + "to deny share group type access for.") + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + identity_client = self.app.client_manager.identity + result = 0 + + share_group_type = apiutils.find_resource( + share_client.share_group_types, parsed_args.share_group_type) + + for project in parsed_args.projects: + try: + project_obj = identity_common.find_project( + identity_client, + project, + parsed_args.project_domain) + + share_client.share_group_type_access.remove_project_access( + share_group_type, project_obj.id) + except Exception as e: + result += 1 + LOG.error(_( + "Failed to deny access for project '%(project)s' " + "to share group type with name or ID " + "'%(share_group_type)s': %(e)s"), + {'project': project, + 'share_group_type': share_group_type, 'e': e}) + + if result > 0: + total = len(parsed_args.projects) + msg = (_("Failed to deny access to " + "%(result)s of %(total)s projects") % {'result': result, + 'total': total}) + raise exceptions.CommandError(msg) diff --git a/manilaclient/osc/v2/share_group_types.py b/manilaclient/osc/v2/share_group_types.py new file mode 100644 index 000000000..00e5ad451 --- /dev/null +++ b/manilaclient/osc/v2/share_group_types.py @@ -0,0 +1,341 @@ +# 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.command import command +from osc_lib import exceptions +from osc_lib import utils as oscutils +from oslo_utils import strutils + +from manilaclient.common._i18n import _ +from manilaclient.common.apiclient import utils as apiutils +from manilaclient.osc import utils + +LOG = logging.getLogger(__name__) + +ATTRIBUTES = [ + 'id', + 'name', + 'share_types', + 'visibility', + 'is_default', + 'group_specs' +] + + +class CreateShareGroupType(command.ShowOne): + """Create new share group type.""" + _description = _( + "Create new share group type") + + log = logging.getLogger(__name__ + ".CreateShareGroupType") + + def get_parser(self, prog_name): + parser = super(CreateShareGroupType, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar="", + default=None, + help=_('Share group type name') + ) + parser.add_argument( + "share_types", + metavar="", + nargs="+", + default=None, + help=_("List of share type names or IDs. Example:" + " my-share-type-1 my-share-type-2"), + ) + parser.add_argument( + "--group-specs", + type=str, + nargs='*', + metavar='', + default=None, + help=_("Share Group type extra specs by key and value." + " OPTIONAL: Default=None. Example:" + " --group-specs consistent_snapshot_support=host."), + ) + parser.add_argument( + '--public', + metavar="", + default=True, + help=_('Make type accessible to the public (default true).') + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + kwargs = { + 'name': parsed_args.name + } + + share_types_list = [] + for share_type in parsed_args.share_types: + try: + share_type_obj = apiutils.find_resource( + share_client.share_types, + share_type) + + share_types_list.append(share_type_obj.name) + except Exception as e: + msg = LOG.error(_("Failed to find the share type with " + "name or ID '%(share_type)s': %(e)s"), + {'share_type': share_type, 'e': e}) + raise exceptions.CommandError(msg) + + kwargs['share_types'] = share_types_list + + if parsed_args.public: + kwargs['is_public'] = strutils.bool_from_string( + parsed_args.public, default=True) + + group_specs = {} + if parsed_args.group_specs: + for item in parsed_args.group_specs: + group_specs = utils.extract_group_specs(group_specs, [item]) + + kwargs['group_specs'] = group_specs + + share_group_type = share_client.share_group_types.create(**kwargs) + + formatter = parsed_args.formatter + + formatted_group_type = utils.format_share_group_type( + share_group_type, formatter) + + return (ATTRIBUTES, oscutils.get_dict_properties( + formatted_group_type, ATTRIBUTES)) + + +class DeleteShareGroupType(command.Command): + """Delete a share group type.""" + _description = _("Delete a share group type") + + log = logging.getLogger(__name__ + ".DeleteShareGroupType") + + def get_parser(self, prog_name): + parser = super(DeleteShareGroupType, self).get_parser(prog_name) + parser.add_argument( + 'share_group_types', + metavar="", + nargs="+", + help=_("Name or ID of the share group type(s) to delete") + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + result = 0 + + for share_group_type in parsed_args.share_group_types: + try: + share_group_type_obj = apiutils.find_resource( + share_client.share_group_types, + share_group_type) + + share_client.share_group_types.delete(share_group_type_obj) + except Exception as e: + result += 1 + LOG.error(_( + "Failed to delete share group type with " + "name or ID '%(share_group_type)s': %(e)s"), + {'share_group_type': share_group_type, 'e': e}) + + if result > 0: + total = len(parsed_args.share_group_types) + msg = (_("%(result)s of %(total)s share group types failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListShareGroupType(command.Lister): + """List Share Group Types.""" + _description = _("List share types") + + log = logging.getLogger(__name__ + ".ListShareGroupType") + + def get_parser(self, prog_name): + parser = super(ListShareGroupType, self).get_parser(prog_name) + parser.add_argument( + '--all', + action='store_true', + default=False, + help=_('Display all share group types whether public or private. ' + 'Default=False. (Admin only)'), + ) + parser.add_argument( + '--group-specs', + type=str, + nargs='*', + metavar='', + default=None, + help=_('Filter share group types with group specs (key=value).'), + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + search_opts = {} + if parsed_args.group_specs: + search_opts = { + 'group_specs': utils.extract_group_specs( + extra_specs={}, + specs_to_add=parsed_args.group_specs) + } + + formatter = parsed_args.formatter + + share_group_types = share_client.share_group_types.list( + search_opts=search_opts, + show_all=parsed_args.all) + + formatted_types = [] + for share_group_type in share_group_types: + formatted_types.append(utils.format_share_group_type( + share_group_type, formatter)) + + values = (oscutils.get_dict_properties( + sgt, ATTRIBUTES) for sgt in formatted_types) + + return (ATTRIBUTES, values) + + +class ShowShareGroupType(command.ShowOne): + """Show Share Group Types.""" + _description = _("Show share group types") + + log = logging.getLogger(__name__ + ".ShowShareGroupType") + + def get_parser(self, prog_name): + parser = super(ShowShareGroupType, self).get_parser(prog_name) + parser.add_argument( + 'share_group_type', + metavar="", + help=_("Name or ID of the share group type to show") + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + share_group_type = apiutils.find_resource( + share_client.share_group_types, parsed_args.share_group_type) + + share_group_type_obj = share_client.share_group_types.get( + share_group_type) + + formatter = parsed_args.formatter + + formatted_group_type = utils.format_share_group_type( + share_group_type_obj, formatter) + + return (ATTRIBUTES, oscutils.get_dict_properties( + formatted_group_type, ATTRIBUTES)) + + +class SetShareGroupType(command.Command): + """Set share type properties.""" + _description = _("Set share group type properties") + + log = logging.getLogger(__name__ + ".SetShareGroupType") + + def get_parser(self, prog_name): + parser = super(SetShareGroupType, self).get_parser(prog_name) + parser.add_argument( + 'share_group_type', + metavar="", + help=_("Name or ID of the share group type to modify") + ) + parser.add_argument( + "--group-specs", + type=str, + nargs='*', + metavar='', + default=None, + help=_("Extra specs key and value of share group type that will be" + " used for share type creation. OPTIONAL: Default=None." + " Example: --group-specs consistent-snapshot-support=True"), + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + try: + share_group_type_obj = apiutils.find_resource( + share_client.share_group_types, parsed_args.share_group_type) + except Exception as e: + msg = LOG.error(_( + "Failed to find the share group type with " + "name or ID '%(share_group_type)s': %(e)s"), + {'share_group_type': parsed_args.share_group_type, 'e': e}) + raise exceptions.CommandError(msg) + kwargs = {} + + if kwargs: + share_group_type_obj.set_keys(**kwargs) + + if parsed_args.group_specs: + group_specs = utils.extract_group_specs( + extra_specs={}, + specs_to_add=parsed_args.group_specs) + try: + share_group_type_obj.set_keys(group_specs) + except Exception as e: + raise exceptions.CommandError( + "Failed to set share group type key: %s" % e) + + +class UnsetShareGroupType(command.Command): + """Unset share group type extra specs.""" + _description = _("Unset share group type extra specs") + + log = logging.getLogger(__name__ + ".UnsetShareGroupType") + + def get_parser(self, prog_name): + parser = super(UnsetShareGroupType, self).get_parser(prog_name) + parser.add_argument( + 'share_group_type', + metavar="", + help=_("Name or ID of the share grouptype to modify") + ) + parser.add_argument( + 'group_specs', + metavar='', + nargs='+', + help=_('Remove group specs from this share group type'), + ) + return parser + + def take_action(self, parsed_args): + share_client = self.app.client_manager.share + + try: + share_group_type_obj = apiutils.find_resource( + share_client.share_group_types, parsed_args.share_group_type) + except Exception as e: + msg = LOG.error(_( + "Failed to find the share group type with " + "name or ID '%(share_group_type)s': %(e)s"), + {'share_group_type': parsed_args.share_group_type, 'e': e}) + raise exceptions.CommandError(msg) + + if parsed_args.group_specs: + try: + share_group_type_obj.unset_keys(parsed_args.group_specs) + except Exception as e: + raise exceptions.CommandError( + "Failed to remove share type group extra spec: %s" % e) diff --git a/manilaclient/tests/unit/osc/v2/fakes.py b/manilaclient/tests/unit/osc/v2/fakes.py index 7a845c700..be443a6a1 100644 --- a/manilaclient/tests/unit/osc/v2/fakes.py +++ b/manilaclient/tests/unit/osc/v2/fakes.py @@ -55,6 +55,8 @@ class FakeShareClient(object): self.share_instances = mock.Mock() self.pools = mock.Mock() self.limits = mock.Mock() + self.share_group_types = mock.Mock() + self.share_group_type_access = mock.Mock() class ManilaParseException(Exception): @@ -826,7 +828,7 @@ class FakeShareAvailabilityZones(object): :param Dictionary attrs: A dictionary with all attributes :param Integer count: - The number of share types to be faked + The number of availability zones to be faked :return: A list of FakeResource objects """ @@ -1084,6 +1086,7 @@ class FakeShareNetwork(object): A dictionary with all attributes :param Integer count: The number of share networks to be faked + :return: A list of FakeResource objects """ @@ -1211,3 +1214,78 @@ class FakeShareGroup(object): share_groups.append( FakeShareGroup.create_one_share_group(attrs)) return share_groups + + +class FakeShareGroupType(object): + """Fake one or more share group types""" + + @staticmethod + def create_one_share_group_type(attrs=None, methods=None): + """Create a fake share group type + + :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_group_type_info = { + "is_public": True, + "group_specs": { + "snapshot_support": True + }, + "share_types": ['share-types-id-' + uuid.uuid4().hex], + "id": 'share-group-type-id-' + uuid.uuid4().hex, + "name": 'share-group-type-name-' + uuid.uuid4().hex, + "is_default": False + } + + share_group_type_info.update(attrs) + share_group_type = osc_fakes.FakeResource(info=copy.deepcopy( + share_group_type_info), + methods=methods, + loaded=True) + return share_group_type + + @staticmethod + def create_share_group_types(attrs=None, count=2): + """Create multiple fake share group types. + + :param Dictionary attrs: + A dictionary with all attributes + :param Integer count: + The number of share group types to be faked + :return: + A list of FakeResource objects + """ + + share_group_types = [] + for n in range(0, count): + share_group_types.append( + FakeShareGroupType.create_one_share_group_type(attrs)) + + return share_group_types + + @staticmethod + def get_share_group_types(share_group_types=None, count=2): + """Get an iterable MagicMock object with a list of faked group 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 group types to be faked + :return + An iterable Mock object with side_effect set to a list of faked + group types + """ + + if share_group_types is None: + share_group_types = FakeShareGroupType.share_group_types(count) + + return mock.Mock(side_effect=share_group_types) diff --git a/manilaclient/tests/unit/osc/v2/test_share_group_type.py b/manilaclient/tests/unit/osc/v2/test_share_group_type.py new file mode 100644 index 000000000..b9355f6c6 --- /dev/null +++ b/manilaclient/tests/unit/osc/v2/test_share_group_type.py @@ -0,0 +1,520 @@ +# 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 + +from osc_lib import exceptions +from osc_lib import utils as oscutils + +from manilaclient import api_versions +from manilaclient.common.apiclient.exceptions import BadRequest +from manilaclient.common.apiclient.exceptions import NotFound +from manilaclient.osc import utils +from manilaclient.osc.v2 import share_group_types as osc_share_group_types +from manilaclient.tests.unit.osc import osc_utils +from manilaclient.tests.unit.osc.v2 import fakes as manila_fakes + +COLUMNS = [ + 'id', + 'name', + 'share_types', + 'visibility', + 'is_default', + 'group_specs', +] + + +class TestShareGroupType(manila_fakes.TestShare): + + def setUp(self): + super(TestShareGroupType, self).setUp() + + self.sgt_mock = self.app.client_manager.share.share_group_types + self.sgt_mock.reset_mock() + self.app.client_manager.share.api_version = api_versions.APIVersion( + api_versions.MAX_VERSION) + + +class TestShareGroupTypeCreate(TestShareGroupType): + + def setUp(self): + super(TestShareGroupTypeCreate, self).setUp() + + self.share_types = ( + manila_fakes.FakeShareType.create_share_types(count=2)) + + formatted_share_types = [] + + for st in self.share_types: + formatted_share_types.append(st.name) + + self.share_group_type = ( + manila_fakes.FakeShareGroupType.create_one_share_group_type( + attrs={ + 'share_types': formatted_share_types + } + )) + + self.share_group_type_formatted = ( + manila_fakes.FakeShareGroupType.create_one_share_group_type( + attrs={ + 'id': self.share_group_type['id'], + 'name': self.share_group_type['name'], + 'share_types': formatted_share_types + } + )) + + formatted_sgt = utils.format_share_group_type( + self.share_group_type_formatted) + + self.sgt_mock.create.return_value = self.share_group_type + self.sgt_mock.get.return_value = self.share_group_type + + # Get the command object to test + self.cmd = osc_share_group_types.CreateShareGroupType(self.app, None) + + self.data = tuple(formatted_sgt.values()) + self.columns = tuple(formatted_sgt.keys()) + + def test_share_group_type_create_required_args(self): + """Verifies required arguments.""" + + arglist = [ + self.share_group_type.name, + self.share_types[0].name, + self.share_types[1].name, + ] + verifylist = [ + ('name', self.share_group_type.name), + ('share_types', [self.share_types[0].name, + self.share_types[1].name]) + ] + + with mock.patch( + 'manilaclient.common.apiclient.utils.find_resource', + side_effect=[self.share_types[0], self.share_types[1]]): + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.sgt_mock.create.assert_called_with( + group_specs={}, + is_public=True, + name=self.share_group_type.name, + share_types=[ + self.share_types[0].name, self.share_types[1].name] + ) + + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_share_group_type_create_missing_required_arg(self): + """Verifies missing required arguments.""" + + arglist = [ + self.share_group_type.name, + ] + verifylist = [ + ('name', self.share_group_type.name) + ] + + self.assertRaises(osc_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_share_group_type_create_private(self): + arglist = [ + self.share_group_type.name, + self.share_types[0].name, + self.share_types[1].name, + '--public', 'False' + ] + verifylist = [ + ('name', self.share_group_type.name), + ('share_types', [self.share_types[0].name, + self.share_types[1].name]), + ('public', 'False') + ] + + with mock.patch( + 'manilaclient.common.apiclient.utils.find_resource', + side_effect=[self.share_types[0], + self.share_types[1]]): + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.sgt_mock.create.assert_called_with( + group_specs={}, + is_public=False, + name=self.share_group_type.name, + share_types=[self.share_types[0].name, + self.share_types[1].name] + ) + + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_share_group_type_create_group_specs(self): + + arglist = [ + self.share_group_type.name, + self.share_types[0].name, + self.share_types[1].name, + '--group-specs', 'consistent_snapshot_support=true' + ] + verifylist = [ + ('name', self.share_group_type.name), + ('share_types', [self.share_types[0].name, + self.share_types[1].name]), + ('group_specs', ['consistent_snapshot_support=true']) + ] + + with mock.patch( + 'manilaclient.common.apiclient.utils.find_resource', + side_effect=[self.share_types[0], + self.share_types[1]]): + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.sgt_mock.create.assert_called_with( + group_specs={'consistent_snapshot_support': 'True'}, + is_public=True, + name=self.share_group_type.name, + share_types=[ + self.share_types[0].name, self.share_types[1].name] + ) + + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_create_share_group_type(self): + arglist = [ + self.share_group_type.name, + self.share_types[0].name, + self.share_types[1].name + ] + verifylist = [ + ('name', self.share_group_type.name), + ('share_types', [self.share_types[0].name, + self.share_types[1].name]) + ] + + with mock.patch( + 'manilaclient.common.apiclient.utils.find_resource', + side_effect=[self.share_types[0], + self.share_types[1], + self.share_group_type]): + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.sgt_mock.create.assert_called_with( + group_specs={}, + is_public=True, + name=self.share_group_type.name, + share_types=[self.share_types[0].name, + self.share_types[1].name] + ) + + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + +class TestShareGroupTypeDelete(TestShareGroupType): + + def setUp(self): + super(TestShareGroupTypeDelete, self).setUp() + + self.share_group_types = ( + manila_fakes.FakeShareGroupType.create_share_group_types(count=2)) + + self.sgt_mock.delete.return_value = None + self.sgt_mock.get = ( + manila_fakes.FakeShareGroupType.get_share_group_types( + self.share_group_types)) + + # Get the command object to test + self.cmd = osc_share_group_types.DeleteShareGroupType(self.app, None) + + def test_share_group_type_delete_one(self): + arglist = [ + self.share_group_types[0].name + ] + + verifylist = [ + ('share_group_types', [self.share_group_types[0].name]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.sgt_mock.delete.assert_called_with( + self.share_group_types[0]) + self.assertIsNone(result) + + def test_share_group_type_delete_multiple(self): + arglist = [] + for t in self.share_group_types: + arglist.append(t.name) + verifylist = [ + ('share_group_types', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for t in self.share_group_types: + calls.append(mock.call(t)) + self.sgt_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_share_group_type_with_exception(self): + arglist = [ + 'non_existing_type', + ] + verifylist = [ + ('share_group_types', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.sgt_mock.delete.side_effect = exceptions.CommandError() + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + + def test_delete_share_group_type(self): + arglist = [ + self.share_group_types[0].name + ] + + verifylist = [ + ('share_group_types', [self.share_group_types[0].name]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.sgt_mock.delete.assert_called_with(self.share_group_types[0]) + + self.assertIsNone(result) + + +class TestShareGroupTypeSet(TestShareGroupType): + + def setUp(self): + super(TestShareGroupTypeSet, self).setUp() + + self.share_group_type = ( + manila_fakes.FakeShareGroupType.create_one_share_group_type( + methods={'set_keys': None, 'update': None})) + self.sgt_mock.get.return_value = self.share_group_type + + # Get the command object to test + self.cmd = osc_share_group_types.SetShareGroupType(self.app, None) + + def test_share_group_type_set_group_specs(self): + arglist = [ + self.share_group_type.id, + '--group-specs', 'consistent_snapshot_support=true' + ] + verifylist = [ + ('share_group_type', self.share_group_type.id), + ('group_specs', ['consistent_snapshot_support=true']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.share_group_type.set_keys.assert_called_with( + {'consistent_snapshot_support': 'True'}) + self.assertIsNone(result) + + def test_share_group_type_set_extra_specs_exception(self): + arglist = [ + self.share_group_type.id, + '--group-specs', 'snapshot_support=true' + ] + verifylist = [ + ('share_group_type', self.share_group_type.id), + ('group_specs', ['snapshot_support=true']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.share_group_type.set_keys.side_effect = BadRequest() + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + + +class TestShareGroupTypeUnset(TestShareGroupType): + + def setUp(self): + super(TestShareGroupTypeUnset, self).setUp() + + self.share_group_type = ( + manila_fakes.FakeShareGroupType.create_one_share_group_type( + methods={'unset_keys': None})) + self.sgt_mock.get.return_value = self.share_group_type + + # Get the command object to test + self.cmd = osc_share_group_types.UnsetShareGroupType(self.app, None) + + def test_share_group_type_unset_extra_specs(self): + arglist = [ + self.share_group_type.id, + 'consistent_snapshot_support' + ] + verifylist = [ + ('share_group_type', self.share_group_type.id), + ('group_specs', ['consistent_snapshot_support']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.share_group_type.unset_keys.assert_called_with( + ['consistent_snapshot_support']) + self.assertIsNone(result) + + def test_share_group_type_unset_exception(self): + arglist = [ + self.share_group_type.id, + 'snapshot_support' + ] + verifylist = [ + ('share_group_type', self.share_group_type.id), + ('group_specs', ['snapshot_support']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.share_group_type.unset_keys.side_effect = NotFound() + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + + +class TestShareGroupTypeList(TestShareGroupType): + + def setUp(self): + super(TestShareGroupTypeList, self).setUp() + + self.share_group_types = ( + manila_fakes.FakeShareGroupType.create_share_group_types()) + + self.sgt_mock.list.return_value = self.share_group_types + + # Get the command object to test + self.cmd = osc_share_group_types.ListShareGroupType(self.app, None) + + self.values = (oscutils.get_dict_properties( + s._info, COLUMNS) for s in self.share_group_types) + + def test_share_group_type_list_no_options(self): + arglist = [] + verifylist = [ + ('all', False) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.sgt_mock.list.assert_called_once_with( + search_opts={}, + show_all=False + ) + self.assertEqual(COLUMNS, columns) + self.assertEqual(list(self.values), list(data)) + + def test_share_group_type_list_all(self): + arglist = [ + '--all', + ] + verifylist = [ + ('all', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.sgt_mock.list.assert_called_once_with( + search_opts={}, + show_all=True) + self.assertEqual(COLUMNS, columns) + self.assertEqual(list(self.values), list(data)) + + def test_share_group_type_list_group_specs(self): + arglist = [ + '--group-specs', 'consistent_snapshot_support=true' + ] + verifylist = [ + ('group_specs', ['consistent_snapshot_support=true']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.sgt_mock.list.assert_called_once_with( + search_opts={'group_specs': { + 'consistent_snapshot_support': 'True'}}, + show_all=False) + self.assertEqual(COLUMNS, columns) + self.assertEqual(list(self.values), list(data)) + + +class TestShareGroupTypeShow(TestShareGroupType): + + def setUp(self): + super(TestShareGroupTypeShow, self).setUp() + + self.share_types = ( + manila_fakes.FakeShareType.create_share_types(count=2)) + + formatted_share_types = [] + + for st in self.share_types: + formatted_share_types.append(st.name) + + self.share_group_type = ( + manila_fakes.FakeShareGroupType.create_one_share_group_type( + attrs={ + 'share_types': formatted_share_types + } + )) + + self.share_group_type_formatted = ( + manila_fakes.FakeShareGroupType.create_one_share_group_type( + attrs={ + 'id': self.share_group_type['id'], + 'name': self.share_group_type['name'], + 'share_types': formatted_share_types + } + )) + + formatted_sgt = utils.format_share_group_type( + self.share_group_type_formatted) + + self.sgt_mock.get.return_value = self.share_group_type + + # Get the command object to test + self.cmd = osc_share_group_types.ShowShareGroupType(self.app, None) + + self.data = tuple(formatted_sgt.values()) + self.columns = tuple(formatted_sgt.keys()) + + def test_share_group_type_show(self): + arglist = [ + self.share_group_type.name + ] + verifylist = [ + ("share_group_type", self.share_group_type.name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.sgt_mock.get.assert_called_with(self.share_group_type) + + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) diff --git a/manilaclient/tests/unit/osc/v2/test_share_group_type_access.py b/manilaclient/tests/unit/osc/v2/test_share_group_type_access.py new file mode 100644 index 000000000..fd2000de2 --- /dev/null +++ b/manilaclient/tests/unit/osc/v2/test_share_group_type_access.py @@ -0,0 +1,204 @@ +# 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 osc_lib import exceptions + +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + +from manilaclient.common.apiclient.exceptions import BadRequest +from manilaclient.osc.v2 import share_group_type_access as osc_sgta +from manilaclient.tests.unit.osc.v2 import fakes as manila_fakes + + +class TestShareGroupTypeAccess(manila_fakes.TestShare): + + def setUp(self): + super(TestShareGroupTypeAccess, self).setUp() + + self.type_access_mock = ( + self.app.client_manager.share.share_group_type_access) + self.type_access_mock.reset_mock() + + self.share_group_types_mock = ( + self.app.client_manager.share.share_group_types) + self.share_group_types_mock.reset_mock() + + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + +class TestShareGroupTypeAccessAllow(TestShareGroupTypeAccess): + + def setUp(self): + super(TestShareGroupTypeAccessAllow, self).setUp() + + self.project = identity_fakes.FakeProject.create_one_project() + + self.share_group_type = ( + manila_fakes.FakeShareGroupType.create_one_share_group_type( + attrs={'is_public': False} + ) + ) + self.share_group_types_mock.get.return_value = self.share_group_type + self.projects_mock.get.return_value = self.project + + self.type_access_mock.add_project_access.return_value = None + + # Get the command object to test + self.cmd = osc_sgta.ShareGroupTypeAccessAllow(self.app, None) + + def test_share_group_type_access_create(self): + arglist = [ + self.share_group_type.id, + self.project.id + ] + verifylist = [ + ('share_group_type', self.share_group_type.id), + ('projects', [self.project.id]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.type_access_mock.add_project_access.assert_called_with( + self.share_group_type, self.project.id) + + self.assertIsNone(result) + + def test_share_group_type_access_create_invalid_project_exception(self): + arglist = [ + self.share_group_type.id, + 'invalid_project_format' + ] + verifylist = [ + ('share_group_type', self.share_group_type.id), + ('projects', ['invalid_project_format']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.type_access_mock.add_project_access.side_effect = BadRequest() + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + + +class TestShareGroupTypeAccessList(TestShareGroupTypeAccess): + + columns = ['Project ID'] + data = (('',), ('',)) + + def setUp(self): + super(TestShareGroupTypeAccessList, self).setUp() + + self.type_access_mock.list.return_value = ( + self.columns, self.data) + + # Get the command object to test + self.cmd = osc_sgta.ListShareGroupTypeAccess(self.app, None) + + def test_share_group_type_access_list(self): + share_group_type = ( + manila_fakes.FakeShareGroupType.create_one_share_group_type( + attrs={'is_public': False} + ) + ) + self.share_group_types_mock.get.return_value = share_group_type + + arglist = [ + share_group_type.id, + ] + verifylist = [ + ('share_group_type', share_group_type.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.type_access_mock.list.assert_called_once_with( + share_group_type) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) + + def test_share_group_type_access_list_public_type(self): + share_group_type = ( + manila_fakes.FakeShareGroupType.create_one_share_group_type( + attrs={'is_public': True} + ) + ) + + self.share_group_types_mock.get.return_value = share_group_type + + arglist = [ + share_group_type.id, + ] + verifylist = [ + ('share_group_type', share_group_type.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + + +class TestShareGroupTypeAccessDeny(TestShareGroupTypeAccess): + + def setUp(self): + super(TestShareGroupTypeAccessDeny, self).setUp() + + self.project = identity_fakes.FakeProject.create_one_project() + + self.share_group_type = ( + manila_fakes.FakeShareGroupType.create_one_share_group_type( + attrs={'is_public': False})) + self.share_group_types_mock.get.return_value = self.share_group_type + self.projects_mock.get.return_value = self.project + + self.type_access_mock.remove_project_access.return_value = None + + # Get the command object to test + self.cmd = osc_sgta.ShareGroupTypeAccessDeny(self.app, None) + + def test_share_group_type_access_delete(self): + arglist = [ + self.share_group_type.id, + self.project.id + ] + verifylist = [ + ('share_group_type', self.share_group_type.id), + ('projects', [self.project.id]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.type_access_mock.remove_project_access.assert_called_with( + self.share_group_type, self.project.id) + + self.assertIsNone(result) + + def test_share_group_type_access_delete_exception(self): + arglist = [ + self.share_group_type.id, + 'invalid_project_format' + ] + verifylist = [ + ('share_group_type', self.share_group_type.id), + ('projects', ['invalid_project_format']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.type_access_mock.remove_project_access.side_effect = BadRequest() + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) diff --git a/manilaclient/v2/share_group_types.py b/manilaclient/v2/share_group_types.py index 3d26da224..98d982783 100644 --- a/manilaclient/v2/share_group_types.py +++ b/manilaclient/v2/share_group_types.py @@ -152,7 +152,10 @@ class ShareGroupTypeManager(base.ManagerWithFind): :rtype: list of :class:`ShareGroupType`. """ - query_string = '?is_public=all' if show_all else '' + search_opts = search_opts or {} + if show_all: + search_opts['is_public'] = 'all' + query_string = self._build_query_string(search_opts) url = RESOURCES_PATH + query_string return self._list(url, RESOURCES_NAME) diff --git a/setup.cfg b/setup.cfg index 1c9a1f3af..25985207c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -122,6 +122,15 @@ openstack.share.v2 = share_group_show = manilaclient.osc.v2.share_groups:ShowShareGroup share_group_set = manilaclient.osc.v2.share_groups:SetShareGroup share_group_unset = manilaclient.osc.v2.share_groups:UnsetShareGroup + share_group_type_create = manilaclient.osc.v2.share_group_types:CreateShareGroupType + share_group_type_delete = manilaclient.osc.v2.share_group_types:DeleteShareGroupType + share_group_type_list = manilaclient.osc.v2.share_group_types:ListShareGroupType + share_group_type_show = manilaclient.osc.v2.share_group_types:ShowShareGroupType + share_group_type_set = manilaclient.osc.v2.share_group_types:SetShareGroupType + share_group_type_unset = manilaclient.osc.v2.share_group_types:UnsetShareGroupType + share_group_type_access_create = manilaclient.osc.v2.share_group_type_access:ShareGroupTypeAccessAllow + share_group_type_access_list = manilaclient.osc.v2.share_group_type_access:ListShareGroupTypeAccess + share_group_type_access_delete = manilaclient.osc.v2.share_group_type_access:ShareGroupTypeAccessDeny [coverage:run] omit = manilaclient/tests/*