From 0cb695fd54f90a94fe185ff7e34ba0b175b6c75b Mon Sep 17 00:00:00 2001 From: Valeriy Ponomaryov Date: Tue, 27 Oct 2015 17:23:50 +0200 Subject: [PATCH] Port share type extensions to core API Changes: - Register share type APIs as core API. - Remove extensions code for share types. - Leave rename of API 'post' data for future update which will be done with bump of microversion after port of all extensions to core API. Partially implements bp ext-to-core Change-Id: I334cc0fa8e69c2f9b5226f247149a0b9b748dd60 --- etc/manila/policy.json | 15 +- manila/api/contrib/share_type_access.py | 163 ------- manila/api/contrib/types_manage.py | 132 ----- manila/api/v1/router.py | 11 +- manila/api/v1/share_types.py | 175 ++++++- .../share_types_extra_specs.py} | 37 +- manila/api/views/types.py | 12 +- .../api/contrib/test_share_type_access.py | 317 ------------ manila/tests/api/contrib/test_types_manage.py | 153 ------ manila/tests/api/v1/test_share_types.py | 457 +++++++++++++++++- .../test_share_types_extra_specs.py} | 5 +- manila/tests/policy.json | 16 +- 12 files changed, 657 insertions(+), 836 deletions(-) delete mode 100644 manila/api/contrib/share_type_access.py delete mode 100644 manila/api/contrib/types_manage.py rename manila/api/{contrib/types_extra_specs.py => v1/share_types_extra_specs.py} (88%) delete mode 100644 manila/tests/api/contrib/test_share_type_access.py delete mode 100644 manila/tests/api/contrib/test_types_manage.py rename manila/tests/api/{contrib/test_types_extra_specs.py => v1/test_share_types_extra_specs.py} (98%) diff --git a/etc/manila/policy.json b/etc/manila/policy.json index 80ff6bc484..16973a21bd 100644 --- a/etc/manila/policy.json +++ b/etc/manila/policy.json @@ -54,12 +54,17 @@ "share_type:index": "rule:default", "share_type:show": "rule:default", "share_type:default": "rule:default", + "share_type:create": "rule:admin_api", + "share_type:delete": "rule:admin_api", + "share_type:add_project_access": "rule:admin_api", + "share_type:list_project_access": "rule:admin_api", + "share_type:remove_project_access": "rule:admin_api", - "share_extension:types_manage": "rule:admin_api", - "share_extension:types_extra_specs": "rule:admin_api", - "share_extension:share_type_access": "", - "share_extension:share_type_access:addProjectAccess": "rule:admin_api", - "share_extension:share_type_access:removeProjectAccess": "rule:admin_api", + "share_types_extra_spec:create": "rule:admin_api", + "share_types_extra_spec:update": "rule:admin_api", + "share_types_extra_spec:show": "rule:admin_api", + "share_types_extra_spec:index": "rule:admin_api", + "share_types_extra_spec:delete": "rule:admin_api", "security_service:create": "rule:default", "security_service:delete": "rule:default", diff --git a/manila/api/contrib/share_type_access.py b/manila/api/contrib/share_type_access.py deleted file mode 100644 index 2e54b98e1b..0000000000 --- a/manila/api/contrib/share_type_access.py +++ /dev/null @@ -1,163 +0,0 @@ -# -# 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. - -"""The share type access extension.""" - -from oslo_utils import uuidutils -import six -import webob - - -from manila.api import extensions -from manila.api.openstack import wsgi -from manila import exception -from manila.i18n import _ -from manila.share import share_types - - -soft_authorize = extensions.soft_extension_authorizer('share', - 'share_type_access') -authorize = extensions.extension_authorizer('share', 'share_type_access') - - -def _marshall_share_type_access(share_type): - rval = [] - for project_id in share_type['projects']: - rval.append({'share_type_id': share_type['id'], - 'project_id': project_id}) - - return {'share_type_access': rval} - - -class ShareTypeAccessController(object): - """The share type access API controller for the OpenStack API.""" - - def index(self, req, type_id): - context = req.environ['manila.context'] - authorize(context) - - try: - share_type = share_types.get_share_type( - context, type_id, expected_fields=['projects']) - except exception.ShareTypeNotFound: - explanation = _("Share type %s not found.") % type_id - raise webob.exc.HTTPNotFound(explanation=explanation) - - if share_type['is_public']: - expl = _("Access list not available for public share types.") - raise webob.exc.HTTPNotFound(explanation=expl) - - return _marshall_share_type_access(share_type) - - -class ShareTypeActionController(wsgi.Controller): - """The share type access API controller for the OpenStack API.""" - - def _check_body(self, body, action_name): - if not self.is_valid_body(body, action_name): - raise webob.exc.HTTPBadRequest() - access = body[action_name] - project = access.get('project') - if not uuidutils.is_uuid_like(project): - msg = _("Bad project format: " - "project is not in proper format (%s)") % project - raise webob.exc.HTTPBadRequest(explanation=msg) - - def _extend_share_type(self, share_type_rval, share_type_ref): - if share_type_ref: - key = "%s:is_public" % (Share_type_access.alias) - share_type_rval[key] = share_type_ref.get('is_public', True) - - @wsgi.extends - def show(self, req, resp_obj, id): - context = req.environ['manila.context'] - if soft_authorize(context): - share_type = req.get_db_share_type(id) - self._extend_share_type(resp_obj.obj['share_type'], share_type) - - @wsgi.extends - def index(self, req, resp_obj): - context = req.environ['manila.context'] - if soft_authorize(context): - for share_type_rval in list(resp_obj.obj['share_types']): - type_id = share_type_rval['id'] - share_type = req.get_db_share_type(type_id) - self._extend_share_type(share_type_rval, share_type) - - @wsgi.extends(action='create') - def create(self, req, body, resp_obj): - context = req.environ['manila.context'] - if soft_authorize(context): - type_id = resp_obj.obj['share_type']['id'] - share_type = req.get_db_share_type(type_id) - self._extend_share_type(resp_obj.obj['share_type'], share_type) - - @wsgi.action('addProjectAccess') - def _addProjectAccess(self, req, id, body): - context = req.environ['manila.context'] - authorize(context, action="addProjectAccess") - self._check_body(body, 'addProjectAccess') - project = body['addProjectAccess']['project'] - - try: - share_type = share_types.get_share_type(context, id) - - if share_type['is_public']: - msg = _("You cannot add project to public share_type.") - raise webob.exc.HTTPForbidden(explanation=msg) - - except exception.ShareTypeNotFound as err: - raise webob.exc.HTTPNotFound(explanation=six.text_type(err)) - - try: - share_types.add_share_type_access(context, id, project) - except exception.ShareTypeAccessExists as err: - raise webob.exc.HTTPConflict(explanation=six.text_type(err)) - - return webob.Response(status_int=202) - - @wsgi.action('removeProjectAccess') - def _removeProjectAccess(self, req, id, body): - context = req.environ['manila.context'] - authorize(context, action="removeProjectAccess") - self._check_body(body, 'removeProjectAccess') - project = body['removeProjectAccess']['project'] - - try: - share_types.remove_share_type_access(context, id, project) - except (exception.ShareTypeNotFound, - exception.ShareTypeAccessNotFound) as err: - raise webob.exc.HTTPNotFound(explanation=six.text_type(err)) - return webob.Response(status_int=202) - - -class Share_type_access(extensions.ExtensionDescriptor): - """share type access support.""" - - name = "ShareTypeAccess" - alias = "os-share-type-access" - updated = "2015-03-02T00:00:00Z" - - def get_resources(self): - resources = [] - res = extensions.ResourceExtension( - Share_type_access.alias, - ShareTypeAccessController(), - parent=dict(member_name='type', collection_name='types')) - resources.append(res) - return resources - - def get_controller_extensions(self): - controller = ShareTypeActionController() - extension = extensions.ControllerExtension(self, 'types', controller) - return [extension] diff --git a/manila/api/contrib/types_manage.py b/manila/api/contrib/types_manage.py deleted file mode 100644 index 7d2054f09c..0000000000 --- a/manila/api/contrib/types_manage.py +++ /dev/null @@ -1,132 +0,0 @@ -# Copyright (c) 2011 OpenStack Foundation -# -# 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. - -"""The share types manage extension.""" - -import six -import webob - -from manila.api import extensions -from manila.api.openstack import wsgi -from manila.api.views import types as views_types -from manila import exception -from manila.i18n import _ -from manila import rpc -from manila.share import share_types - - -authorize = extensions.extension_authorizer('share', 'types_manage') - - -class ShareTypesManageController(wsgi.Controller): - """The share types API controller for the OpenStack API.""" - - _view_builder_class = views_types.ViewBuilder - - def _notify_share_type_error(self, context, method, payload): - rpc.get_notifier('shareType').error(context, method, payload) - - @wsgi.action("create") - def _create(self, req, body): - """Creates a new share type.""" - context = req.environ['manila.context'] - authorize(context) - - if not self.is_valid_body(body, 'share_type') and \ - not self.is_valid_body(body, 'volume_type'): - raise webob.exc.HTTPBadRequest() - - elif self.is_valid_body(body, 'share_type'): - share_type = body['share_type'] - else: - share_type = body['volume_type'] - name = share_type.get('name', None) - specs = share_type.get('extra_specs', {}) - is_public = share_type.get('os-share-type-access:is_public', True) - - if name is None or name == "" or len(name) > 255: - msg = _("Type name is not valid.") - raise webob.exc.HTTPBadRequest(explanation=msg) - - try: - required_extra_specs = ( - share_types.get_valid_required_extra_specs(specs) - ) - except exception.InvalidExtraSpec as e: - raise webob.exc.HTTPBadRequest(explanation=six.text_type(e)) - - try: - share_types.create(context, name, specs, is_public) - share_type = share_types.get_share_type_by_name(context, name) - share_type['required_extra_specs'] = required_extra_specs - req.cache_db_share_type(share_type) - notifier_info = dict(share_types=share_type) - rpc.get_notifier('shareType').info( - context, 'share_type.create', notifier_info) - - except exception.ShareTypeExists as err: - notifier_err = dict(share_types=share_type, - error_message=six.text_type(err)) - self._notify_share_type_error(context, 'share_type.create', - notifier_err) - - raise webob.exc.HTTPConflict(explanation=six.text_type(err)) - except exception.NotFound as err: - notifier_err = dict(share_types=share_type, - error_message=six.text_type(err)) - self._notify_share_type_error(context, 'share_type.create', - notifier_err) - raise webob.exc.HTTPNotFound() - - return self._view_builder.show(req, share_type) - - @wsgi.action("delete") - def _delete(self, req, id): - """Deletes an existing share type.""" - context = req.environ['manila.context'] - authorize(context) - - try: - share_type = share_types.get_share_type(context, id) - share_types.destroy(context, share_type['id']) - notifier_info = dict(share_types=share_type) - rpc.get_notifier('shareType').info( - context, 'share_type.delete', notifier_info) - except exception.ShareTypeInUse as err: - notifier_err = dict(id=id, error_message=six.text_type(err)) - self._notify_share_type_error(context, 'share_type.delete', - notifier_err) - msg = 'Target share type is still in use.' - raise webob.exc.HTTPBadRequest(explanation=msg) - except exception.NotFound as err: - notifier_err = dict(id=id, error_message=six.text_type(err)) - self._notify_share_type_error(context, 'share_type.delete', - notifier_err) - - raise webob.exc.HTTPNotFound() - - return webob.Response(status_int=202) - - -class Types_manage(extensions.ExtensionDescriptor): - """Types manage support.""" - - name = "TypesManage" - alias = "os-types-manage" - updated = "2011-08-24T00:00:00+00:00" - - def get_controller_extensions(self): - controller = ShareTypesManageController() - extension = extensions.ControllerExtension(self, 'types', controller) - return [extension] diff --git a/manila/api/v1/router.py b/manila/api/v1/router.py index 265131102a..2be4b75a94 100644 --- a/manila/api/v1/router.py +++ b/manila/api/v1/router.py @@ -39,6 +39,7 @@ from manila.api.v1 import share_networks from manila.api.v1 import share_servers from manila.api.v1 import share_snapshots from manila.api.v1 import share_types +from manila.api.v1 import share_types_extra_specs from manila.api.v1 import share_unmanage from manila.api.v1 import shares from manila.api import versions @@ -176,7 +177,15 @@ class APIRouter(manila.api.openstack.APIRouter): mapper.resource("type", "types", controller=self.resources['types'], collection={'detail': 'GET', 'default': 'GET'}, - member={'action': 'POST'}) + member={'action': 'POST', + 'os-share-type-access': 'GET'}) + + self.resources['extra_specs'] = ( + share_types_extra_specs.create_resource()) + mapper.resource('extra_spec', 'extra_specs', + controller=self.resources['extra_specs'], + parent_resource=dict(member_name='type', + collection_name='types')) self.resources['scheduler_stats'] = scheduler_stats.create_resource() mapper.connect('pools', '/{project_id}/scheduler-stats/pools', diff --git a/manila/api/v1/share_types.py b/manila/api/v1/share_types.py index 7f719572d2..013a278a65 100644 --- a/manila/api/v1/share_types.py +++ b/manila/api/v1/share_types.py @@ -1,3 +1,4 @@ +# Copyright (c) 2011 OpenStack Foundation # Copyright (c) 2014 NetApp, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -12,10 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. -"""The share type & share types extra specs extension.""" +"""The share type API controller module..""" from oslo_utils import strutils +from oslo_utils import uuidutils import six +import webob from webob import exc from manila.api.openstack import wsgi @@ -23,20 +26,38 @@ from manila.api.views import types as views_types from manila import exception from manila.i18n import _ from manila import policy +from manila import rpc from manila.share import share_types -RESOURCE_NAME = 'share_type' - class ShareTypesController(wsgi.Controller): """The share types API controller for the OpenStack API.""" + resource_name = 'share_type' _view_builder_class = views_types.ViewBuilder + def __getattr__(self, key): + if key == 'os-share-type-access': + return self._list_project_access + return super(self.__class__, self).__getattr__(key) + + def _notify_share_type_error(self, context, method, payload): + rpc.get_notifier('shareType').error(context, method, payload) + + def _check_body(self, body, action_name): + if not self.is_valid_body(body, action_name): + raise webob.exc.HTTPBadRequest() + access = body[action_name] + project = access.get('project') + if not uuidutils.is_uuid_like(project): + msg = _("Bad project format: " + "project is not in proper format (%s)") % project + raise webob.exc.HTTPBadRequest(explanation=msg) + def index(self, req): """Returns the list of share types.""" context = req.environ['manila.context'] - policy.check_policy(context, RESOURCE_NAME, 'index') + policy.check_policy(context, self.resource_name, 'index') limited_types = self._get_share_types(req) req.cache_db_share_types(limited_types) @@ -45,7 +66,7 @@ class ShareTypesController(wsgi.Controller): def show(self, req, id): """Return a single share type item.""" context = req.environ['manila.context'] - policy.check_policy(context, RESOURCE_NAME, 'show') + policy.check_policy(context, self.resource_name, 'show') try: share_type = share_types.get_share_type(context, id) @@ -60,7 +81,7 @@ class ShareTypesController(wsgi.Controller): def default(self, req): """Return default volume type.""" context = req.environ['manila.context'] - policy.check_policy(context, RESOURCE_NAME, 'default') + policy.check_policy(context, self.resource_name, 'default') try: share_type = share_types.get_default_share_type(context) @@ -109,6 +130,148 @@ class ShareTypesController(wsgi.Controller): msg = _('Invalid is_public filter [%s]') % is_public raise exc.HTTPBadRequest(explanation=msg) + @wsgi.action("create") + def _create(self, req, body): + """Creates a new share type.""" + context = req.environ['manila.context'] + self.authorize(context, 'create') + + if not self.is_valid_body(body, 'share_type') and \ + not self.is_valid_body(body, 'volume_type'): + raise webob.exc.HTTPBadRequest() + + elif self.is_valid_body(body, 'share_type'): + share_type = body['share_type'] + else: + share_type = body['volume_type'] + name = share_type.get('name', None) + specs = share_type.get('extra_specs', {}) + is_public = share_type.get('os-share-type-access:is_public', True) + + if name is None or name == "" or len(name) > 255: + msg = _("Type name is not valid.") + raise webob.exc.HTTPBadRequest(explanation=msg) + + try: + required_extra_specs = ( + share_types.get_valid_required_extra_specs(specs) + ) + except exception.InvalidExtraSpec as e: + raise webob.exc.HTTPBadRequest(explanation=six.text_type(e)) + + try: + share_types.create(context, name, specs, is_public) + share_type = share_types.get_share_type_by_name(context, name) + share_type['required_extra_specs'] = required_extra_specs + req.cache_db_share_type(share_type) + notifier_info = dict(share_types=share_type) + rpc.get_notifier('shareType').info( + context, 'share_type.create', notifier_info) + + except exception.ShareTypeExists as err: + notifier_err = dict(share_types=share_type, + error_message=six.text_type(err)) + self._notify_share_type_error(context, 'share_type.create', + notifier_err) + + raise webob.exc.HTTPConflict(explanation=six.text_type(err)) + except exception.NotFound as err: + notifier_err = dict(share_types=share_type, + error_message=six.text_type(err)) + self._notify_share_type_error(context, 'share_type.create', + notifier_err) + raise webob.exc.HTTPNotFound() + + return self._view_builder.show(req, share_type) + + @wsgi.action("delete") + def _delete(self, req, id): + """Deletes an existing share type.""" + context = req.environ['manila.context'] + self.authorize(context, 'delete') + + try: + share_type = share_types.get_share_type(context, id) + share_types.destroy(context, share_type['id']) + notifier_info = dict(share_types=share_type) + rpc.get_notifier('shareType').info( + context, 'share_type.delete', notifier_info) + except exception.ShareTypeInUse as err: + notifier_err = dict(id=id, error_message=six.text_type(err)) + self._notify_share_type_error(context, 'share_type.delete', + notifier_err) + msg = 'Target share type is still in use.' + raise webob.exc.HTTPBadRequest(explanation=msg) + except exception.NotFound as err: + notifier_err = dict(id=id, error_message=six.text_type(err)) + self._notify_share_type_error(context, 'share_type.delete', + notifier_err) + + raise webob.exc.HTTPNotFound() + + return webob.Response(status_int=202) + + def _list_project_access(self, req, id): + context = req.environ['manila.context'] + self.authorize(context, 'list_project_access') + + try: + share_type = share_types.get_share_type( + context, id, expected_fields=['projects']) + except exception.ShareTypeNotFound: + explanation = _("Share type %s not found.") % id + raise webob.exc.HTTPNotFound(explanation=explanation) + + if share_type['is_public']: + expl = _("Access list not available for public share types.") + raise webob.exc.HTTPNotFound(explanation=expl) + + # TODO(vponomaryov): move to views. + rval = [] + for project_id in share_type['projects']: + rval.append( + {'share_type_id': share_type['id'], 'project_id': project_id} + ) + return {'share_type_access': rval} + + @wsgi.action('addProjectAccess') + def _add_project_access(self, req, id, body): + context = req.environ['manila.context'] + self.authorize(context, 'add_project_access') + self._check_body(body, 'addProjectAccess') + project = body['addProjectAccess']['project'] + + try: + share_type = share_types.get_share_type(context, id) + + if share_type['is_public']: + msg = _("Project cannot be added to public share_type.") + raise webob.exc.HTTPForbidden(explanation=msg) + + except exception.ShareTypeNotFound as err: + raise webob.exc.HTTPNotFound(explanation=six.text_type(err)) + + try: + share_types.add_share_type_access(context, id, project) + except exception.ShareTypeAccessExists as err: + raise webob.exc.HTTPConflict(explanation=six.text_type(err)) + + return webob.Response(status_int=202) + + @wsgi.action('removeProjectAccess') + def _remove_project_access(self, req, id, body): + context = req.environ['manila.context'] + self.authorize(context, 'remove_project_access') + self._check_body(body, 'removeProjectAccess') + project = body['removeProjectAccess']['project'] + + try: + share_types.remove_share_type_access(context, id, project) + except (exception.ShareTypeNotFound, + exception.ShareTypeAccessNotFound) as err: + raise webob.exc.HTTPNotFound(explanation=six.text_type(err)) + return webob.Response(status_int=202) + def create_resource(): return wsgi.Resource(ShareTypesController()) diff --git a/manila/api/contrib/types_extra_specs.py b/manila/api/v1/share_types_extra_specs.py similarity index 88% rename from manila/api/contrib/types_extra_specs.py rename to manila/api/v1/share_types_extra_specs.py index 18a908f5c9..2b6911475d 100644 --- a/manila/api/contrib/types_extra_specs.py +++ b/manila/api/v1/share_types_extra_specs.py @@ -13,13 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -"""The share types extra specs extension""" - import six import webob from manila.api import common -from manila.api import extensions from manila.api.openstack import wsgi from manila import db from manila import exception @@ -27,12 +24,12 @@ from manila.i18n import _ from manila import rpc from manila.share import share_types -authorize = extensions.extension_authorizer('share', 'types_extra_specs') - class ShareTypeExtraSpecsController(wsgi.Controller): """The share type extra specs API controller for the OpenStack API.""" + resource_name = 'share_types_extra_spec' + def _get_extra_specs(self, context, type_id): extra_specs = db.share_type_extra_specs_get(context, type_id) specs_dict = {} @@ -77,13 +74,13 @@ class ShareTypeExtraSpecsController(wsgi.Controller): def index(self, req, type_id): """Returns the list of extra specs for a given share type.""" context = req.environ['manila.context'] - authorize(context) + self.authorize(context, 'index') self._check_type(context, type_id) return self._get_extra_specs(context, type_id) def create(self, req, type_id, body=None): context = req.environ['manila.context'] - authorize(context) + self.authorize(context, 'create') if not self.is_valid_body(body, 'extra_specs'): raise webob.exc.HTTPBadRequest() @@ -100,7 +97,7 @@ class ShareTypeExtraSpecsController(wsgi.Controller): def update(self, req, type_id, id, body=None): context = req.environ['manila.context'] - authorize(context) + self.authorize(context, 'update') if not body: expl = _('Request body empty') raise webob.exc.HTTPBadRequest(explanation=expl) @@ -121,7 +118,7 @@ class ShareTypeExtraSpecsController(wsgi.Controller): def show(self, req, type_id, id): """Return a single extra spec item.""" context = req.environ['manila.context'] - authorize(context) + self.authorize(context, 'show') self._check_type(context, type_id) specs = self._get_extra_specs(context, type_id) if id in specs['extra_specs']: @@ -133,7 +130,7 @@ class ShareTypeExtraSpecsController(wsgi.Controller): """Deletes an existing extra spec.""" context = req.environ['manila.context'] self._check_type(context, type_id) - authorize(context) + self.authorize(context, 'delete') if id in share_types.get_undeletable_extra_specs(): msg = _("Extra spec '%s' can't be deleted.") % id @@ -157,21 +154,5 @@ class ShareTypeExtraSpecsController(wsgi.Controller): raise webob.exc.HTTPBadRequest(explanation=expl) -class Types_extra_specs(extensions.ExtensionDescriptor): - """Type extra specs support.""" - - name = "TypesExtraSpecs" - alias = "os-types-extra-specs" - updated = "2011-08-24T00:00:00+00:00" - - def get_resources(self): - resources = [] - res = extensions.ResourceExtension( - 'extra_specs', - ShareTypeExtraSpecsController(), - parent=dict(member_name='type', - collection_name='types') - ) - resources.append(res) - - return resources +def create_resource(): + return wsgi.Resource(ShareTypeExtraSpecsController()) diff --git a/manila/api/views/types.py b/manila/api/views/types.py index acd6732995..752bfcf573 100644 --- a/manila/api/views/types.py +++ b/manila/api/views/types.py @@ -35,10 +35,14 @@ class ViewBuilder(common.ViewBuilder): required_extra_specs = self._filter_extra_specs( required_extra_specs, extra_spec_names) - trimmed = dict(id=share_type.get('id'), - name=share_type.get('name'), - extra_specs=extra_specs, - required_extra_specs=required_extra_specs) + trimmed = { + 'id': share_type.get('id'), + 'name': share_type.get('name'), + 'os-share-type-access:is_public': share_type.get( + 'is_public', True), + 'extra_specs': extra_specs, + 'required_extra_specs': required_extra_specs, + } if brief: return trimmed else: diff --git a/manila/tests/api/contrib/test_share_type_access.py b/manila/tests/api/contrib/test_share_type_access.py deleted file mode 100644 index e2881ce118..0000000000 --- a/manila/tests/api/contrib/test_share_type_access.py +++ /dev/null @@ -1,317 +0,0 @@ -# -# 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 datetime - -import mock -import six -import webob - -from manila.api.contrib import share_type_access as type_access -from manila.api.v1 import share_types -from manila import context -from manila import db -from manila import exception -from manila.share import share_types as share_types_api -from manila import test -from manila.tests.api import fakes - - -def generate_type(type_id, is_public): - return { - 'id': type_id, - 'name': u'test', - 'deleted': False, - 'created_at': datetime.datetime(2012, 1, 1, 1, 1, 1, 1), - 'updated_at': None, - 'deleted_at': None, - 'is_public': bool(is_public), - 'extra_specs': {} - } - - -SHARE_TYPES = { - '0': generate_type('0', True), - '1': generate_type('1', True), - '2': generate_type('2', False), - '3': generate_type('3', False)} - -PROJ1_UUID = '11111111-1111-1111-1111-111111111111' -PROJ2_UUID = '22222222-2222-2222-2222-222222222222' -PROJ3_UUID = '33333333-3333-3333-3333-333333333333' - -ACCESS_LIST = [{'share_type_id': '2', 'project_id': PROJ2_UUID}, - {'share_type_id': '2', 'project_id': PROJ3_UUID}, - {'share_type_id': '3', 'project_id': PROJ3_UUID}] - - -def fake_share_type_get(context, id, inactive=False, expected_fields=None): - vol = SHARE_TYPES[id] - if expected_fields and 'projects' in expected_fields: - vol['projects'] = [a['project_id'] - for a in ACCESS_LIST if a['share_type_id'] == id] - return vol - - -def _has_type_access(type_id, project_id): - for access in ACCESS_LIST: - if (access['share_type_id'] == type_id - and access['project_id'] == project_id): - return True - return False - - -def fake_share_type_get_all(context, inactive=False, filters=None): - if filters is None or filters.get('is_public', None) is None: - return SHARE_TYPES - res = {} - for k, v in six.iteritems(SHARE_TYPES): - if filters['is_public'] and _has_type_access(k, context.project_id): - res.update({k: v}) - continue - if v['is_public'] == filters['is_public']: - res.update({k: v}) - return res - - -class FakeResponse(object): - obj = {'share_type': {'id': '0'}, - 'share_types': [ - {'id': '0'}, - {'id': '2'}]} - - def attach(self, **kwargs): - pass - - -class FakeRequest(object): - environ = {"manila.context": context.get_admin_context()} - - def get_db_share_type(self, resource_id): - return SHARE_TYPES[resource_id] - - -class ShareTypeAccessTest(test.TestCase): - - def setUp(self): - super(ShareTypeAccessTest, self).setUp() - self.type_access_controller = type_access.ShareTypeAccessController() - self.type_action_controller = type_access.ShareTypeActionController() - self.type_controller = share_types.ShareTypesController() - self.req = FakeRequest() - self.context = self.req.environ['manila.context'] - self.mock_object(db, 'share_type_get', - fake_share_type_get) - self.mock_object(db, 'share_type_get_all', - fake_share_type_get_all) - - def assertShareTypeListEqual(self, expected, observed): - self.assertEqual(len(expected), len(observed)) - expected = sorted(expected, key=lambda item: item['id']) - observed = sorted(observed, key=lambda item: item['id']) - for d1, d2 in zip(expected, observed): - self.assertEqual(d1['id'], d2['id']) - - def test_list_type_access_public(self): - """Querying os-share-type-access on public type should return 404.""" - req = fakes.HTTPRequest.blank('/v1/fake/types/os-share-type-access', - use_admin_context=True) - self.assertRaises(webob.exc.HTTPNotFound, - self.type_access_controller.index, - req, '1') - - def test_list_type_access_private(self): - expected = {'share_type_access': [ - {'share_type_id': '2', 'project_id': PROJ2_UUID}, - {'share_type_id': '2', 'project_id': PROJ3_UUID}]} - result = self.type_access_controller.index(self.req, '2') - self.assertEqual(expected, result) - - def test_list_with_no_context(self): - req = fakes.HTTPRequest.blank('/v1/types/fake/types') - - def fake_authorize(context, target=None, action=None): - raise exception.PolicyNotAuthorized(action='index') - self.mock_object(type_access, 'authorize', fake_authorize) - - self.assertRaises(exception.PolicyNotAuthorized, - self.type_access_controller.index, - req, 'fake') - - def test_list_type_with_admin_default_proj1(self): - expected = {'share_types': [{'id': '0'}, {'id': '1'}]} - req = fakes.HTTPRequest.blank('/v1/fake/types', - use_admin_context=True) - req.environ['manila.context'].project_id = PROJ1_UUID - result = self.type_controller.index(req) - self.assertShareTypeListEqual(expected['share_types'], - result['share_types']) - - def test_list_type_with_admin_default_proj2(self): - expected = {'share_types': [{'id': '0'}, {'id': '1'}, {'id': '2'}]} - req = fakes.HTTPRequest.blank('/v2/fake/types', - use_admin_context=True) - req.environ['manila.context'].project_id = PROJ2_UUID - result = self.type_controller.index(req) - self.assertShareTypeListEqual(expected['share_types'], - result['share_types']) - - def test_list_type_with_admin_ispublic_true(self): - expected = {'share_types': [{'id': '0'}, {'id': '1'}]} - req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=true', - use_admin_context=True) - result = self.type_controller.index(req) - self.assertShareTypeListEqual(expected['share_types'], - result['share_types']) - - def test_list_type_with_admin_ispublic_false(self): - expected = {'share_types': [{'id': '2'}, {'id': '3'}]} - req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=false', - use_admin_context=True) - result = self.type_controller.index(req) - self.assertShareTypeListEqual(expected['share_types'], - result['share_types']) - - def test_list_type_with_admin_ispublic_false_proj2(self): - expected = {'share_types': [{'id': '2'}, {'id': '3'}]} - req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=false', - use_admin_context=True) - req.environ['manila.context'].project_id = PROJ2_UUID - result = self.type_controller.index(req) - self.assertShareTypeListEqual(expected['share_types'], - result['share_types']) - - def test_list_type_with_admin_ispublic_none(self): - expected = {'share_types': [{'id': '0'}, {'id': '1'}, {'id': '2'}, - {'id': '3'}]} - req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=all', - use_admin_context=True) - result = self.type_controller.index(req) - self.assertShareTypeListEqual(expected['share_types'], - result['share_types']) - - def test_list_type_with_no_admin_default(self): - expected = {'share_types': [{'id': '0'}, {'id': '1'}]} - req = fakes.HTTPRequest.blank('/v2/fake/types', - use_admin_context=False) - result = self.type_controller.index(req) - self.assertShareTypeListEqual(expected['share_types'], - result['share_types']) - - def test_list_type_with_no_admin_ispublic_true(self): - expected = {'share_types': [{'id': '0'}, {'id': '1'}]} - req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=true', - use_admin_context=False) - result = self.type_controller.index(req) - self.assertShareTypeListEqual(expected['share_types'], - result['share_types']) - - def test_list_type_with_no_admin_ispublic_false(self): - expected = {'share_types': [{'id': '0'}, {'id': '1'}]} - req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=false', - use_admin_context=False) - result = self.type_controller.index(req) - self.assertShareTypeListEqual(expected['share_types'], - result['share_types']) - - def test_list_type_with_no_admin_ispublic_none(self): - expected = {'share_types': [{'id': '0'}, {'id': '1'}]} - req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=all', - use_admin_context=False) - result = self.type_controller.index(req) - self.assertShareTypeListEqual(expected['share_types'], - result['share_types']) - - def test_show(self): - resp = FakeResponse() - self.type_action_controller.show(self.req, resp, '0') - self.assertEqual({'id': '0', 'os-share-type-access:is_public': True}, - resp.obj['share_type']) - self.type_action_controller.show(self.req, resp, '2') - self.assertEqual({'id': '0', 'os-share-type-access:is_public': False}, - resp.obj['share_type']) - - def test_create(self): - resp = FakeResponse() - self.type_action_controller.create(self.req, {}, resp) - self.assertEqual({'id': '0', 'os-share-type-access:is_public': True}, - resp.obj['share_type']) - - def test_add_project_access(self): - def stub_add_share_type_access(context, type_id, project_id): - self.assertEqual('3', type_id, "type_id") - self.assertEqual(PROJ2_UUID, project_id, "project_id") - self.mock_object(db, 'share_type_access_add', - stub_add_share_type_access) - body = {'addProjectAccess': {'project': PROJ2_UUID}} - req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', - use_admin_context=True) - result = self.type_action_controller._addProjectAccess(req, '3', body) - self.assertEqual(202, result.status_code) - - def test_add_project_access_with_no_admin_user(self): - req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', - use_admin_context=False) - body = {'addProjectAccess': {'project': PROJ2_UUID}} - self.assertRaises(exception.PolicyNotAuthorized, - self.type_action_controller._addProjectAccess, - req, '2', body) - - def test_add_project_access_with_already_added_access(self): - def stub_add_share_type_access(context, type_id, project_id): - raise exception.ShareTypeAccessExists(share_type_id=type_id, - project_id=project_id) - self.mock_object(db, 'share_type_access_add', - stub_add_share_type_access) - body = {'addProjectAccess': {'project': PROJ2_UUID}} - req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', - use_admin_context=True) - self.assertRaises(webob.exc.HTTPConflict, - self.type_action_controller._addProjectAccess, - req, '3', body) - - def test_add_project_access_to_public_share_type(self): - share_type_id = '3' - body = {'addProjectAccess': {'project': PROJ2_UUID}} - self.mock_object(share_types_api, 'get_share_type', - mock.Mock(return_value={"is_public": True})) - - req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', - use_admin_context=True) - - self.assertRaises(webob.exc.HTTPForbidden, - self.type_action_controller._addProjectAccess, - req, share_type_id, body) - share_types_api.get_share_type.assert_called_once_with( - mock.ANY, share_type_id) - - def test_remove_project_access_with_bad_access(self): - def stub_remove_share_type_access(context, type_id, project_id): - raise exception.ShareTypeAccessNotFound(share_type_id=type_id, - project_id=project_id) - self.mock_object(db, 'share_type_access_remove', - stub_remove_share_type_access) - body = {'removeProjectAccess': {'project': PROJ2_UUID}} - req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', - use_admin_context=True) - self.assertRaises(webob.exc.HTTPNotFound, - self.type_action_controller._removeProjectAccess, - req, '3', body) - - def test_remove_project_access_with_no_admin_user(self): - req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', - use_admin_context=False) - body = {'removeProjectAccess': {'project': PROJ2_UUID}} - self.assertRaises(exception.PolicyNotAuthorized, - self.type_action_controller._removeProjectAccess, - req, '2', body) diff --git a/manila/tests/api/contrib/test_types_manage.py b/manila/tests/api/contrib/test_types_manage.py deleted file mode 100644 index 988629ecc1..0000000000 --- a/manila/tests/api/contrib/test_types_manage.py +++ /dev/null @@ -1,153 +0,0 @@ -# Copyright 2011 OpenStack Foundation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import ddt -import webob - -from manila.api.contrib import types_manage -from manila.common import constants -from manila import exception -from manila.share import share_types -from manila import test -from manila.tests.api import fakes -from manila.tests import fake_notifier - - -def stub_share_type(id): - specs = {"key1": "value1", - "key2": "value2", - "key3": "value3", - "key4": "value4", - "key5": "value5"} - return dict(id=id, name='share_type_%s' % str(id), extra_specs=specs) - - -def return_share_types_get_share_type(context, id): - if id == "777": - raise exception.ShareTypeNotFound(share_type_id=id) - return stub_share_type(int(id)) - - -def return_share_types_destroy(context, name): - if name == "777": - raise exception.ShareTypeNotFoundByName(share_type_name=name) - pass - - -def return_share_types_with_volumes_destroy(context, id): - if id == "1": - raise exception.ShareTypeInUse(share_type_id=id) - pass - - -def return_share_types_create(context, name, specs, is_public): - pass - - -def return_share_types_get_by_name(context, name): - if name == "777": - raise exception.ShareTypeNotFoundByName(share_type_name=name) - return stub_share_type(int(name.split("_")[2])) - - -def make_create_body(name="test_share_1", extra_specs=None, - spec_driver_handles_share_servers=True): - if not extra_specs: - extra_specs = {} - - if spec_driver_handles_share_servers is not None: - extra_specs[constants.ExtraSpecs.DRIVER_HANDLES_SHARE_SERVERS] =\ - spec_driver_handles_share_servers - - body = { - "share_type": { - "name": name, - "extra_specs": extra_specs - } - } - - return body - - -@ddt.ddt -class ShareTypesManageApiTest(test.TestCase): - def setUp(self): - super(ShareTypesManageApiTest, self).setUp() - self.flags(host='fake') - self.controller = types_manage.ShareTypesManageController() - - """to reset notifier drivers left over from other api/contrib tests""" - fake_notifier.reset() - self.addCleanup(fake_notifier.reset) - self.mock_object(share_types, 'create', - return_share_types_create) - self.mock_object(share_types, 'get_share_type_by_name', - return_share_types_get_by_name) - self.mock_object(share_types, 'get_share_type', - return_share_types_get_share_type) - self.mock_object(share_types, 'destroy', - return_share_types_destroy) - - def test_share_types_delete(self): - req = fakes.HTTPRequest.blank('/v2/fake/types/1') - self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) - self.controller._delete(req, 1) - self.assertEqual(1, len(fake_notifier.NOTIFICATIONS)) - - def test_share_types_delete_not_found(self): - self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) - req = fakes.HTTPRequest.blank('/v2/fake/types/777') - self.assertRaises(webob.exc.HTTPNotFound, self.controller._delete, - req, '777') - self.assertEqual(1, len(fake_notifier.NOTIFICATIONS)) - - def test_share_types_with_volumes_destroy(self): - req = fakes.HTTPRequest.blank('/v2/fake/types/1') - self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) - self.controller._delete(req, 1) - self.assertEqual(1, len(fake_notifier.NOTIFICATIONS)) - - @ddt.data(make_create_body("share_type_1"), - make_create_body(spec_driver_handles_share_servers="false"), - make_create_body(spec_driver_handles_share_servers="true"), - make_create_body(spec_driver_handles_share_servers="1"), - make_create_body(spec_driver_handles_share_servers="0"), - make_create_body(spec_driver_handles_share_servers="True"), - make_create_body(spec_driver_handles_share_servers="False"), - make_create_body(spec_driver_handles_share_servers="FalsE")) - def test_create(self, body): - req = fakes.HTTPRequest.blank('/v2/fake/types') - self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) - res_dict = self.controller._create(req, body) - self.assertEqual(1, len(fake_notifier.NOTIFICATIONS)) - self.assertEqual(2, len(res_dict)) - self.assertEqual('share_type_1', res_dict['share_type']['name']) - self.assertEqual('share_type_1', res_dict['volume_type']['name']) - - @ddt.data(None, - make_create_body(""), - make_create_body("n" * 256), - {'foo': {'a': 'b'}}, - {'share_type': 'string'}, - make_create_body(spec_driver_handles_share_servers=None), - make_create_body(spec_driver_handles_share_servers=""), - make_create_body(spec_driver_handles_share_servers=[]), - ) - def test_create_invalid_request(self, body): - req = fakes.HTTPRequest.blank('/v2/fake/types') - self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) - self.assertRaises(webob.exc.HTTPBadRequest, - self.controller._create, req, body) - self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) diff --git a/manila/tests/api/v1/test_share_types.py b/manila/tests/api/v1/test_share_types.py index f36fa3f731..65080500f4 100644 --- a/manila/tests/api/v1/test_share_types.py +++ b/manila/tests/api/v1/test_share_types.py @@ -13,19 +13,25 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime + import ddt import mock from oslo_utils import timeutils +import six import webob from manila.api.v1 import share_types as types from manila.api.views import types as views_types from manila.common import constants +from manila import context +from manila import db from manila import exception from manila import policy from manila.share import share_types from manila import test from manila.tests.api import fakes +from manila.tests import fake_notifier def stub_share_type(id): @@ -71,13 +77,60 @@ def return_share_types_get_by_name(context, name): return stub_share_type(int(name.split("_")[2])) +def return_share_types_destroy(context, name): + if name == "777": + raise exception.ShareTypeNotFoundByName(share_type_name=name) + pass + + +def return_share_types_with_volumes_destroy(context, id): + if id == "1": + raise exception.ShareTypeInUse(share_type_id=id) + pass + + +def return_share_types_create(context, name, specs, is_public): + pass + + +def make_create_body(name="test_share_1", extra_specs=None, + spec_driver_handles_share_servers=True): + if not extra_specs: + extra_specs = {} + + if spec_driver_handles_share_servers is not None: + extra_specs[constants.ExtraSpecs.DRIVER_HANDLES_SHARE_SERVERS] =\ + spec_driver_handles_share_servers + + body = { + "share_type": { + "name": name, + "extra_specs": extra_specs + } + } + + return body + + @ddt.ddt -class ShareTypesApiTest(test.TestCase): +class ShareTypesAPITest(test.TestCase): + def setUp(self): - super(ShareTypesApiTest, self).setUp() + super(self.__class__, self).setUp() + self.flags(host='fake') self.controller = types.ShareTypesController() self.mock_object(policy, 'check_policy', mock.Mock(return_value=True)) + fake_notifier.reset() + self.addCleanup(fake_notifier.reset) + self.mock_object(share_types, 'create', + return_share_types_create) + self.mock_object(share_types, 'get_share_type_by_name', + return_share_types_get_by_name) + self.mock_object(share_types, 'get_share_type', + return_share_types_get_share_type) + self.mock_object(share_types, 'destroy', + return_share_types_destroy) @ddt.data(True, False) def test_share_types_index(self, admin): @@ -104,7 +157,8 @@ class ShareTypesApiTest(test.TestCase): constants.ExtraSpecs.DRIVER_HANDLES_SHARE_SERVERS, '') self.assertEqual('true', required_extra_spec) policy.check_policy.assert_called_once_with( - req.environ['manila.context'], types.RESOURCE_NAME, 'index') + req.environ['manila.context'], self.controller.resource_name, + 'index') def test_share_types_index_no_data(self): self.mock_object(share_types, 'get_all_types', @@ -115,7 +169,8 @@ class ShareTypesApiTest(test.TestCase): self.assertEqual(0, len(res_dict['share_types'])) policy.check_policy.assert_called_once_with( - req.environ['manila.context'], types.RESOURCE_NAME, 'index') + req.environ['manila.context'], self.controller.resource_name, + 'index') def test_share_types_show(self): self.mock_object(share_types, 'get_share_type', @@ -128,7 +183,8 @@ class ShareTypesApiTest(test.TestCase): self.assertEqual('1', res_dict['share_type']['id']) self.assertEqual('share_type_1', res_dict['share_type']['name']) policy.check_policy.assert_called_once_with( - req.environ['manila.context'], types.RESOURCE_NAME, 'show') + req.environ['manila.context'], self.controller.resource_name, + 'show') def test_share_types_show_not_found(self): self.mock_object(share_types, 'get_share_type', @@ -138,7 +194,8 @@ class ShareTypesApiTest(test.TestCase): self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, req, '777') policy.check_policy.assert_called_once_with( - req.environ['manila.context'], types.RESOURCE_NAME, 'show') + req.environ['manila.context'], self.controller.resource_name, + 'show') def test_share_types_default(self): self.mock_object(share_types, 'get_default_share_type', @@ -151,7 +208,8 @@ class ShareTypesApiTest(test.TestCase): self.assertEqual('1', res_dict['share_type']['id']) self.assertEqual('share_type_1', res_dict['share_type']['name']) policy.check_policy.assert_called_once_with( - req.environ['manila.context'], types.RESOURCE_NAME, 'default') + req.environ['manila.context'], self.controller.resource_name, + 'default') def test_share_types_default_not_found(self): self.mock_object(share_types, 'get_default_share_type', @@ -161,7 +219,8 @@ class ShareTypesApiTest(test.TestCase): self.assertRaises(webob.exc.HTTPNotFound, self.controller.default, req) policy.check_policy.assert_called_once_with( - req.environ['manila.context'], types.RESOURCE_NAME, 'default') + req.environ['manila.context'], self.controller.resource_name, + 'default') def test_view_builder_show(self): view_builder = views_types.ViewBuilder() @@ -182,12 +241,13 @@ class ShareTypesApiTest(test.TestCase): output = view_builder.show(request, raw_share_type) self.assertIn('share_type', output) - expected_share_type = dict( - name='new_type', - extra_specs={}, - required_extra_specs={}, - id=42, - ) + expected_share_type = { + 'name': 'new_type', + 'extra_specs': {}, + 'os-share-type-access:is_public': True, + 'required_extra_specs': {}, + 'id': 42, + } self.assertDictMatch(output['share_type'], expected_share_type) def test_view_builder_list(self): @@ -214,12 +274,13 @@ class ShareTypesApiTest(test.TestCase): self.assertIn('share_types', output) for i in range(0, 10): - expected_share_type = dict( - name='new_type', - extra_specs={}, - required_extra_specs={}, - id=42 + i - ) + expected_share_type = { + 'name': 'new_type', + 'extra_specs': {}, + 'os-share-type-access:is_public': True, + 'required_extra_specs': {}, + 'id': 42 + i, + } self.assertDictMatch(output['share_types'][i], expected_share_type) @@ -232,3 +293,359 @@ class ShareTypesApiTest(test.TestCase): self.assertRaises(webob.exc.HTTPBadRequest, self.controller._parse_is_public, 'fakefakefake') + + def test_share_types_delete(self): + req = fakes.HTTPRequest.blank('/v2/fake/types/1') + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + self.controller._delete(req, 1) + self.assertEqual(1, len(fake_notifier.NOTIFICATIONS)) + + def test_share_types_delete_not_found(self): + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + req = fakes.HTTPRequest.blank('/v2/fake/types/777') + self.assertRaises(webob.exc.HTTPNotFound, self.controller._delete, + req, '777') + self.assertEqual(1, len(fake_notifier.NOTIFICATIONS)) + + def test_share_types_with_volumes_destroy(self): + req = fakes.HTTPRequest.blank('/v2/fake/types/1') + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + self.controller._delete(req, 1) + self.assertEqual(1, len(fake_notifier.NOTIFICATIONS)) + + @ddt.data(make_create_body("share_type_1"), + make_create_body(spec_driver_handles_share_servers="false"), + make_create_body(spec_driver_handles_share_servers="true"), + make_create_body(spec_driver_handles_share_servers="1"), + make_create_body(spec_driver_handles_share_servers="0"), + make_create_body(spec_driver_handles_share_servers="True"), + make_create_body(spec_driver_handles_share_servers="False"), + make_create_body(spec_driver_handles_share_servers="FalsE")) + def test_create(self, body): + req = fakes.HTTPRequest.blank('/v2/fake/types') + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + res_dict = self.controller._create(req, body) + self.assertEqual(1, len(fake_notifier.NOTIFICATIONS)) + self.assertEqual(2, len(res_dict)) + self.assertEqual('share_type_1', res_dict['share_type']['name']) + self.assertEqual('share_type_1', res_dict['volume_type']['name']) + + @ddt.data(None, + make_create_body(""), + make_create_body("n" * 256), + {'foo': {'a': 'b'}}, + {'share_type': 'string'}, + make_create_body(spec_driver_handles_share_servers=None), + make_create_body(spec_driver_handles_share_servers=""), + make_create_body(spec_driver_handles_share_servers=[]), + ) + def test_create_invalid_request(self, body): + req = fakes.HTTPRequest.blank('/v2/fake/types') + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + self.assertRaises(webob.exc.HTTPBadRequest, + self.controller._create, req, body) + self.assertEqual(0, len(fake_notifier.NOTIFICATIONS)) + + def assert_share_type_list_equal(self, expected, observed): + self.assertEqual(len(expected), len(observed)) + expected = sorted(expected, key=lambda item: item['id']) + observed = sorted(observed, key=lambda item: item['id']) + for d1, d2 in zip(expected, observed): + self.assertEqual(d1['id'], d2['id']) + + +def generate_type(type_id, is_public): + return { + 'id': type_id, + 'name': u'test', + 'deleted': False, + 'created_at': datetime.datetime(2012, 1, 1, 1, 1, 1, 1), + 'updated_at': None, + 'deleted_at': None, + 'is_public': bool(is_public), + 'extra_specs': {} + } + + +SHARE_TYPES = { + '0': generate_type('0', True), + '1': generate_type('1', True), + '2': generate_type('2', False), + '3': generate_type('3', False)} + +PROJ1_UUID = '11111111-1111-1111-1111-111111111111' +PROJ2_UUID = '22222222-2222-2222-2222-222222222222' +PROJ3_UUID = '33333333-3333-3333-3333-333333333333' + +ACCESS_LIST = [{'share_type_id': '2', 'project_id': PROJ2_UUID}, + {'share_type_id': '2', 'project_id': PROJ3_UUID}, + {'share_type_id': '3', 'project_id': PROJ3_UUID}] + + +def fake_share_type_get(context, id, inactive=False, expected_fields=None): + vol = SHARE_TYPES[id] + if expected_fields and 'projects' in expected_fields: + vol['projects'] = [a['project_id'] + for a in ACCESS_LIST if a['share_type_id'] == id] + return vol + + +def _has_type_access(type_id, project_id): + for access in ACCESS_LIST: + if (access['share_type_id'] == type_id + and access['project_id'] == project_id): + return True + return False + + +def fake_share_type_get_all(context, inactive=False, filters=None): + if filters is None or filters.get('is_public', None) is None: + return SHARE_TYPES + res = {} + for k, v in six.iteritems(SHARE_TYPES): + if filters['is_public'] and _has_type_access(k, context.project_id): + res.update({k: v}) + continue + if v['is_public'] == filters['is_public']: + res.update({k: v}) + return res + + +class FakeResponse(object): + obj = {'share_type': {'id': '0'}, + 'share_types': [{'id': '0'}, {'id': '2'}]} + + def attach(self, **kwargs): + pass + + +class FakeRequest(object): + environ = {"manila.context": context.get_admin_context()} + + def get_db_share_type(self, resource_id): + return SHARE_TYPES[resource_id] + + +class ShareTypeAccessTest(test.TestCase): + + def setUp(self): + super(self.__class__, self).setUp() + self.controller = types.ShareTypesController() + self.req = FakeRequest() + self.context = self.req.environ['manila.context'] + self.mock_object(db, 'share_type_get', fake_share_type_get) + self.mock_object(db, 'share_type_get_all', fake_share_type_get_all) + + def assertShareTypeListEqual(self, expected, observed): + self.assertEqual(len(expected), len(observed)) + expected = sorted(expected, key=lambda item: item['id']) + observed = sorted(observed, key=lambda item: item['id']) + for d1, d2 in zip(expected, observed): + self.assertEqual(d1['id'], d2['id']) + + def test_list_type_access_public(self): + """Querying os-share-type-access on public type should return 404.""" + req = fakes.HTTPRequest.blank('/v1/fake/types/os-share-type-access', + use_admin_context=True) + + self.assertRaises(webob.exc.HTTPNotFound, + self.controller._list_project_access, + req, '1') + + def test_list_type_access_private(self): + expected = {'share_type_access': [ + {'share_type_id': '2', 'project_id': PROJ2_UUID}, + {'share_type_id': '2', 'project_id': PROJ3_UUID}, + ]} + + result = self.controller._list_project_access(self.req, '2') + + self.assertEqual(expected, result) + + def test_list_with_no_context(self): + req = fakes.HTTPRequest.blank('/v1/types/fake/types') + + def fake_authorize(context, target=None, action=None): + raise exception.PolicyNotAuthorized(action='index') + + self.assertRaises(webob.exc.HTTPForbidden, + self.controller._list_project_access, + req, 'fake') + + def test_list_type_with_admin_default_proj1(self): + expected = {'share_types': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v1/fake/types', use_admin_context=True) + req.environ['manila.context'].project_id = PROJ1_UUID + + result = self.controller.index(req) + + self.assertShareTypeListEqual(expected['share_types'], + result['share_types']) + + def test_list_type_with_admin_default_proj2(self): + expected = {'share_types': [{'id': '0'}, {'id': '1'}, {'id': '2'}]} + req = fakes.HTTPRequest.blank('/v2/fake/types', use_admin_context=True) + req.environ['manila.context'].project_id = PROJ2_UUID + + result = self.controller.index(req) + + self.assertShareTypeListEqual(expected['share_types'], + result['share_types']) + + def test_list_type_with_admin_ispublic_true(self): + expected = {'share_types': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=true', + use_admin_context=True) + + result = self.controller.index(req) + + self.assertShareTypeListEqual(expected['share_types'], + result['share_types']) + + def test_list_type_with_admin_ispublic_false(self): + expected = {'share_types': [{'id': '2'}, {'id': '3'}]} + req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=false', + use_admin_context=True) + + result = self.controller.index(req) + + self.assertShareTypeListEqual(expected['share_types'], + result['share_types']) + + def test_list_type_with_admin_ispublic_false_proj2(self): + expected = {'share_types': [{'id': '2'}, {'id': '3'}]} + req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=false', + use_admin_context=True) + req.environ['manila.context'].project_id = PROJ2_UUID + + result = self.controller.index(req) + + self.assertShareTypeListEqual(expected['share_types'], + result['share_types']) + + def test_list_type_with_admin_ispublic_none(self): + expected = {'share_types': [ + {'id': '0'}, {'id': '1'}, {'id': '2'}, {'id': '3'}, + ]} + req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=all', + use_admin_context=True) + + result = self.controller.index(req) + + self.assertShareTypeListEqual(expected['share_types'], + result['share_types']) + + def test_list_type_with_no_admin_default(self): + expected = {'share_types': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v2/fake/types', + use_admin_context=False) + + result = self.controller.index(req) + + self.assertShareTypeListEqual(expected['share_types'], + result['share_types']) + + def test_list_type_with_no_admin_ispublic_true(self): + expected = {'share_types': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=true', + use_admin_context=False) + + result = self.controller.index(req) + + self.assertShareTypeListEqual(expected['share_types'], + result['share_types']) + + def test_list_type_with_no_admin_ispublic_false(self): + expected = {'share_types': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=false', + use_admin_context=False) + + result = self.controller.index(req) + + self.assertShareTypeListEqual(expected['share_types'], + result['share_types']) + + def test_list_type_with_no_admin_ispublic_none(self): + expected = {'share_types': [{'id': '0'}, {'id': '1'}]} + req = fakes.HTTPRequest.blank('/v2/fake/types?is_public=all', + use_admin_context=False) + + result = self.controller.index(req) + + self.assertShareTypeListEqual(expected['share_types'], + result['share_types']) + + def test_add_project_access(self): + def stub_add_share_type_access(context, type_id, project_id): + self.assertEqual('3', type_id, "type_id") + self.assertEqual(PROJ2_UUID, project_id, "project_id") + self.mock_object(db, 'share_type_access_add', + stub_add_share_type_access) + body = {'addProjectAccess': {'project': PROJ2_UUID}} + req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', + use_admin_context=True) + + result = self.controller._add_project_access(req, '3', body) + + self.assertEqual(202, result.status_code) + + def test_add_project_access_with_no_admin_user(self): + req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', + use_admin_context=False) + body = {'addProjectAccess': {'project': PROJ2_UUID}} + + self.assertRaises(webob.exc.HTTPForbidden, + self.controller._add_project_access, + req, '2', body) + + def test_add_project_access_with_already_added_access(self): + def stub_add_share_type_access(context, type_id, project_id): + raise exception.ShareTypeAccessExists(share_type_id=type_id, + project_id=project_id) + self.mock_object(db, 'share_type_access_add', + stub_add_share_type_access) + body = {'addProjectAccess': {'project': PROJ2_UUID}} + req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', + use_admin_context=True) + + self.assertRaises(webob.exc.HTTPConflict, + self.controller._add_project_access, + req, '3', body) + + def test_add_project_access_to_public_share_type(self): + share_type_id = '3' + body = {'addProjectAccess': {'project': PROJ2_UUID}} + self.mock_object(share_types, 'get_share_type', + mock.Mock(return_value={"is_public": True})) + req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', + use_admin_context=True) + + self.assertRaises(webob.exc.HTTPForbidden, + self.controller._add_project_access, + req, share_type_id, body) + + share_types.get_share_type.assert_called_once_with( + mock.ANY, share_type_id) + + def test_remove_project_access_with_bad_access(self): + def stub_remove_share_type_access(context, type_id, project_id): + raise exception.ShareTypeAccessNotFound(share_type_id=type_id, + project_id=project_id) + self.mock_object(db, 'share_type_access_remove', + stub_remove_share_type_access) + body = {'removeProjectAccess': {'project': PROJ2_UUID}} + req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', + use_admin_context=True) + + self.assertRaises(webob.exc.HTTPNotFound, + self.controller._remove_project_access, + req, '3', body) + + def test_remove_project_access_with_no_admin_user(self): + req = fakes.HTTPRequest.blank('/v2/fake/types/2/action', + use_admin_context=False) + body = {'removeProjectAccess': {'project': PROJ2_UUID}} + + self.assertRaises(webob.exc.HTTPForbidden, + self.controller._remove_project_access, + req, '2', body) diff --git a/manila/tests/api/contrib/test_types_extra_specs.py b/manila/tests/api/v1/test_share_types_extra_specs.py similarity index 98% rename from manila/tests/api/contrib/test_types_extra_specs.py rename to manila/tests/api/v1/test_share_types_extra_specs.py index 491feab80c..e57872c745 100644 --- a/manila/tests/api/contrib/test_types_extra_specs.py +++ b/manila/tests/api/v1/test_share_types_extra_specs.py @@ -20,7 +20,7 @@ import mock from oslo_utils import strutils import webob -from manila.api.contrib import types_extra_specs +from manila.api.v1 import share_types_extra_specs from manila.common import constants from manila import exception from manila import test @@ -89,7 +89,8 @@ class ShareTypesExtraSpecsTest(test.TestCase): self.flags(host='fake') self.mock_object(manila.db, 'share_type_get', share_type_get) self.api_path = '/v2/fake/os-share-types/1/extra_specs' - self.controller = types_extra_specs.ShareTypeExtraSpecsController() + self.controller = ( + share_types_extra_specs.ShareTypeExtraSpecsController()) """to reset notifier drivers left over from other api/contrib tests""" self.addCleanup(fake_notifier.reset) diff --git a/manila/tests/policy.json b/manila/tests/policy.json index fb35e33cdd..3a3f469881 100644 --- a/manila/tests/policy.json +++ b/manila/tests/policy.json @@ -37,6 +37,17 @@ "share_type:index": "rule:default", "share_type:show": "rule:default", "share_type:default": "rule:default", + "share_type:create": "rule:default", + "share_type:delete": "rule:default", + "share_type:add_project_access": "rule:admin_api", + "share_type:list_project_access": "rule:admin_api", + "share_type:remove_project_access": "rule:admin_api", + + "share_types_extra_spec:create": "rule:default", + "share_types_extra_spec:update": "rule:default", + "share_types_extra_spec:show": "rule:default", + "share_types_extra_spec:index": "rule:default", + "share_types_extra_spec:delete": "rule:default", "share_instance:index": "rule:admin_api", "share_instance:show": "rule:admin_api", @@ -62,11 +73,6 @@ "share:get_share_metadata": "", "share:delete_share_metadata": "", "share:update_share_metadata": "", - "share_extension:types_manage": "", - "share_extension:types_extra_specs": "", - "share_extension:share_type_access": "", - "share_extension:share_type_access:addProjectAccess": "rule:admin_api", - "share_extension:share_type_access:removeProjectAccess": "rule:admin_api", "share_extension:availability_zones": "", "security_service:index": "",