118d440a90
Currently when a POST request is made to /consistency-groups with a source cg-snapshot, the API does not register the share network information (share_server_id and share_network_id) in the database row newly created for the CG being created. This information is essential to any shares that are being created along with the consistency group. - Disallow providing a share_network_id when using a source cg_snapshot_id - Copy share network information from the parent CG - Fix the share_server_id that was incorrect in the API response APIImpact Closes-Bug: #1571594 Closes-Bug: #1572742 Change-Id: I1c3581c81e0b845f46eef3cd0acddb55850447a5
256 lines
9.4 KiB
Python
256 lines
9.4 KiB
Python
# Copyright 2015 Alex Meade
|
|
# 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 consistency groups API."""
|
|
|
|
from oslo_log import log
|
|
from oslo_utils import uuidutils
|
|
import six
|
|
import webob
|
|
from webob import exc
|
|
|
|
from manila.api import common
|
|
from manila.api.openstack import wsgi
|
|
import manila.api.views.consistency_groups as cg_views
|
|
import manila.consistency_group.api as cg_api
|
|
from manila import db
|
|
from manila import exception
|
|
from manila.i18n import _
|
|
from manila.i18n import _LI
|
|
from manila.share import share_types
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class CGController(wsgi.Controller, wsgi.AdminActionsMixin):
|
|
"""The Consistency Groups API controller for the OpenStack API."""
|
|
|
|
resource_name = 'consistency_group'
|
|
_view_builder_class = cg_views.CGViewBuilder
|
|
resource_name = 'consistency_group'
|
|
|
|
def __init__(self):
|
|
super(CGController, self).__init__()
|
|
self.cg_api = cg_api.API()
|
|
|
|
@wsgi.Controller.api_version('2.4', experimental=True)
|
|
@wsgi.Controller.authorize('get')
|
|
def show(self, req, id):
|
|
"""Return data about the given CG."""
|
|
context = req.environ['manila.context']
|
|
|
|
try:
|
|
cg = self.cg_api.get(context, id)
|
|
except exception.NotFound:
|
|
msg = _("Consistency group %s not found.") % id
|
|
raise exc.HTTPNotFound(explanation=msg)
|
|
|
|
return self._view_builder.detail(req, cg)
|
|
|
|
@wsgi.Controller.api_version('2.4', experimental=True)
|
|
@wsgi.Controller.authorize
|
|
def delete(self, req, id):
|
|
"""Delete a CG."""
|
|
context = req.environ['manila.context']
|
|
|
|
LOG.info(_LI("Delete consistency group with id: %s"), id,
|
|
context=context)
|
|
|
|
try:
|
|
cg = self.cg_api.get(context, id)
|
|
except exception.NotFound:
|
|
msg = _("Consistency group %s not found.") % id
|
|
raise exc.HTTPNotFound(explanation=msg)
|
|
|
|
try:
|
|
self.cg_api.delete(context, cg)
|
|
except exception.InvalidConsistencyGroup as e:
|
|
raise exc.HTTPConflict(explanation=six.text_type(e))
|
|
|
|
return webob.Response(status_int=202)
|
|
|
|
@wsgi.Controller.api_version('2.4', experimental=True)
|
|
@wsgi.Controller.authorize('get_all')
|
|
def index(self, req):
|
|
"""Returns a summary list of shares."""
|
|
return self._get_cgs(req, is_detail=False)
|
|
|
|
@wsgi.Controller.api_version('2.4', experimental=True)
|
|
@wsgi.Controller.authorize('get_all')
|
|
def detail(self, req):
|
|
"""Returns a detailed list of shares."""
|
|
return self._get_cgs(req, is_detail=True)
|
|
|
|
def _get_cgs(self, req, is_detail):
|
|
"""Returns a list of shares, transformed through view builder."""
|
|
context = req.environ['manila.context']
|
|
|
|
search_opts = {}
|
|
search_opts.update(req.GET)
|
|
|
|
# Remove keys that are not related to cg attrs
|
|
search_opts.pop('limit', None)
|
|
search_opts.pop('offset', None)
|
|
|
|
cgs = self.cg_api.get_all(
|
|
context, detailed=is_detail, search_opts=search_opts)
|
|
|
|
limited_list = common.limited(cgs, req)
|
|
|
|
if is_detail:
|
|
cgs = self._view_builder.detail_list(req, limited_list)
|
|
else:
|
|
cgs = self._view_builder.summary_list(req, limited_list)
|
|
return cgs
|
|
|
|
@wsgi.Controller.api_version('2.4', experimental=True)
|
|
@wsgi.Controller.authorize
|
|
def update(self, req, id, body):
|
|
"""Update a share."""
|
|
context = req.environ['manila.context']
|
|
|
|
if not self.is_valid_body(body, 'consistency_group'):
|
|
msg = _("'consistency_group' is missing from the request body.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
cg_data = body['consistency_group']
|
|
valid_update_keys = {
|
|
'name',
|
|
'description',
|
|
}
|
|
invalid_fields = set(cg_data.keys()) - valid_update_keys
|
|
if invalid_fields:
|
|
msg = _("The fields %s are invalid or not allowed to be updated.")
|
|
raise exc.HTTPBadRequest(explanation=msg % invalid_fields)
|
|
|
|
try:
|
|
cg = self.cg_api.get(context, id)
|
|
except exception.NotFound:
|
|
msg = _("Consistency group %s not found.") % id
|
|
raise exc.HTTPNotFound(explanation=msg)
|
|
|
|
cg = self.cg_api.update(context, cg, cg_data)
|
|
return self._view_builder.detail(req, cg)
|
|
|
|
@wsgi.Controller.api_version('2.4', experimental=True)
|
|
@wsgi.response(202)
|
|
@wsgi.Controller.authorize
|
|
def create(self, req, body):
|
|
"""Creates a new share."""
|
|
context = req.environ['manila.context']
|
|
|
|
if not self.is_valid_body(body, 'consistency_group'):
|
|
msg = _("'consistency_group' is missing from the request body.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
cg = body['consistency_group']
|
|
|
|
valid_fields = {'name', 'description', 'share_types',
|
|
'source_cgsnapshot_id', 'share_network_id'}
|
|
invalid_fields = set(cg.keys()) - valid_fields
|
|
if invalid_fields:
|
|
msg = _("The fields %s are invalid.") % invalid_fields
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
if 'share_types' in cg and 'source_cgsnapshot_id' in cg:
|
|
msg = _("Cannot supply both 'share_types' and "
|
|
"'source_cgsnapshot_id' attributes.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
if not cg.get('share_types') and 'source_cgsnapshot_id' not in cg:
|
|
default_share_type = share_types.get_default_share_type()
|
|
if default_share_type:
|
|
cg['share_types'] = [default_share_type['id']]
|
|
else:
|
|
msg = _("Must specify at least one share type as a default "
|
|
"share type has not been configured.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
kwargs = {}
|
|
|
|
if 'name' in cg:
|
|
kwargs['name'] = cg.get('name')
|
|
if 'description' in cg:
|
|
kwargs['description'] = cg.get('description')
|
|
|
|
_share_types = cg.get('share_types')
|
|
if _share_types:
|
|
if not all([uuidutils.is_uuid_like(st) for st in _share_types]):
|
|
msg = _("The 'share_types' attribute must be a list of uuids")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
kwargs['share_type_ids'] = _share_types
|
|
|
|
if 'share_network_id' in cg and 'source_cgsnapshot_id' in cg:
|
|
msg = _("Cannot supply both 'share_network_id' and "
|
|
"'source_cgsnapshot_id' attributes as the share network "
|
|
"is inherited from the source.")
|
|
raise exc.HTTPBadRequest(explanation=msg)
|
|
|
|
if 'source_cgsnapshot_id' in cg:
|
|
source_cgsnapshot_id = cg.get('source_cgsnapshot_id')
|
|
if not uuidutils.is_uuid_like(source_cgsnapshot_id):
|
|
msg = _("The 'source_cgsnapshot_id' attribute must be a uuid.")
|
|
raise exc.HTTPBadRequest(explanation=six.text_type(msg))
|
|
kwargs['source_cgsnapshot_id'] = source_cgsnapshot_id
|
|
|
|
elif 'share_network_id' in cg:
|
|
share_network_id = cg.get('share_network_id')
|
|
if not uuidutils.is_uuid_like(share_network_id):
|
|
msg = _("The 'share_network_id' attribute must be a uuid.")
|
|
raise exc.HTTPBadRequest(explanation=six.text_type(msg))
|
|
kwargs['share_network_id'] = share_network_id
|
|
|
|
try:
|
|
new_cg = self.cg_api.create(context, **kwargs)
|
|
except exception.InvalidCGSnapshot as e:
|
|
raise exc.HTTPConflict(explanation=six.text_type(e))
|
|
except (exception.CGSnapshotNotFound, exception.InvalidInput) as e:
|
|
raise exc.HTTPBadRequest(explanation=six.text_type(e))
|
|
|
|
return self._view_builder.detail(req, dict(new_cg.items()))
|
|
|
|
def _update(self, *args, **kwargs):
|
|
db.consistency_group_update(*args, **kwargs)
|
|
|
|
def _get(self, *args, **kwargs):
|
|
return self.cg_api.get(*args, **kwargs)
|
|
|
|
def _delete(self, context, resource, force=True):
|
|
db.consistency_group_destroy(context.elevated(), resource['id'])
|
|
|
|
@wsgi.Controller.api_version('2.4', '2.6', experimental=True)
|
|
@wsgi.action('os-reset_status')
|
|
def cg_reset_status_legacy(self, req, id, body):
|
|
return self._reset_status(req, id, body)
|
|
|
|
@wsgi.Controller.api_version('2.7', experimental=True)
|
|
@wsgi.action('reset_status')
|
|
def cg_reset_status(self, req, id, body):
|
|
return self._reset_status(req, id, body)
|
|
|
|
@wsgi.Controller.api_version('2.4', '2.6', experimental=True)
|
|
@wsgi.action('os-force_delete')
|
|
def cg_force_delete_legacy(self, req, id, body):
|
|
return self._force_delete(req, id, body)
|
|
|
|
@wsgi.Controller.api_version('2.7', experimental=True)
|
|
@wsgi.action('force_delete')
|
|
def cg_force_delete(self, req, id, body):
|
|
return self._force_delete(req, id, body)
|
|
|
|
|
|
def create_resource():
|
|
return wsgi.Resource(CGController())
|