556ae86d38
This patch prints a deprecation message when CG APIs are used and prompts users to swich to Generic Volume Group APIs instead. CG APIs are also marked as deprecated in API reference docs. CG APIs will be removed in a future release when it is appropriate and will be decided by the Cinder team. This was communicated in Pike. Change-Id: Ib6751fae6b5fb78de98a2ea62f507f9102f71b76
318 lines
13 KiB
Python
318 lines
13 KiB
Python
# Copyright (C) 2012 - 2014 EMC Corporation.
|
|
# 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 consistencygroups api."""
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_log import versionutils
|
|
from oslo_utils import strutils
|
|
from six.moves import http_client
|
|
import webob
|
|
from webob import exc
|
|
|
|
from cinder.api import common
|
|
from cinder.api import extensions
|
|
from cinder.api.openstack import wsgi
|
|
from cinder.api.views import consistencygroups as consistencygroup_views
|
|
from cinder import exception
|
|
from cinder import group as group_api
|
|
from cinder.i18n import _
|
|
from cinder.policies import group_actions as gp_action_policy
|
|
from cinder.policies import groups as group_policy
|
|
from cinder.volume import group_types
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
DEPRECATE_CG_API_MSG = ("Consistency Group APIs are deprecated. "
|
|
"Use Generic Volume Group APIs instead.")
|
|
|
|
|
|
class ConsistencyGroupsController(wsgi.Controller):
|
|
"""The ConsistencyGroups API controller for the OpenStack API."""
|
|
|
|
_view_builder_class = consistencygroup_views.ViewBuilder
|
|
|
|
def __init__(self):
|
|
self.group_api = group_api.API()
|
|
super(ConsistencyGroupsController, self).__init__()
|
|
|
|
def show(self, req, id):
|
|
"""Return data about the given consistency group."""
|
|
versionutils.report_deprecated_feature(LOG, DEPRECATE_CG_API_MSG)
|
|
LOG.debug('show called for member %s', id)
|
|
context = req.environ['cinder.context']
|
|
|
|
# Not found exception will be handled at the wsgi level
|
|
consistencygroup = self._get(context, id)
|
|
|
|
return self._view_builder.detail(req, consistencygroup)
|
|
|
|
def delete(self, req, id, body):
|
|
"""Delete a consistency group."""
|
|
versionutils.report_deprecated_feature(LOG, DEPRECATE_CG_API_MSG)
|
|
LOG.debug('delete called for member %s', id)
|
|
context = req.environ['cinder.context']
|
|
force = False
|
|
if body:
|
|
self.assert_valid_body(body, 'consistencygroup')
|
|
|
|
cg_body = body['consistencygroup']
|
|
try:
|
|
force = strutils.bool_from_string(cg_body.get('force', False),
|
|
strict=True)
|
|
except ValueError:
|
|
msg = _("Invalid value '%s' for force.") % force
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
LOG.info('Delete consistency group with id: %s', id)
|
|
|
|
try:
|
|
group = self._get(context, id)
|
|
context.authorize(gp_action_policy.DELETE_POLICY, target_obj=group)
|
|
self.group_api.delete(context, group, force)
|
|
# Not found exception will be handled at the wsgi level
|
|
except exception.InvalidConsistencyGroup as error:
|
|
raise exc.HTTPBadRequest(explanation=error.msg)
|
|
|
|
return webob.Response(status_int=http_client.ACCEPTED)
|
|
|
|
def index(self, req):
|
|
"""Returns a summary list of consistency groups."""
|
|
versionutils.report_deprecated_feature(LOG, DEPRECATE_CG_API_MSG)
|
|
return self._get_consistencygroups(req, is_detail=False)
|
|
|
|
def detail(self, req):
|
|
"""Returns a detailed list of consistency groups."""
|
|
versionutils.report_deprecated_feature(LOG, DEPRECATE_CG_API_MSG)
|
|
return self._get_consistencygroups(req, is_detail=True)
|
|
|
|
def _get(self, context, id):
|
|
# Not found exception will be handled at the wsgi level
|
|
consistencygroup = self.group_api.get(context, group_id=id)
|
|
|
|
return consistencygroup
|
|
|
|
def _get_cgsnapshot(self, context, id):
|
|
# Not found exception will be handled at the wsgi level
|
|
cgsnapshot = self.group_api.get_group_snapshot(
|
|
context,
|
|
group_snapshot_id=id)
|
|
|
|
return cgsnapshot
|
|
|
|
def _get_consistencygroups(self, req, is_detail):
|
|
"""Returns a list of consistency groups through view builder."""
|
|
context = req.environ['cinder.context']
|
|
context.authorize(group_policy.GET_ALL_POLICY)
|
|
filters = req.params.copy()
|
|
|
|
# make another copy of filters, since it is being modified in
|
|
# consistencygroup_api while getting consistencygroups
|
|
marker, limit, offset = common.get_pagination_params(filters)
|
|
sort_keys, sort_dirs = common.get_sort_params(filters)
|
|
|
|
groups = self.group_api.get_all(
|
|
context, filters=filters, marker=marker, limit=limit,
|
|
offset=offset, sort_keys=sort_keys, sort_dirs=sort_dirs)
|
|
|
|
if is_detail:
|
|
groups = self._view_builder.detail_list(req, groups)
|
|
else:
|
|
groups = self._view_builder.summary_list(req, groups)
|
|
|
|
return groups
|
|
|
|
@wsgi.response(http_client.ACCEPTED)
|
|
def create(self, req, body):
|
|
"""Create a new consistency group."""
|
|
versionutils.report_deprecated_feature(LOG, DEPRECATE_CG_API_MSG)
|
|
LOG.debug('Creating new consistency group %s', body)
|
|
self.assert_valid_body(body, 'consistencygroup')
|
|
|
|
context = req.environ['cinder.context']
|
|
context.authorize(group_policy.CREATE_POLICY)
|
|
consistencygroup = body['consistencygroup']
|
|
self.validate_name_and_description(consistencygroup)
|
|
name = consistencygroup.get('name', None)
|
|
description = consistencygroup.get('description', None)
|
|
volume_types = consistencygroup.get('volume_types', None)
|
|
if not volume_types:
|
|
msg = _("volume_types must be provided to create "
|
|
"consistency group %(name)s.") % {'name': name}
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
volume_types = volume_types.rstrip(',').split(',')
|
|
availability_zone = consistencygroup.get('availability_zone', None)
|
|
group_type = group_types.get_default_cgsnapshot_type()
|
|
if not group_type:
|
|
msg = (_('Group type %s not found. Rerun migration script to '
|
|
'create the default cgsnapshot type.') %
|
|
group_types.DEFAULT_CGSNAPSHOT_TYPE)
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
LOG.info("Creating consistency group %(name)s.",
|
|
{'name': name})
|
|
|
|
try:
|
|
new_consistencygroup = self.group_api.create(
|
|
context, name, description, group_type['id'], volume_types,
|
|
availability_zone=availability_zone)
|
|
except (exception.InvalidConsistencyGroup,
|
|
exception.InvalidGroup,
|
|
exception.InvalidVolumeType,
|
|
exception.ObjectActionError) as error:
|
|
raise exc.HTTPBadRequest(explanation=error.msg)
|
|
except exception.NotFound:
|
|
# Not found exception will be handled at the wsgi level
|
|
raise
|
|
|
|
retval = self._view_builder.summary(req, new_consistencygroup)
|
|
return retval
|
|
|
|
@wsgi.response(http_client.ACCEPTED)
|
|
def create_from_src(self, req, body):
|
|
"""Create a new consistency group from a source.
|
|
|
|
The source can be a CG snapshot or a CG. Note that
|
|
this does not require volume_types as the "create"
|
|
API above.
|
|
"""
|
|
versionutils.report_deprecated_feature(LOG, DEPRECATE_CG_API_MSG)
|
|
LOG.debug('Creating new consistency group %s.', body)
|
|
self.assert_valid_body(body, 'consistencygroup-from-src')
|
|
|
|
context = req.environ['cinder.context']
|
|
context.authorize(group_policy.CREATE_POLICY)
|
|
consistencygroup = body['consistencygroup-from-src']
|
|
self.validate_name_and_description(consistencygroup)
|
|
name = consistencygroup.get('name', None)
|
|
description = consistencygroup.get('description', None)
|
|
cgsnapshot_id = consistencygroup.get('cgsnapshot_id', None)
|
|
source_cgid = consistencygroup.get('source_cgid', None)
|
|
if not cgsnapshot_id and not source_cgid:
|
|
msg = _("Either 'cgsnapshot_id' or 'source_cgid' must be "
|
|
"provided to create consistency group %(name)s "
|
|
"from source.") % {'name': name}
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
if cgsnapshot_id and source_cgid:
|
|
msg = _("Cannot provide both 'cgsnapshot_id' and 'source_cgid' "
|
|
"to create consistency group %(name)s from "
|
|
"source.") % {'name': name}
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
if cgsnapshot_id:
|
|
LOG.info("Creating consistency group %(name)s from "
|
|
"cgsnapshot %(snap)s.",
|
|
{'name': name, 'snap': cgsnapshot_id})
|
|
elif source_cgid:
|
|
LOG.info("Creating consistency group %(name)s from "
|
|
"source consistency group %(source_cgid)s.",
|
|
{'name': name, 'source_cgid': source_cgid})
|
|
|
|
try:
|
|
if source_cgid:
|
|
self._get(context, source_cgid)
|
|
if cgsnapshot_id:
|
|
self._get_cgsnapshot(context, cgsnapshot_id)
|
|
new_group = self.group_api.create_from_src(
|
|
context, name, description, cgsnapshot_id, source_cgid)
|
|
except exception.NotFound:
|
|
# Not found exception will be handled at the wsgi level
|
|
raise
|
|
except exception.CinderException as error:
|
|
raise exc.HTTPBadRequest(explanation=error.msg)
|
|
|
|
retval = self._view_builder.summary(req, new_group)
|
|
return retval
|
|
|
|
def _check_update_parameters(self, name, description, add_volumes,
|
|
remove_volumes):
|
|
if not (name or description or add_volumes or remove_volumes):
|
|
msg = _("Name, description, add_volumes, and remove_volumes "
|
|
"can not be all empty in the request body.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
def _update(self, context, group, name, description, add_volumes,
|
|
remove_volumes,
|
|
allow_empty=False):
|
|
LOG.info("Updating consistency group %(id)s with name %(name)s "
|
|
"description: %(description)s add_volumes: "
|
|
"%(add_volumes)s remove_volumes: %(remove_volumes)s.",
|
|
{'id': group.id,
|
|
'name': name,
|
|
'description': description,
|
|
'add_volumes': add_volumes,
|
|
'remove_volumes': remove_volumes})
|
|
|
|
self.group_api.update(context, group, name, description,
|
|
add_volumes, remove_volumes)
|
|
|
|
def update(self, req, id, body):
|
|
"""Update the consistency group.
|
|
|
|
Expected format of the input parameter 'body':
|
|
|
|
.. code-block:: json
|
|
|
|
{
|
|
"consistencygroup":
|
|
{
|
|
"name": "my_cg",
|
|
"description": "My consistency group",
|
|
"add_volumes": "volume-uuid-1,volume-uuid-2,...",
|
|
"remove_volumes": "volume-uuid-8,volume-uuid-9,..."
|
|
}
|
|
}
|
|
|
|
"""
|
|
versionutils.report_deprecated_feature(LOG, DEPRECATE_CG_API_MSG)
|
|
LOG.debug('Update called for consistency group %s.', id)
|
|
if not body:
|
|
msg = _("Missing request body.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
self.assert_valid_body(body, 'consistencygroup')
|
|
context = req.environ['cinder.context']
|
|
group = self._get(context, id)
|
|
context.authorize(group_policy.UPDATE_POLICY, target_obj=group)
|
|
consistencygroup = body.get('consistencygroup', None)
|
|
self.validate_name_and_description(consistencygroup)
|
|
name = consistencygroup.get('name', None)
|
|
description = consistencygroup.get('description', None)
|
|
add_volumes = consistencygroup.get('add_volumes', None)
|
|
remove_volumes = consistencygroup.get('remove_volumes', None)
|
|
|
|
self._check_update_parameters(name, description, add_volumes,
|
|
remove_volumes)
|
|
self._update(context, group, name, description, add_volumes,
|
|
remove_volumes)
|
|
return webob.Response(status_int=http_client.ACCEPTED)
|
|
|
|
|
|
class Consistencygroups(extensions.ExtensionDescriptor):
|
|
"""consistency groups support."""
|
|
|
|
name = 'Consistencygroups'
|
|
alias = 'consistencygroups'
|
|
updated = '2014-08-18T00:00:00+00:00'
|
|
|
|
def get_resources(self):
|
|
resources = []
|
|
res = extensions.ResourceExtension(
|
|
Consistencygroups.alias, ConsistencyGroupsController(),
|
|
collection_actions={'detail': 'GET', 'create_from_src': 'POST'},
|
|
member_actions={'delete': 'POST', 'update': 'PUT'})
|
|
resources.append(res)
|
|
return resources
|