# Copyright 2016 OpenStack Foundation. # 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. import pecan from pecan import expose from pecan import rest from oslo_log import log as logging from oslo_utils import uuidutils import tricircle.common.context as t_context from tricircle.common import exceptions from tricircle.common.i18n import _ from tricircle.common.i18n import _LE from tricircle.common import utils import tricircle.db.api as db_api from tricircle.db import core from tricircle.db import models LOG = logging.getLogger(__name__) class VolumeTypeController(rest.RestController): def __init__(self, tenant_id): self.tenant_id = tenant_id def _metadata_refs(self, metadata_dict, meta_class): metadata_refs = [] if metadata_dict: for k, v in metadata_dict.items(): metadata_ref = meta_class() metadata_ref['key'] = k metadata_ref['value'] = v metadata_refs.append(metadata_ref) return metadata_refs @expose(generic=True, template='json') def post(self, **kw): """Creates volume types.""" context = t_context.extract_context_from_environ() if not context.is_admin: return utils.format_cinder_error( 403, _("Policy doesn't allow volume_extension:types_manage " "to be performed.")) if 'volume_type' not in kw: return utils.format_cinder_error( 400, _("Missing required element 'volume_type' in " "request body.")) projects = [] if self.tenant_id is not None: projects = [self.tenant_id] vol_type = kw['volume_type'] name = vol_type.get('name', None) description = vol_type.get('description') specs = vol_type.get('extra_specs', {}) is_public = vol_type.pop('os-volume-type-access:is_public', True) if name is None or len(name.strip()) == 0: return utils.format_cinder_error( 400, _("Volume type name can not be empty.")) try: utils.check_string_length(name, 'Type name', min_len=1, max_len=255) except exceptions.InvalidInput as e: return utils.format_cinder_error( 400, e.message) if description is not None: try: utils.check_string_length(description, 'Type description', min_len=0, max_len=255) except exceptions.InvalidInput as e: return utils.format_cinder_error(400, e.message) if not utils.is_valid_boolstr(is_public): msg = _("Invalid value '%(is_public)s' for is_public. " "Accepted values: True or False.") % { 'is_public': is_public} return utils.format_cinder_error(400, msg) vol_type['extra_specs'] = specs vol_type['is_public'] = is_public vol_type['id'] = uuidutils.generate_uuid() session = core.get_session() with session.begin(): try: db_api.volume_type_get_by_name(context, vol_type['name'], session) return utils.format_cinder_error( 409, _("Volume Type %(id)s already exists.") % { 'id': vol_type['id']}) except exceptions.VolumeTypeNotFoundByName: pass try: extra_specs = vol_type['extra_specs'] vol_type['extra_specs'] = \ self._metadata_refs(vol_type.get('extra_specs'), models.VolumeTypeExtraSpecs) volume_type_ref = models.VolumeTypes() volume_type_ref.update(vol_type) session.add(volume_type_ref) for project in set(projects): access_ref = models.VolumeTypeProjects() access_ref.update({"volume_type_id": volume_type_ref.id, "project_id": project}) access_ref.save(session=session) except Exception as e: LOG.exception(_LE('Fail to create volume type: %(name)s,' '%(exception)s'), {'name': vol_type['name'], 'exception': e}) return utils.format_cinder_error( 500, _('Fail to create volume type')) vol_type['extra_specs'] = extra_specs return {'volume_type': vol_type} @expose(generic=True, template='json') def get_one(self, _id): """Retrieves single volume type by id. :param _id: id of volume type to be retrieved :returns: retrieved volume type """ context = t_context.extract_context_from_environ() try: result = db_api.volume_type_get(context, _id) except exceptions.VolumeTypeNotFound as e: return utils.format_cinder_error(404, e.message) except Exception as e: LOG.exception(_LE('Volume type not found: %(id)s,' '%(exception)s'), {'id': _id, 'exception': e}) return utils.format_cinder_error( 404, _("Volume type %(id)s could not be found.") % { 'id': _id}) return {'volume_type': result} @expose(generic=True, template='json') def get_all(self): """Get all non-deleted volume_types.""" filters = {} context = t_context.extract_context_from_environ() if not context.is_admin: # Only admin has query access to all volume types filters['is_public'] = True try: list_result = db_api.volume_type_get_all(context, list_result=True, filters=filters) except Exception as e: LOG.exception(_LE('Fail to retrieve volume types: %(exception)s'), {'exception': e}) return utils.format_cinder_error(500, e) return {'volume_types': list_result} @expose(generic=True, template='json') def put(self, _id, **kw): """Update volume type by id. :param _id: id of volume type to be updated :param kw: dictionary of values to be updated :returns: updated volume type """ context = t_context.extract_context_from_environ() if not context.is_admin: return utils.format_cinder_error( 403, _("Policy doesn't allow volume_extension:types_manage " "to be performed.")) if 'volume_type' not in kw: return utils.format_cinder_error( 400, _("Missing required element 'volume_type' in " "request body.")) values = kw['volume_type'] name = values.get('name') description = values.get('description') is_public = values.get('os-volume-type-access:is_public') # Name and description can not be both None. # If name specified, name can not be empty. if name and len(name.strip()) == 0: return utils.format_cinder_error( 400, _("Volume type name can not be empty.")) if name is None and description is None and is_public is None: msg = _("Specify volume type name, description, is_public or " "a combination thereof.") return utils.format_cinder_error(400, msg) if is_public is not None and not utils.is_valid_boolstr(is_public): msg = _("Invalid value '%(is_public)s' for is_public. Accepted " "values: True or False.") % {'is_public': is_public} return utils.format_cinder_error(400, msg) if name: try: utils.check_string_length(name, 'Type name', min_len=1, max_len=255) except exceptions.InvalidInput as e: return utils.format_cinder_error(400, e.message) if description is not None: try: utils.check_string_length(description, 'Type description', min_len=0, max_len=255) except exceptions.InvalidInput as e: return utils.format_cinder_error(400, e.message) try: type_updated = \ db_api.volume_type_update(context, _id, dict(name=name, description=description, is_public=is_public)) except exceptions.VolumeTypeNotFound as e: return utils.format_cinder_error(404, e.message) except exceptions.VolumeTypeExists as e: return utils.format_cinder_error(409, e.message) except exceptions.VolumeTypeUpdateFailed as e: return utils.format_cinder_error(500, e.message) except Exception as e: LOG.exception(_LE('Fail to update volume type: %(name)s,' '%(exception)s'), {'name': values['name'], 'exception': e}) return utils.format_cinder_error( 500, _("Fail to update volume type.")) return {'volume_type': type_updated} @expose(generic=True, template='json') def delete(self, _id): """Marks volume types as deleted. :param _id: id of volume type to be deleted """ context = t_context.extract_context_from_environ() if not context.is_admin: return utils.format_cinder_error( 403, _("Policy doesn't allow volume_extension:types_manage " "to be performed.")) session = core.get_session() with session.begin(): try: db_api.volume_type_get(context, _id, session) except exceptions.VolumeTypeNotFound as e: return utils.format_cinder_error(404, e.message) try: db_api.volume_type_delete(context, _id, session) except Exception as e: LOG.exception(_LE('Fail to update volume type: %(id)s,' '%(exception)s'), {'id': _id, 'exception': e}) return utils.format_cinder_error( 500, _('Fail to delete volume type.')) pecan.response.status = 202 return pecan.response