You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
321 lines
12 KiB
Python
321 lines
12 KiB
Python
# Copyright 2011 Justin Santa Barbara
|
|
# 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.
|
|
|
|
"""The volumes api."""
|
|
|
|
from http import HTTPStatus
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import uuidutils
|
|
import webob
|
|
from webob import exc
|
|
|
|
from cinder.api import api_utils
|
|
from cinder.api import common
|
|
from cinder.api.contrib import scheduler_hints
|
|
from cinder.api import microversions as mv
|
|
from cinder.api.openstack import wsgi
|
|
from cinder.api.schemas import volumes
|
|
from cinder.api.v2.views import volumes as volume_views
|
|
from cinder.api import validation
|
|
from cinder import exception
|
|
from cinder import group as group_api
|
|
from cinder.i18n import _
|
|
from cinder.image import glance
|
|
from cinder import objects
|
|
from cinder import utils
|
|
from cinder import volume as cinder_volume
|
|
from cinder.volume import volume_utils
|
|
|
|
CONF = cfg.CONF
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class VolumeController(wsgi.Controller):
|
|
"""The Volumes API controller for the OpenStack API."""
|
|
|
|
_view_builder_class = volume_views.ViewBuilder
|
|
|
|
def __init__(self, ext_mgr):
|
|
self.volume_api = cinder_volume.API()
|
|
self.group_api = group_api.API()
|
|
self.ext_mgr = ext_mgr
|
|
super(VolumeController, self).__init__()
|
|
|
|
def show(self, req, id):
|
|
"""Return data about the given volume."""
|
|
context = req.environ['cinder.context']
|
|
|
|
# Not found exception will be handled at the wsgi level
|
|
vol = self.volume_api.get(context, id, viewable_admin_meta=True)
|
|
req.cache_db_volume(vol)
|
|
|
|
api_utils.add_visible_admin_metadata(vol)
|
|
|
|
return self._view_builder.detail(req, vol)
|
|
|
|
def delete(self, req, id):
|
|
"""Delete a volume."""
|
|
context = req.environ['cinder.context']
|
|
|
|
cascade = utils.get_bool_param('cascade', req.params)
|
|
|
|
LOG.info("Delete volume with id: %s", id)
|
|
|
|
# Not found exception will be handled at the wsgi level
|
|
volume = self.volume_api.get(context, id)
|
|
self.volume_api.delete(context, volume, cascade=cascade)
|
|
return webob.Response(status_int=HTTPStatus.ACCEPTED)
|
|
|
|
def index(self, req):
|
|
"""Returns a summary list of volumes."""
|
|
return self._get_volumes(req, is_detail=False)
|
|
|
|
def detail(self, req):
|
|
"""Returns a detailed list of volumes."""
|
|
return self._get_volumes(req, is_detail=True)
|
|
|
|
def _get_volumes(self, req, is_detail):
|
|
"""Returns a list of volumes, transformed through view builder."""
|
|
|
|
context = req.environ['cinder.context']
|
|
|
|
params = req.params.copy()
|
|
marker, limit, offset = common.get_pagination_params(params)
|
|
sort_keys, sort_dirs = common.get_sort_params(params)
|
|
filters = params
|
|
|
|
# NOTE(wanghao): Always removing glance_metadata since we support it
|
|
# only in API version >= VOLUME_LIST_GLANCE_METADATA.
|
|
filters.pop('glance_metadata', None)
|
|
api_utils.remove_invalid_filter_options(
|
|
context,
|
|
filters,
|
|
self._get_volume_filter_options())
|
|
|
|
# NOTE(thingee): v2 API allows name instead of display_name
|
|
if 'name' in sort_keys:
|
|
sort_keys[sort_keys.index('name')] = 'display_name'
|
|
|
|
if 'name' in filters:
|
|
filters['display_name'] = filters.pop('name')
|
|
|
|
self.volume_api.check_volume_filters(filters)
|
|
volumes = self.volume_api.get_all(context, marker, limit,
|
|
sort_keys=sort_keys,
|
|
sort_dirs=sort_dirs,
|
|
filters=filters,
|
|
viewable_admin_meta=True,
|
|
offset=offset)
|
|
|
|
for volume in volumes:
|
|
api_utils.add_visible_admin_metadata(volume)
|
|
|
|
req.cache_db_volumes(volumes.objects)
|
|
|
|
if is_detail:
|
|
volumes = self._view_builder.detail_list(req, volumes)
|
|
else:
|
|
volumes = self._view_builder.summary_list(req, volumes)
|
|
return volumes
|
|
|
|
def _image_uuid_from_ref(self, image_ref, context):
|
|
# If the image ref was generated by nova api, strip image_ref
|
|
# down to an id.
|
|
image_uuid = None
|
|
try:
|
|
image_uuid = image_ref.split('/').pop()
|
|
except AttributeError:
|
|
msg = _("Invalid imageRef provided.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
image_service = glance.get_default_image_service()
|
|
|
|
# First see if this is an actual image ID
|
|
if uuidutils.is_uuid_like(image_uuid):
|
|
try:
|
|
image = image_service.show(context, image_uuid)
|
|
if 'id' in image:
|
|
return image['id']
|
|
except Exception:
|
|
# Pass and see if there is a matching image name
|
|
pass
|
|
|
|
# Could not find by ID, check if it is an image name
|
|
try:
|
|
params = {'filters': {'name': image_ref}}
|
|
images = list(image_service.detail(context, **params))
|
|
if len(images) > 1:
|
|
msg = _("Multiple matches found for '%s', use an ID to be more"
|
|
" specific.") % image_ref
|
|
raise exc.HTTPConflict(explanation=msg)
|
|
for img in images:
|
|
return img['id']
|
|
except exc.HTTPConflict:
|
|
raise
|
|
except Exception:
|
|
# Pass the other exception and let default not found error
|
|
# handling take care of it
|
|
pass
|
|
|
|
msg = _("Invalid image identifier or unable to "
|
|
"access requested image.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
# NOTE: using mv.BASE_VERSION (which is 3.0) is a bit nonstandard,
|
|
# but this class is no longer consumed by the v2 API, though it is
|
|
# a superclass of cinder.api.v3.volumes. Although create() is
|
|
# overridden in the subclass, I didn't want to remove it from
|
|
# here until we are sure that the v3 unit tests for create() test
|
|
# everything that the v2 unit tests covered.
|
|
@wsgi.response(HTTPStatus.ACCEPTED)
|
|
@validation.schema(volumes.create, mv.BASE_VERSION)
|
|
def create(self, req, body):
|
|
"""Creates a new volume."""
|
|
|
|
LOG.debug('Create volume request body: %s', body)
|
|
context = req.environ['cinder.context']
|
|
|
|
# NOTE (pooja_jadhav) To fix bug 1774155, scheduler hints is not
|
|
# loaded as a standard extension. If user passes
|
|
# OS-SCH-HNT:scheduler_hints in the request body, then it will be
|
|
# validated in the create method and this method will add
|
|
# scheduler_hints in body['volume'].
|
|
body = scheduler_hints.create(req, body)
|
|
volume = body['volume']
|
|
|
|
kwargs = {}
|
|
self.validate_name_and_description(volume, check_length=False)
|
|
|
|
# NOTE(thingee): v2 API allows name instead of display_name
|
|
if 'name' in volume:
|
|
volume['display_name'] = volume.pop('name')
|
|
|
|
# NOTE(thingee): v2 API allows description instead of
|
|
# display_description
|
|
if 'description' in volume:
|
|
volume['display_description'] = volume.pop('description')
|
|
|
|
if 'image_id' in volume:
|
|
volume['imageRef'] = volume.pop('image_id')
|
|
|
|
req_volume_type = volume.get('volume_type', None)
|
|
if req_volume_type:
|
|
# Not found exception will be handled at the wsgi level
|
|
kwargs['volume_type'] = (
|
|
objects.VolumeType.get_by_name_or_id(context, req_volume_type))
|
|
|
|
kwargs['metadata'] = volume.get('metadata', None)
|
|
|
|
snapshot_id = volume.get('snapshot_id')
|
|
if snapshot_id is not None:
|
|
# Not found exception will be handled at the wsgi level
|
|
kwargs['snapshot'] = self.volume_api.get_snapshot(context,
|
|
snapshot_id)
|
|
else:
|
|
kwargs['snapshot'] = None
|
|
|
|
source_volid = volume.get('source_volid')
|
|
if source_volid is not None:
|
|
# Not found exception will be handled at the wsgi level
|
|
kwargs['source_volume'] = \
|
|
self.volume_api.get_volume(context,
|
|
source_volid)
|
|
else:
|
|
kwargs['source_volume'] = None
|
|
|
|
kwargs['group'] = None
|
|
kwargs['consistencygroup'] = None
|
|
consistencygroup_id = volume.get('consistencygroup_id')
|
|
if consistencygroup_id is not None:
|
|
# Not found exception will be handled at the wsgi level
|
|
kwargs['group'] = self.group_api.get(context, consistencygroup_id)
|
|
|
|
size = volume.get('size', None)
|
|
if size is None and kwargs['snapshot'] is not None:
|
|
size = kwargs['snapshot']['volume_size']
|
|
elif size is None and kwargs['source_volume'] is not None:
|
|
size = kwargs['source_volume']['size']
|
|
|
|
LOG.info("Create volume of %s GB", size)
|
|
|
|
image_ref = volume.get('imageRef')
|
|
if image_ref is not None:
|
|
image_uuid = self._image_uuid_from_ref(image_ref, context)
|
|
kwargs['image_id'] = image_uuid
|
|
|
|
kwargs['availability_zone'] = volume.get('availability_zone', None)
|
|
kwargs['scheduler_hints'] = volume.get('scheduler_hints', None)
|
|
|
|
try:
|
|
new_volume = self.volume_api.create(
|
|
context, size, volume.get('display_name'),
|
|
volume.get('display_description'), **kwargs)
|
|
except exception.VolumeTypeDefaultMisconfiguredError as err:
|
|
raise webob.exc.HTTPInternalServerError(explanation=err.msg)
|
|
|
|
retval = self._view_builder.detail(req, new_volume)
|
|
|
|
return retval
|
|
|
|
def _get_volume_filter_options(self):
|
|
"""Return volume search options allowed by non-admin."""
|
|
return common.get_enabled_resource_filters('volume').get('volume', [])
|
|
|
|
# NOTE: see NOTE for create(), above
|
|
@validation.schema(volumes.update, mv.BASE_VERSION,
|
|
mv.get_prior_version(mv.SUPPORT_VOLUME_SCHEMA_CHANGES))
|
|
@validation.schema(volumes.update_volume_v353,
|
|
mv.SUPPORT_VOLUME_SCHEMA_CHANGES)
|
|
def update(self, req, id, body):
|
|
"""Update a volume."""
|
|
context = req.environ['cinder.context']
|
|
update_dict = body['volume']
|
|
|
|
self.validate_name_and_description(update_dict, check_length=False)
|
|
|
|
# NOTE(thingee): v2 API allows name instead of display_name
|
|
if 'name' in update_dict:
|
|
update_dict['display_name'] = update_dict.pop('name')
|
|
|
|
# NOTE(thingee): v2 API allows description instead of
|
|
# display_description
|
|
if 'description' in update_dict:
|
|
update_dict['display_description'] = update_dict.pop('description')
|
|
|
|
# Not found and Invalid exceptions will be handled at the wsgi level
|
|
try:
|
|
volume = self.volume_api.get(context, id, viewable_admin_meta=True)
|
|
volume_utils.notify_about_volume_usage(context, volume,
|
|
'update.start')
|
|
self.volume_api.update(context, volume, update_dict)
|
|
except exception.InvalidVolumeMetadataSize as error:
|
|
raise webob.exc.HTTPRequestEntityTooLarge(explanation=error.msg)
|
|
|
|
volume.update(update_dict)
|
|
|
|
api_utils.add_visible_admin_metadata(volume)
|
|
|
|
volume_utils.notify_about_volume_usage(context, volume,
|
|
'update.end')
|
|
|
|
return self._view_builder.detail(req, volume)
|
|
|
|
|
|
def create_resource(ext_mgr):
|
|
return wsgi.Resource(VolumeController(ext_mgr))
|