manila/manila/api/v2/shares.py
Goutham Pacha Ravi 55edb00cc1 [RBAC] Enforce check for share updates
When a user has access to the APIs to reset status,
task state or replica state but doesn't have access to
the share, they must be prevented from performing
those actions. This enforcement allows granular control
of these actions and the resources themselves.

Change-Id: Ic3be777b238a467d1b7bd1daa6aa088dedb095b0
Closes-Bug: #1955627
Signed-off-by: Goutham Pacha Ravi <gouthampravi@gmail.com>
2023-09-25 16:58:59 -07:00

713 lines
30 KiB
Python

# Copyright (c) 2015 Mirantis inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from http import client as http_client
from oslo_log import log
import webob
from webob import exc
from manila.api import common
from manila.api.openstack import api_version_request as api_version
from manila.api.openstack import wsgi
from manila.api.v1 import share_manage
from manila.api.v1 import share_unmanage
from manila.api.v1 import shares
from manila.api.v2 import metadata
from manila.api.views import share_accesses as share_access_views
from manila.api.views import share_migration as share_migration_views
from manila.api.views import shares as share_views
from manila.common import constants
from manila import db
from manila import exception
from manila.i18n import _
from manila.lock import api as resource_locks
from manila import policy
from manila import share
from manila import utils
LOG = log.getLogger(__name__)
class ShareController(wsgi.Controller,
shares.ShareMixin,
share_manage.ShareManageMixin,
share_unmanage.ShareUnmanageMixin,
metadata.MetadataController,
wsgi.AdminActionsMixin):
"""The Shares API v2 controller for the OpenStack API."""
resource_name = 'share'
_view_builder_class = share_views.ViewBuilder
def __init__(self):
super(ShareController, self).__init__()
self.share_api = share.API()
self.resource_locks_api = resource_locks.API()
self._access_view_builder = share_access_views.ViewBuilder()
self._migration_view_builder = share_migration_views.ViewBuilder()
@wsgi.Controller.authorize('revert_to_snapshot')
def _revert(self, req, id, body=None):
"""Revert a share to a snapshot."""
context = req.environ['manila.context']
revert_data = self._validate_revert_parameters(context, body)
try:
share_id = id
snapshot_id = revert_data['snapshot_id']
share = self.share_api.get(context, share_id)
snapshot = self.share_api.get_snapshot(context, snapshot_id)
if share.get('is_soft_deleted'):
msg = _("Share '%s cannot revert to snapshot, "
"since it has been soft deleted.") % share_id
raise exc.HTTPForbidden(explanation=msg)
# Ensure share supports reverting to a snapshot
if not share['revert_to_snapshot_support']:
msg_args = {'share_id': share_id, 'snap_id': snapshot_id}
msg = _('Share %(share_id)s may not be reverted to snapshot '
'%(snap_id)s, because the share does not have that '
'capability.')
raise exc.HTTPBadRequest(explanation=msg % msg_args)
# Ensure requested share & snapshot match.
if share['id'] != snapshot['share_id']:
msg_args = {'share_id': share_id, 'snap_id': snapshot_id}
msg = _('Snapshot %(snap_id)s is not associated with share '
'%(share_id)s.')
raise exc.HTTPBadRequest(explanation=msg % msg_args)
# Ensure share status is 'available'.
if share['status'] != constants.STATUS_AVAILABLE:
msg_args = {
'share_id': share_id,
'state': share['status'],
'available': constants.STATUS_AVAILABLE,
}
msg = _("Share %(share_id)s is in '%(state)s' state, but it "
"must be in '%(available)s' state to be reverted to a "
"snapshot.")
raise exc.HTTPConflict(explanation=msg % msg_args)
# Ensure snapshot status is 'available'.
if snapshot['status'] != constants.STATUS_AVAILABLE:
msg_args = {
'snap_id': snapshot_id,
'state': snapshot['status'],
'available': constants.STATUS_AVAILABLE,
}
msg = _("Snapshot %(snap_id)s is in '%(state)s' state, but it "
"must be in '%(available)s' state to be restored.")
raise exc.HTTPConflict(explanation=msg % msg_args)
# Ensure a long-running task isn't active on the share
if share.is_busy:
msg_args = {'share_id': share_id}
msg = _("Share %(share_id)s may not be reverted while it has "
"an active task.")
raise exc.HTTPConflict(explanation=msg % msg_args)
# Ensure the snapshot is the most recent one.
latest_snapshot = self.share_api.get_latest_snapshot_for_share(
context, share_id)
if not latest_snapshot:
msg_args = {'share_id': share_id}
msg = _("Could not determine the latest snapshot for share "
"%(share_id)s.")
raise exc.HTTPBadRequest(explanation=msg % msg_args)
if latest_snapshot['id'] != snapshot_id:
msg_args = {
'share_id': share_id,
'snap_id': snapshot_id,
'latest_snap_id': latest_snapshot['id'],
}
msg = _("Snapshot %(snap_id)s may not be restored because "
"it is not the most recent snapshot of share "
"%(share_id)s. Currently the latest snapshot is "
"%(latest_snap_id)s.")
raise exc.HTTPConflict(explanation=msg % msg_args)
# Ensure the access rules are not in the process of updating
for instance in share['instances']:
access_rules_status = instance['access_rules_status']
if access_rules_status != constants.ACCESS_STATE_ACTIVE:
msg_args = {
'share_id': share_id,
'snap_id': snapshot_id,
'state': constants.ACCESS_STATE_ACTIVE
}
msg = _("Snapshot %(snap_id)s belongs to a share "
"%(share_id)s which has access rules that are "
"not %(state)s.")
raise exc.HTTPConflict(explanation=msg % msg_args)
msg_args = {'share_id': share_id, 'snap_id': snapshot_id}
msg = 'Reverting share %(share_id)s to snapshot %(snap_id)s.'
LOG.info(msg, msg_args)
self.share_api.revert_to_snapshot(context, share, snapshot)
except exception.ShareNotFound as e:
raise exc.HTTPNotFound(explanation=e.msg)
except exception.ShareSnapshotNotFound as e:
raise exc.HTTPBadRequest(explanation=e.msg)
except exception.ShareSizeExceedsAvailableQuota as e:
raise exc.HTTPForbidden(explanation=e.msg)
except exception.ReplicationException as e:
raise exc.HTTPBadRequest(explanation=e.msg)
return webob.Response(status_int=http_client.ACCEPTED)
def _validate_revert_parameters(self, context, body):
if not (body and self.is_valid_body(body, 'revert')):
msg = _("Revert entity not found in request body.")
raise exc.HTTPBadRequest(explanation=msg)
required_parameters = ('snapshot_id',)
data = body['revert']
for parameter in required_parameters:
if parameter not in data:
msg = _("Required parameter %s not found.") % parameter
raise exc.HTTPBadRequest(explanation=msg)
if not data.get(parameter):
msg = _("Required parameter %s is empty.") % parameter
raise exc.HTTPBadRequest(explanation=msg)
return data
@wsgi.Controller.api_version("2.65")
def create(self, req, body):
if not self.is_valid_body(body, 'share'):
raise exc.HTTPUnprocessableEntity()
share = body['share']
scheduler_hints = share.pop('scheduler_hints', None)
if req.api_version_request < api_version.APIVersionRequest("2.67"):
if scheduler_hints:
scheduler_hints.pop('only_host', None)
return self._create(req, body,
check_create_share_from_snapshot_support=True,
check_availability_zones_extra_spec=True,
scheduler_hints=scheduler_hints)
@wsgi.Controller.api_version("2.48", "2.64") # noqa
def create(self, req, body): # pylint: disable=function-redefined # noqa F811
return self._create(req, body,
check_create_share_from_snapshot_support=True,
check_availability_zones_extra_spec=True)
@wsgi.Controller.api_version("2.31", "2.47") # noqa
def create(self, req, body): # pylint: disable=function-redefined # noqa F811
return self._create(
req, body, check_create_share_from_snapshot_support=True)
@wsgi.Controller.api_version("2.24", "2.30") # noqa
def create(self, req, body): # pylint: disable=function-redefined # noqa F811
body.get('share', {}).pop('share_group_id', None)
return self._create(req, body,
check_create_share_from_snapshot_support=True)
@wsgi.Controller.api_version("2.0", "2.23") # noqa
def create(self, req, body): # pylint: disable=function-redefined # noqa F811
body.get('share', {}).pop('share_group_id', None)
return self._create(req, body)
@wsgi.Controller.api_version('2.0', '2.6')
@wsgi.action('os-reset_status')
def share_reset_status_legacy(self, req, id, body):
context = req.environ['manila.context']
try:
share = self.share_api.get(context, id)
except exception.NotFound:
raise exc.HTTPNotFound("Share %s not found" % id)
if share.get('is_soft_deleted'):
msg = _("status cannot be reset for share '%s' "
"since it has been soft deleted.") % id
raise exc.HTTPForbidden(explanation=msg)
return self._reset_status(req, id, body, resource=share)
@wsgi.Controller.api_version('2.7')
@wsgi.action('reset_status')
@wsgi.Controller.authorize('reset_status')
def share_reset_status(self, req, id, body):
context = req.environ['manila.context']
try:
share = self.share_api.get(context, id)
except exception.NotFound:
raise exc.HTTPNotFound("Share %s not found" % id)
if share.get('is_soft_deleted'):
msg = _("status cannot be reset for share '%s' "
"since it has been soft deleted.") % id
raise exc.HTTPForbidden(explanation=msg)
return self._reset_status(req, id, body, resource=share)
@wsgi.Controller.api_version('2.0', '2.6')
@wsgi.action('os-force_delete')
def share_force_delete_legacy(self, req, id, body):
return self._force_delete(req, id, body)
@wsgi.Controller.api_version('2.7')
@wsgi.action('force_delete')
def share_force_delete(self, req, id, body):
return self._force_delete(req, id, body)
@wsgi.Controller.api_version('2.69')
@wsgi.action('soft_delete')
@wsgi.Controller.authorize('soft_delete')
def share_soft_delete(self, req, id, body):
"""Soft delete a share."""
context = req.environ['manila.context']
LOG.debug("Soft delete share with id: %s", id, context=context)
try:
share = self.share_api.get(context, id)
self.share_api.soft_delete(context, share)
except exception.NotFound:
raise exc.HTTPNotFound()
except exception.InvalidShare as e:
raise exc.HTTPForbidden(explanation=e.msg)
except exception.ShareBusyException as e:
raise exc.HTTPForbidden(explanation=e.msg)
except exception.Conflict as e:
raise exc.HTTPConflict(explanation=e.msg)
return webob.Response(status_int=http_client.ACCEPTED)
@wsgi.Controller.api_version('2.69')
@wsgi.action('restore')
@wsgi.Controller.authorize("restore")
def share_restore(self, req, id, body):
"""Restore a share from recycle bin."""
context = req.environ['manila.context']
LOG.debug("Restore share with id: %s", id, context=context)
try:
share = self.share_api.get(context, id)
except exception.NotFound:
msg = _("No share exists with ID %s.")
raise exc.HTTPNotFound(explanation=msg % id)
# If the share not exist in Recycle Bin, the API will return
# success directly.
is_soft_deleted = share.get('is_soft_deleted')
if not is_soft_deleted:
return webob.Response(status_int=http_client.OK)
# If the share has reached the expired time, and is been deleting,
# it too late to restore the share.
if share['status'] in [constants.STATUS_DELETING,
constants.STATUS_ERROR_DELETING]:
msg = _("Share %s is being deleted or has suffered an error "
"during deletion, cannot be restored.")
raise exc.HTTPForbidden(explanation=msg % id)
self.share_api.restore(context, share)
return webob.Response(status_int=http_client.ACCEPTED)
@wsgi.Controller.api_version('2.29', experimental=True)
@wsgi.action("migration_start")
@wsgi.Controller.authorize
def migration_start(self, req, id, body):
"""Migrate a share to the specified host."""
context = req.environ['manila.context']
try:
share = self.share_api.get(context, id)
except exception.NotFound:
msg = _("Share %s not found.") % id
raise exc.HTTPNotFound(explanation=msg)
if share.get('is_soft_deleted'):
msg = _("Migration cannot start for share '%s' "
"since it has been soft deleted.") % id
raise exception.InvalidShare(reason=msg)
params = body.get('migration_start')
if not params:
raise exc.HTTPBadRequest(explanation=_("Request is missing body."))
driver_assisted_params = ['preserve_metadata', 'writable',
'nondisruptive', 'preserve_snapshots']
bool_params = (driver_assisted_params +
['force_host_assisted_migration'])
mandatory_params = driver_assisted_params + ['host']
utils.check_params_exist(mandatory_params, params)
bool_param_values = utils.check_params_are_boolean(bool_params, params)
new_share_network = None
new_share_type = None
new_share_network_id = params.get('new_share_network_id', None)
if new_share_network_id:
try:
new_share_network = db.share_network_get(
context, new_share_network_id)
except exception.NotFound:
msg = _("Share network %s not "
"found.") % new_share_network_id
raise exc.HTTPBadRequest(explanation=msg)
common.check_share_network_is_active(new_share_network)
else:
share_network_id = share.get('share_network_id', None)
if share_network_id:
current_share_network = db.share_network_get(
context, share_network_id)
common.check_share_network_is_active(current_share_network)
new_share_type_id = params.get('new_share_type_id', None)
if new_share_type_id:
try:
new_share_type = db.share_type_get(
context, new_share_type_id)
except exception.NotFound:
msg = _("Share type %s not found.") % new_share_type_id
raise exc.HTTPBadRequest(explanation=msg)
try:
return_code = self.share_api.migration_start(
context, share, params['host'],
bool_param_values['force_host_assisted_migration'],
bool_param_values['preserve_metadata'],
bool_param_values['writable'],
bool_param_values['nondisruptive'],
bool_param_values['preserve_snapshots'],
new_share_network=new_share_network,
new_share_type=new_share_type)
except exception.Conflict as e:
raise exc.HTTPConflict(explanation=e.msg)
return webob.Response(status_int=return_code)
@wsgi.Controller.api_version('2.22', experimental=True)
@wsgi.action("migration_complete")
@wsgi.Controller.authorize
def migration_complete(self, req, id, body):
"""Invokes 2nd phase of share migration."""
context = req.environ['manila.context']
try:
share = self.share_api.get(context, id)
except exception.NotFound:
msg = _("Share %s not found.") % id
raise exc.HTTPNotFound(explanation=msg)
self.share_api.migration_complete(context, share)
return webob.Response(status_int=http_client.ACCEPTED)
@wsgi.Controller.api_version('2.22', experimental=True)
@wsgi.action("migration_cancel")
@wsgi.Controller.authorize
def migration_cancel(self, req, id, body):
"""Attempts to cancel share migration."""
context = req.environ['manila.context']
try:
share = self.share_api.get(context, id)
except exception.NotFound:
msg = _("Share %s not found.") % id
raise exc.HTTPNotFound(explanation=msg)
self.share_api.migration_cancel(context, share)
return webob.Response(status_int=http_client.ACCEPTED)
@wsgi.Controller.api_version('2.22', experimental=True)
@wsgi.action("migration_get_progress")
@wsgi.Controller.authorize
def migration_get_progress(self, req, id, body):
"""Retrieve share migration progress for a given share."""
context = req.environ['manila.context']
try:
share = self.share_api.get(context, id)
except exception.NotFound:
msg = _("Share %s not found.") % id
raise exc.HTTPNotFound(explanation=msg)
result = self.share_api.migration_get_progress(context, share)
# refresh share model
share = self.share_api.get(context, id)
return self._migration_view_builder.get_progress(req, share, result)
@wsgi.Controller.api_version('2.22', experimental=True)
@wsgi.action("reset_task_state")
@wsgi.Controller.authorize
def reset_task_state(self, req, id, body):
context = req.environ['manila.context']
try:
share = self.share_api.get(context, id)
except exception.NotFound:
raise exception.ShareNotFound(share_id=id)
if share.get('is_soft_deleted'):
msg = _("task state cannot be reset for share '%s' "
"since it has been soft deleted.") % id
raise exc.HTTPForbidden(explanation=msg)
return self._reset_status(req, id, body, status_attr='task_state',
resource=share)
@wsgi.Controller.api_version('2.0', '2.6')
@wsgi.action('os-allow_access')
def allow_access_legacy(self, req, id, body):
"""Add share access rule."""
return self._allow_access(req, id, body)
@wsgi.Controller.api_version('2.7')
@wsgi.action('allow_access')
def allow_access(self, req, id, body):
"""Add share access rule."""
args = (req, id, body)
kwargs = {}
if req.api_version_request >= api_version.APIVersionRequest("2.13"):
kwargs['enable_ceph'] = True
if req.api_version_request >= api_version.APIVersionRequest("2.28"):
kwargs['allow_on_error_status'] = True
if req.api_version_request >= api_version.APIVersionRequest("2.38"):
kwargs['enable_ipv6'] = True
if req.api_version_request >= api_version.APIVersionRequest("2.45"):
kwargs['enable_metadata'] = True
if req.api_version_request >= api_version.APIVersionRequest("2.74"):
kwargs['allow_on_error_state'] = True
if req.api_version_request >= api_version.APIVersionRequest("2.82"):
access_data = body.get('allow_access')
kwargs['lock_visibility'] = access_data.get(
'lock_visibility', False)
kwargs['lock_deletion'] = access_data.get('lock_deletion', False)
kwargs['lock_reason'] = access_data.get('lock_reason')
return self._allow_access(*args, **kwargs)
@wsgi.Controller.api_version('2.0', '2.6')
@wsgi.action('os-deny_access')
def deny_access_legacy(self, req, id, body):
"""Remove share access rule."""
return self._deny_access(req, id, body)
@wsgi.Controller.api_version('2.7')
@wsgi.action('deny_access')
def deny_access(self, req, id, body):
"""Remove share access rule."""
args = (req, id, body)
kwargs = {}
if req.api_version_request >= api_version.APIVersionRequest("2.74"):
kwargs['allow_on_error_state'] = True
return self._deny_access(*args, **kwargs)
@wsgi.Controller.api_version('2.0', '2.6')
@wsgi.action('os-access_list')
def access_list_legacy(self, req, id, body):
"""List share access rules."""
return self._access_list(req, id, body)
@wsgi.Controller.api_version('2.7', '2.44')
@wsgi.action('access_list')
def access_list(self, req, id, body):
"""List share access rules."""
return self._access_list(req, id, body)
@wsgi.Controller.api_version('2.0', '2.6')
@wsgi.action('os-extend')
def extend_legacy(self, req, id, body):
"""Extend size of a share."""
body.get('os-extend', {}).pop('force', None)
return self._extend(req, id, body)
@wsgi.Controller.api_version('2.7', '2.63')
@wsgi.action('extend')
def extend(self, req, id, body):
"""Extend size of a share."""
body.get('extend', {}).pop('force', None)
return self._extend(req, id, body)
@wsgi.Controller.api_version('2.64') # noqa
@wsgi.action('extend')
def extend(self, req, id, body): # pylint: disable=function-redefined # noqa F811
"""Extend size of a share."""
return self._extend(req, id, body)
@wsgi.Controller.api_version('2.0', '2.6')
@wsgi.action('os-shrink')
def shrink_legacy(self, req, id, body):
"""Shrink size of a share."""
return self._shrink(req, id, body)
@wsgi.Controller.api_version('2.7')
@wsgi.action('shrink')
def shrink(self, req, id, body):
"""Shrink size of a share."""
return self._shrink(req, id, body)
@wsgi.Controller.api_version('2.7', '2.7')
def manage(self, req, body):
body.get('share', {}).pop('is_public', None)
detail = self._manage(req, body, allow_dhss_true=False)
return detail
@wsgi.Controller.api_version("2.8", "2.48") # noqa
def manage(self, req, body): # pylint: disable=function-redefined # noqa F811
detail = self._manage(req, body, allow_dhss_true=False)
return detail
@wsgi.Controller.api_version("2.49") # noqa
def manage(self, req, body): # pylint: disable=function-redefined # noqa F811
detail = self._manage(req, body, allow_dhss_true=True)
return detail
@wsgi.Controller.api_version('2.7', '2.48')
@wsgi.action('unmanage')
def unmanage(self, req, id, body=None):
return self._unmanage(req, id, body, allow_dhss_true=False)
@wsgi.Controller.api_version('2.49') # noqa
@wsgi.action('unmanage')
def unmanage(self, req, id, # pylint: disable=function-redefined # noqa F811
body=None):
return self._unmanage(req, id, body, allow_dhss_true=True)
@wsgi.Controller.api_version('2.27')
@wsgi.action('revert')
def revert(self, req, id, body=None):
return self._revert(req, id, body)
@wsgi.Controller.api_version("2.0")
@wsgi.Controller.authorize("get_all")
def index(self, req):
"""Returns a summary list of shares."""
if req.api_version_request < api_version.APIVersionRequest("2.35"):
req.GET.pop('export_location_id', None)
req.GET.pop('export_location_path', None)
if req.api_version_request < api_version.APIVersionRequest("2.36"):
req.GET.pop('name~', None)
req.GET.pop('description~', None)
req.GET.pop('description', None)
if req.api_version_request < api_version.APIVersionRequest("2.42"):
req.GET.pop('with_count', None)
if req.api_version_request < api_version.APIVersionRequest("2.69"):
req.GET.pop('is_soft_deleted', None)
return self._get_shares(req, is_detail=False)
@wsgi.Controller.api_version("2.0")
@wsgi.Controller.authorize("get_all")
def detail(self, req):
"""Returns a detailed list of shares."""
if req.api_version_request < api_version.APIVersionRequest("2.35"):
req.GET.pop('export_location_id', None)
req.GET.pop('export_location_path', None)
if req.api_version_request < api_version.APIVersionRequest("2.36"):
req.GET.pop('name~', None)
req.GET.pop('description~', None)
req.GET.pop('description', None)
if req.api_version_request < api_version.APIVersionRequest("2.69"):
req.GET.pop('is_soft_deleted', None)
return self._get_shares(req, is_detail=True)
def _validate_metadata_for_update(self, req, share_id, metadata,
delete=True):
admin_metadata_ignore_keys = (
constants.AdminOnlyMetadata.SCHEDULER_FILTERS
)
context = req.environ['manila.context']
if set(metadata).intersection(set(admin_metadata_ignore_keys)):
try:
policy.check_policy(
context, 'share', 'update_admin_only_metadata')
except exception.PolicyNotAuthorized:
msg = _("Cannot set or update admin only metadata.")
LOG.exception(msg)
raise exc.HTTPForbidden(explanation=msg)
admin_metadata_ignore_keys = []
current_share_metadata = db.share_metadata_get(context, share_id)
if delete:
_metadata = metadata
for key in admin_metadata_ignore_keys:
if key in current_share_metadata:
_metadata[key] = current_share_metadata[key]
else:
metadata_copy = metadata.copy()
for key in admin_metadata_ignore_keys:
metadata_copy.pop(key, None)
_metadata = current_share_metadata.copy()
_metadata.update(metadata_copy)
return _metadata
# NOTE: (ashrod98) original metadata method and policy overrides
@wsgi.Controller.api_version("2.0")
@wsgi.Controller.authorize("get_share_metadata")
def index_metadata(self, req, resource_id):
"""Returns the list of metadata for a given share."""
return self._index_metadata(req, resource_id)
@wsgi.Controller.api_version("2.0")
@wsgi.Controller.authorize("update_share_metadata")
def create_metadata(self, req, resource_id, body):
if not self.is_valid_body(body, 'metadata'):
expl = _('Malformed request body')
raise exc.HTTPBadRequest(explanation=expl)
_metadata = self._validate_metadata_for_update(req, resource_id,
body['metadata'],
delete=False)
body['metadata'] = _metadata
return self._create_metadata(req, resource_id, body)
@wsgi.Controller.api_version("2.0")
@wsgi.Controller.authorize("update_share_metadata")
def update_all_metadata(self, req, resource_id, body):
if not self.is_valid_body(body, 'metadata'):
expl = _('Malformed request body')
raise exc.HTTPBadRequest(explanation=expl)
_metadata = self._validate_metadata_for_update(req, resource_id,
body['metadata'])
body['metadata'] = _metadata
return self._update_all_metadata(req, resource_id, body)
@wsgi.Controller.api_version("2.0")
@wsgi.Controller.authorize("update_share_metadata")
def update_metadata_item(self, req, resource_id, body, key):
if not self.is_valid_body(body, 'meta'):
expl = _('Malformed request body')
raise exc.HTTPBadRequest(explanation=expl)
_metadata = self._validate_metadata_for_update(req, resource_id,
body['metadata'],
delete=False)
body['metadata'] = _metadata
return self._update_metadata_item(req, resource_id, body, key)
@wsgi.Controller.api_version("2.0")
@wsgi.Controller.authorize("get_share_metadata")
def show_metadata(self, req, resource_id, key):
return self._show_metadata(req, resource_id, key)
@wsgi.Controller.api_version("2.0")
@wsgi.Controller.authorize("delete_share_metadata")
def delete_metadata(self, req, resource_id, key):
context = req.environ['manila.context']
if key in constants.AdminOnlyMetadata.SCHEDULER_FILTERS:
policy.check_policy(context, 'share',
'update_admin_only_metadata')
return self._delete_metadata(req, resource_id, key)
def create_resource():
return wsgi.Resource(ShareController())