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/*