340 lines
13 KiB
Python
340 lines
13 KiB
Python
# Copyright (c) 2011 OpenStack Foundation
|
|
# Copyright (c) 2014 NetApp, Inc.
|
|
#
|
|
# 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 API controller module.."""
|
|
|
|
import ast
|
|
|
|
from oslo_log import log
|
|
from oslo_utils import strutils
|
|
from oslo_utils import uuidutils
|
|
import six
|
|
from six.moves import http_client
|
|
import webob
|
|
from webob import exc
|
|
|
|
from manila.api.openstack import api_version_request as api_version
|
|
from manila.api.openstack import wsgi
|
|
from manila.api.views import types as views_types
|
|
from manila.common import constants
|
|
from manila import exception
|
|
from manila.i18n import _
|
|
from manila import rpc
|
|
from manila.share import share_types
|
|
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
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.share_type_access
|
|
return super(ShareTypesController, 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)
|
|
|
|
@wsgi.Controller.authorize
|
|
def index(self, req):
|
|
"""Returns the list of share types."""
|
|
|
|
limited_types = self._get_share_types(req)
|
|
req.cache_db_share_types(limited_types)
|
|
return self._view_builder.index(req, limited_types)
|
|
|
|
@wsgi.Controller.authorize
|
|
def show(self, req, id):
|
|
"""Return a single share type item."""
|
|
context = req.environ['manila.context']
|
|
try:
|
|
share_type = self._show_share_type_details(context, id)
|
|
except exception.NotFound:
|
|
msg = _("Share type not found.")
|
|
raise exc.HTTPNotFound(explanation=msg)
|
|
|
|
share_type['id'] = six.text_type(share_type['id'])
|
|
req.cache_db_share_type(share_type)
|
|
return self._view_builder.show(req, share_type)
|
|
|
|
def _show_share_type_details(self, context, id):
|
|
share_type = share_types.get_share_type(context, id)
|
|
required_extra_specs = {}
|
|
try:
|
|
required_extra_specs = share_types.get_valid_required_extra_specs(
|
|
share_type['extra_specs'])
|
|
except exception.InvalidExtraSpec:
|
|
LOG.exception('Share type %(share_type_id)s has invalid required'
|
|
' extra specs.', {'share_type_id': id})
|
|
|
|
share_type['required_extra_specs'] = required_extra_specs
|
|
return share_type
|
|
|
|
@wsgi.Controller.authorize
|
|
def default(self, req):
|
|
"""Return default volume type."""
|
|
context = req.environ['manila.context']
|
|
|
|
try:
|
|
share_type = share_types.get_default_share_type(context)
|
|
except exception.NotFound:
|
|
msg = _("Share type not found")
|
|
raise exc.HTTPNotFound(explanation=msg)
|
|
|
|
if not share_type:
|
|
msg = _("Default share type not found")
|
|
raise exc.HTTPNotFound(explanation=msg)
|
|
|
|
share_type['id'] = six.text_type(share_type['id'])
|
|
return self._view_builder.show(req, share_type)
|
|
|
|
def _get_share_types(self, req):
|
|
"""Helper function that returns a list of type dicts."""
|
|
filters = {}
|
|
context = req.environ['manila.context']
|
|
if context.is_admin:
|
|
# Only admin has query access to all share types
|
|
filters['is_public'] = self._parse_is_public(
|
|
req.params.get('is_public'))
|
|
else:
|
|
filters['is_public'] = True
|
|
|
|
if (req.api_version_request < api_version.APIVersionRequest("2.43")):
|
|
extra_specs = req.params.get('extra_specs')
|
|
if extra_specs:
|
|
msg = _("Filter by 'extra_specs' is not supported by this "
|
|
"microversion. Use 2.43 or greater microversion to "
|
|
"be able to use filter search by 'extra_specs.")
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
else:
|
|
extra_specs = req.params.get('extra_specs')
|
|
if extra_specs:
|
|
filters['extra_specs'] = ast.literal_eval(extra_specs)
|
|
|
|
limited_types = share_types.get_all_types(
|
|
context, search_opts=filters).values()
|
|
return list(limited_types)
|
|
|
|
@staticmethod
|
|
def _parse_is_public(is_public):
|
|
"""Parse is_public into something usable.
|
|
|
|
* True: API should list public share types only
|
|
* False: API should list private share types only
|
|
* None: API should list both public and private share types
|
|
"""
|
|
if is_public is None:
|
|
# preserve default value of showing only public types
|
|
return True
|
|
elif six.text_type(is_public).lower() == "all":
|
|
return None
|
|
else:
|
|
try:
|
|
return strutils.bool_from_string(is_public, strict=True)
|
|
except ValueError:
|
|
msg = _('Invalid is_public filter [%s]') % is_public
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
@wsgi.Controller.api_version("1.0", "2.23")
|
|
@wsgi.action("create")
|
|
def create(self, req, body):
|
|
return self._create(req, body, set_defaults=True)
|
|
|
|
@wsgi.Controller.api_version("2.24") # noqa
|
|
@wsgi.action("create")
|
|
def create(self, req, body): # pylint: disable=E0102
|
|
return self._create(req, body, set_defaults=False)
|
|
|
|
@wsgi.Controller.authorize('create')
|
|
def _create(self, req, body, set_defaults=False):
|
|
"""Creates a new share type."""
|
|
context = req.environ['manila.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')
|
|
specs = share_type.get('extra_specs', {})
|
|
description = share_type.get('description')
|
|
if (description and req.api_version_request
|
|
< api_version.APIVersionRequest("2.41")):
|
|
msg = _("'description' key is not supported by this "
|
|
"microversion. Use 2.41 or greater microversion "
|
|
"to be able to use 'description' in share type.")
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
is_public = share_type.get(
|
|
'os-share-type-access:is_public',
|
|
share_type.get('share_type_access:is_public', True),
|
|
)
|
|
|
|
if (name is None or name == "" or len(name) > 255
|
|
or (description and len(description) > 255)):
|
|
msg = _("Type name or description is not valid.")
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
|
|
# Note(cknight): Set the default extra spec value for snapshot_support
|
|
# for API versions before it was required.
|
|
if set_defaults:
|
|
if constants.ExtraSpecs.SNAPSHOT_SUPPORT not in specs:
|
|
specs[constants.ExtraSpecs.SNAPSHOT_SUPPORT] = True
|
|
|
|
try:
|
|
required_extra_specs = (
|
|
share_types.get_valid_required_extra_specs(specs)
|
|
)
|
|
share_types.create(context, name, specs, is_public,
|
|
description=description)
|
|
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.InvalidExtraSpec as e:
|
|
raise webob.exc.HTTPBadRequest(explanation=six.text_type(e))
|
|
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")
|
|
@wsgi.Controller.authorize('delete')
|
|
def _delete(self, req, id):
|
|
"""Deletes an existing share type."""
|
|
context = req.environ['manila.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=http_client.ACCEPTED)
|
|
|
|
@wsgi.Controller.authorize('list_project_access')
|
|
def share_type_access(self, req, id):
|
|
context = req.environ['manila.context']
|
|
|
|
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')
|
|
@wsgi.Controller.authorize('add_project_access')
|
|
def _add_project_access(self, req, id, body):
|
|
context = req.environ['manila.context']
|
|
self._check_body(body, 'addProjectAccess')
|
|
project = body['addProjectAccess']['project']
|
|
|
|
self._verify_if_non_public_share_type(context, id)
|
|
|
|
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=http_client.ACCEPTED)
|
|
|
|
@wsgi.action('removeProjectAccess')
|
|
@wsgi.Controller.authorize('remove_project_access')
|
|
def _remove_project_access(self, req, id, body):
|
|
context = req.environ['manila.context']
|
|
self._check_body(body, 'removeProjectAccess')
|
|
project = body['removeProjectAccess']['project']
|
|
|
|
self._verify_if_non_public_share_type(context, id)
|
|
|
|
try:
|
|
share_types.remove_share_type_access(context, id, project)
|
|
except exception.ShareTypeAccessNotFound as err:
|
|
raise webob.exc.HTTPNotFound(explanation=six.text_type(err))
|
|
return webob.Response(status_int=http_client.ACCEPTED)
|
|
|
|
def _verify_if_non_public_share_type(self, context, share_type_id):
|
|
try:
|
|
share_type = share_types.get_share_type(context, share_type_id)
|
|
|
|
if share_type['is_public']:
|
|
msg = _("Type access modification is not applicable to "
|
|
"public share type.")
|
|
raise webob.exc.HTTPConflict(explanation=msg)
|
|
|
|
except exception.ShareTypeNotFound as err:
|
|
raise webob.exc.HTTPNotFound(explanation=six.text_type(err))
|
|
|
|
|
|
def create_resource():
|
|
return wsgi.Resource(ShareTypesController())
|