1794131293
Currently when Cinder calls volume.api.create(), it transfers dict of volume type to the interface. This patch is to get volume type object, and transfer it to volume.api.create() interface. So that it uses object fields instead of dict. Change-Id: I981b5060abaff280aa34a9570c65300f467045d6
292 lines
11 KiB
Python
292 lines
11 KiB
Python
#
|
|
# 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 V3 api."""
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_utils import uuidutils
|
|
import webob
|
|
from webob import exc
|
|
|
|
from cinder.api import common
|
|
from cinder.api.openstack import wsgi
|
|
from cinder.api.v2 import volumes as volumes_v2
|
|
from cinder.api.v3.views import volumes as volume_views_v3
|
|
from cinder import exception
|
|
from cinder import group as group_api
|
|
from cinder import objects
|
|
from cinder.i18n import _, _LI
|
|
import cinder.policy
|
|
from cinder import utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
SUMMARY_BASE_MICRO_VERSION = '3.12'
|
|
|
|
|
|
def check_policy(context, action, target_obj=None):
|
|
target = {
|
|
'project_id': context.project_id,
|
|
'user_id': context.user_id
|
|
}
|
|
target.update(target_obj or {})
|
|
|
|
_action = 'volume:%s' % action
|
|
cinder.policy.enforce(context, _action, target)
|
|
|
|
|
|
class VolumeController(volumes_v2.VolumeController):
|
|
"""The Volumes API controller for the OpenStack API V3."""
|
|
|
|
_view_builder_class = volume_views_v3.ViewBuilder
|
|
|
|
def __init__(self, ext_mgr):
|
|
self.group_api = group_api.API()
|
|
super(VolumeController, self).__init__(ext_mgr)
|
|
|
|
def delete(self, req, id):
|
|
"""Delete a volume."""
|
|
context = req.environ['cinder.context']
|
|
req_version = req.api_version_request
|
|
|
|
cascade = utils.get_bool_param('cascade', req.params)
|
|
force = False
|
|
|
|
params = ""
|
|
if req_version.matches('3.23'):
|
|
force = utils.get_bool_param('force', req.params)
|
|
if cascade or force:
|
|
params = "(cascade: %(c)s, force: %(f)s)" % {'c': cascade,
|
|
'f': force}
|
|
|
|
msg = _LI("Delete volume with id: %(id)s %(params)s")
|
|
LOG.info(msg, {'id': id, 'params': params}, context=context)
|
|
|
|
if force:
|
|
check_policy(context, 'force_delete')
|
|
|
|
volume = self.volume_api.get(context, id)
|
|
|
|
self.volume_api.delete(context, volume,
|
|
cascade=cascade,
|
|
force=force)
|
|
|
|
return webob.Response(status_int=202)
|
|
|
|
def _get_volumes(self, req, is_detail):
|
|
"""Returns a list of volumes, transformed through view builder."""
|
|
|
|
context = req.environ['cinder.context']
|
|
req_version = req.api_version_request
|
|
|
|
params = req.params.copy()
|
|
marker, limit, offset = common.get_pagination_params(params)
|
|
sort_keys, sort_dirs = common.get_sort_params(params)
|
|
filters = params
|
|
|
|
if req_version.matches(None, "3.3"):
|
|
filters.pop('glance_metadata', None)
|
|
|
|
if req_version.matches(None, "3.9"):
|
|
filters.pop('group_id', None)
|
|
|
|
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')
|
|
|
|
if 'group_id' in filters:
|
|
filters['consistencygroup_id'] = filters.pop('group_id')
|
|
|
|
strict = req.api_version_request.matches("3.2", None)
|
|
self.volume_api.check_volume_filters(filters, strict)
|
|
|
|
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:
|
|
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
|
|
|
|
@wsgi.Controller.api_version(SUMMARY_BASE_MICRO_VERSION)
|
|
def summary(self, req):
|
|
"""Return summary of volumes."""
|
|
view_builder_v3 = volume_views_v3.ViewBuilder()
|
|
context = req.environ['cinder.context']
|
|
filters = req.params.copy()
|
|
|
|
utils.remove_invalid_filter_options(context, filters,
|
|
self._get_volume_filter_options())
|
|
|
|
volumes = self.volume_api.get_volume_summary(context, filters=filters)
|
|
return view_builder_v3.quick_summary(volumes[0], int(volumes[1]))
|
|
|
|
@wsgi.response(202)
|
|
def create(self, req, body):
|
|
"""Creates a new volume.
|
|
|
|
:param req: the request
|
|
:param body: the request body
|
|
:returns: dict -- the new volume dictionary
|
|
:raises: HTTPNotFound, HTTPBadRequest
|
|
"""
|
|
self.assert_valid_body(body, 'volume')
|
|
|
|
LOG.debug('Create volume request body: %s', body)
|
|
context = req.environ['cinder.context']
|
|
|
|
req_version = req.api_version_request
|
|
# Remove group_id from body if max version is less than 3.13.
|
|
if req_version.matches(None, "3.12"):
|
|
# NOTE(xyang): The group_id is from a group created with a
|
|
# group_type. So with this group_id, we've got a group_type
|
|
# for this volume. Also if group_id is passed in, that means
|
|
# we already know which backend is hosting the group and the
|
|
# volume will be created on the same backend as well. So it
|
|
# won't go through the scheduler again if a group_id is
|
|
# passed in.
|
|
try:
|
|
body.get('volume', {}).pop('group_id', None)
|
|
except AttributeError:
|
|
msg = (_("Invalid body provided for creating volume. "
|
|
"Request API version: %s.") % req_version)
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
volume = body['volume']
|
|
kwargs = {}
|
|
self.validate_name_and_description(volume)
|
|
|
|
# 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:
|
|
if not uuidutils.is_uuid_like(snapshot_id):
|
|
msg = _("Snapshot ID must be in UUID form.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
# 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
|
|
|
|
source_replica = volume.get('source_replica')
|
|
if source_replica is not None:
|
|
# Not found exception will be handled at the wsgi level
|
|
src_vol = self.volume_api.get_volume(context,
|
|
source_replica)
|
|
if src_vol['replication_status'] == 'disabled':
|
|
explanation = _('source volume id:%s is not'
|
|
' replicated') % source_replica
|
|
raise exc.HTTPBadRequest(explanation=explanation)
|
|
kwargs['source_replica'] = src_vol
|
|
else:
|
|
kwargs['source_replica'] = None
|
|
|
|
consistencygroup_id = volume.get('consistencygroup_id')
|
|
if consistencygroup_id is not None:
|
|
try:
|
|
kwargs['consistencygroup'] = (
|
|
self.consistencygroup_api.get(context,
|
|
consistencygroup_id))
|
|
except exception.ConsistencyGroupNotFound:
|
|
# Not found exception will be handled at the wsgi level
|
|
kwargs['group'] = self.group_api.get(
|
|
context, consistencygroup_id)
|
|
else:
|
|
kwargs['consistencygroup'] = None
|
|
|
|
# Get group_id if volume is in a group.
|
|
group_id = volume.get('group_id')
|
|
if group_id is not None:
|
|
try:
|
|
kwargs['group'] = self.group_api.get(context, group_id)
|
|
except exception.GroupNotFound as error:
|
|
raise exc.HTTPNotFound(explanation=error.msg)
|
|
|
|
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']
|
|
elif size is None and kwargs['source_replica'] is not None:
|
|
size = kwargs['source_replica']['size']
|
|
|
|
LOG.info(_LI("Create volume of %s GB"), size)
|
|
|
|
if self.ext_mgr.is_loaded('os-image-create'):
|
|
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)
|
|
multiattach = volume.get('multiattach', False)
|
|
kwargs['multiattach'] = multiattach
|
|
|
|
new_volume = self.volume_api.create(context,
|
|
size,
|
|
volume.get('display_name'),
|
|
volume.get('display_description'),
|
|
**kwargs)
|
|
|
|
retval = self._view_builder.detail(req, new_volume)
|
|
|
|
return retval
|
|
|
|
|
|
def create_resource(ext_mgr):
|
|
return wsgi.Resource(VolumeController(ext_mgr))
|