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
def delete(self, _id): context = t_context.extract_context_from_environ() # TODO(joehuang): get the release of top and bottom t_release = cons.R_MITAKA b_release = cons.R_MITAKA s_ctx = hclient.get_res_routing_ref(context, _id, request.url, cons.ST_CINDER) if not s_ctx: return utils.format_cinder_error( 404, _('Volume %s could not be found.') % _id) if s_ctx['b_url'] == '': return utils.format_cinder_error( 404, _('Bottom Pod endpoint incorrect')) b_headers = hclient.convert_header(t_release, b_release, request.headers) resp = hclient.forward_req(context, 'DELETE', b_headers, s_ctx['b_url'], request.body) response.status = resp.status_code # don't remove the resource routing for delete is async. operation # remove the routing when query is executed but not find # No content in the resp actually return response
def get_one(self, _id): context = t_context.extract_context_from_environ() if _id == 'detail': return {'volumes': self._get_all(context)} # TODO(joehuang): get the release of top and bottom t_release = cons.R_MITAKA b_release = cons.R_MITAKA b_headers = hclient.convert_header(t_release, b_release, request.headers) s_ctx = hclient.get_res_routing_ref(context, _id, request.url, cons.ST_CINDER) if not s_ctx: return utils.format_cinder_error( 404, _('Volume %s could not be found.') % _id) if s_ctx['b_url'] == '': return utils.format_cinder_error( 404, _('Bottom Pod endpoint incorrect')) resp = hclient.forward_req(context, 'GET', b_headers, s_ctx['b_url'], request.body) b_ret_body = jsonutils.loads(resp.content) b_status = resp.status_code response.status = b_status if b_status == 200: if b_ret_body.get('volume') is not None: b_vol_ret = b_ret_body['volume'] ret_vol = hclient.convert_object(b_release, t_release, b_vol_ret, res_type=cons.RT_VOLUME) pod = utils.get_pod_by_top_id(context, _id) if pod: ret_vol['availability_zone'] = pod['az_name'] return {'volume': ret_vol} # resource not find but routing exist, remove the routing if b_status == 404: filters = [{ 'key': 'top_id', 'comparator': 'eq', 'value': _id }, { 'key': 'resource_type', 'comparator': 'eq', 'value': cons.RT_VOLUME }] with context.session.begin(): core.delete_resources(context, models.ResourceRouting, filters) return b_ret_body
def _reset_status(self, context, pod_name, kw): """Update the provided volume with the provided state. :param pod_name: the bottom pod name. :param kw: request body. """ try: status = None if 'status' in kw['os-reset_status']: status = kw['os-reset_status']['status'] attach_status = None if 'attach_status' in kw['os-reset_status']: attach_status = kw['os-reset_status']['attach_status'] migration_status = None if 'migration_status' in kw['os-reset_status']: migration_status = kw['os-reset_status']['migration_status'] except (TypeError, KeyError, ValueError): msg = _('The server has either erred or is incapable of ' 'performing the requested operation.') return utils.format_cinder_error(500, msg) body = {'status': status} if status else {} if attach_status: body.update({'attach_status': attach_status}) if migration_status: body.update({'migration_status': migration_status}) return self._action(context, pod_name, 'os-reset_status', body)
def _attach(self, context, pod_name, kw): """Add attachment metadata. :param pod_name: the bottom pod name. :param kw: request body. """ try: mountpoint = None if 'mountpoint' in kw['os-attach']: mountpoint = kw['os-attach']['mountpoint'] body = {'mountpoint': mountpoint} instance_uuid = None if 'instance_uuid' in kw['os-attach']: instance_uuid = kw['os-attach']['instance_uuid'] host_name = None if 'host_name' in kw['os-attach']: host_name = kw['os-attach']['host_name'] except (KeyError, ValueError, TypeError): msg = _('The server could not comply with the request since ' 'it is either malformed or otherwise incorrect.') return utils.format_cinder_error(400, msg) if instance_uuid is not None: body.update({'instance_uuid': instance_uuid}) if host_name is not None: body.update({'host_name': host_name}) return self._action(context, pod_name, 'os-attach', body)
def get_one(self): """Get all metadata associated with a volume.""" context = t_context.extract_context_from_environ() t_release = cons.R_MITAKA b_release = cons.R_MITAKA b_headers = hclient.convert_header(t_release, b_release, request.headers) try: s_ctx = hclient.get_res_routing_ref(context, self.volume_id, request.url, cons.ST_CINDER) if not s_ctx: return utils.format_cinder_error(500, _('Fail to find resource')) except Exception as e: LOG.exception( _LE('Fail to get metadata for a volume:' '%(volume_id)s' '%(exception)s'), { 'volume_id': self.volume_id, 'exception': e }) return utils.format_cinder_error(500, _('Fail to get metadata')) if s_ctx['b_url'] == '': return utils.format_cinder_error( 500, _('Bottom pod endpoint incorrect')) resp = hclient.forward_req(context, 'GET', b_headers, s_ctx['b_url'], request.body) b_body_ret = jsonutils.loads(resp.content) b_status = resp.status_code response.status = b_status if b_status == 200: if b_body_ret.get('metadata') is not None: b_metadata_ret = b_body_ret['metadata'] vol_ret = hclient.convert_object(b_release, t_release, b_metadata_ret, res_type=cons.RT_VOl_METADATA) return {'metadata': vol_ret} return b_body_ret
def delete(self, key): """Delete the given metadata item from a volume.""" context = t_context.extract_context_from_environ() t_release = cons.R_MITAKA b_release = cons.R_MITAKA try: s_ctx = hclient.get_res_routing_ref(context, self.volume_id, request.url, cons.ST_CINDER) if not s_ctx: return utils.format_cinder_error(404, _('Fail to find resource')) except Exception as e: LOG.exception( _LE('Fail to delete metadata from a volume: ' '%(volume_id)s' '%(exception)s'), { 'volume_id': self.volume_id, 'exception': e }) return utils.format_cinder_error(500, _('Fail to delete metadata')) if s_ctx['b_url'] == '': return utils.format_cinder_error( 500, _('Bottom pod endpoint incorrect')) b_headers = hclient.convert_header(t_release, b_release, request.headers) resp = hclient.forward_req(context, 'DELETE', b_headers, s_ctx['b_url'], request.body) response.status = resp.status_code # don't remove the resource routing for delete is async. operation # remove the routing when query is executed but not found # No content in the resp actually return response
def _extend(self, context, pod_name, kw): """Extend the size of the specified volume. :param pod_name: the bottom pod name. :param kw: request body. """ try: new_size = int(kw['os-extend']['new_size']) except (KeyError, ValueError, TypeError): msg = _("New volume size must be specified as an integer.") return utils.format_cinder_error(400, msg) return self._action(context, pod_name, 'os-extend', {'new_size': new_size})
def _set_image_metadata(self, context, pod_name, kw): """Set a volume's image metadata. :param pod_name: the bottom pod name. :param kw: request body. """ try: metadata = kw['os-set_image_metadata']['metadata'] except (KeyError, TypeError): msg = _("Malformed request body.") return utils.format_cinder_error(400, msg) return self._action(context, pod_name, 'os-set_image_metadata', {'metadata': metadata})
def _unset_image_metadata(self, context, pod_name, kw): """Unset specified keys from volume's image metadata. :param pod_name: the bottom pod name. :param kw: request body. """ try: key = kw['os-unset_image_metadata']['key'] except (KeyError, TypeError): msg = _("Malformed request body.") return utils.format_cinder_error(400, msg) return self._action(context, pod_name, 'os-unset_image_metadata', {'key': key})
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}
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}
def post(self, **kw): """Create volume metadata associated with a volume. :param kw: dictionary of values to be created :returns: created volume metadata """ context = t_context.extract_context_from_environ() if 'metadata' not in kw: return utils.format_cinder_error( 400, _("Missing required element 'metadata' in " "request body.")) try: pod = utils.get_pod_by_top_id(context, self.volume_id) if pod is None: return utils.format_cinder_error( 404, _('Volume %(volume_id)s could not be found.') % {'volume_id': self.volume_id}) t_pod = db_api.get_top_pod(context) if not t_pod: LOG.error(_LE("Top Pod not configured")) return utils.format_cinder_error(500, _('Top Pod not configured')) except Exception as e: LOG.exception( _LE('Fail to create metadata for a volume:' '%(volume_id)s' '%(exception)s'), { 'volume_id': self.volume_id, 'exception': e }) return utils.format_cinder_error(500, _('Fail to create metadata')) t_release = cons.R_MITAKA b_release = cons.R_MITAKA s_ctx = hclient.get_pod_service_ctx(context, request.url, pod['pod_name'], s_type=cons.ST_CINDER) if s_ctx['b_url'] == '': LOG.error( _LE("Bottom pod endpoint incorrect %s") % pod['pod_name']) return utils.format_cinder_error( 500, _('Bottom pod endpoint incorrect')) b_headers = hclient.convert_header(t_release, b_release, request.headers) t_metadata = kw['metadata'] # add or remove key-value in the request for diff. version b_vol_req = hclient.convert_object(t_release, b_release, t_metadata, res_type=cons.RT_VOl_METADATA) b_body = jsonutils.dumps({'metadata': b_vol_req}) resp = hclient.forward_req(context, 'POST', b_headers, s_ctx['b_url'], b_body) b_status = resp.status_code b_body_ret = jsonutils.loads(resp.content) # convert response from the bottom pod # for different version. response.status = b_status if b_status == 200: if b_body_ret.get('metadata') is not None: b_metadata_ret = b_body_ret['metadata'] vol_ret = hclient.convert_object(b_release, t_release, b_metadata_ret, res_type=cons.RT_VOl_METADATA) return {'metadata': vol_ret} return b_body_ret
def put(self, **kw): """Update volume metadata. :param kw: dictionary of values to be updated :returns: updated volume type """ context = t_context.extract_context_from_environ() if 'metadata' not in kw: return utils.format_cinder_error( 400, _("Missing required element 'metadata' in " "request body.")) t_release = cons.R_MITAKA b_release = cons.R_MITAKA try: s_ctx = hclient.get_res_routing_ref(context, self.volume_id, request.url, cons.ST_CINDER) if not s_ctx: return utils.format_cinder_error(404, _('Resource not found')) except Exception as e: LOG.exception( _LE('Fail to update metadata for a volume: ' '%(volume_id)s' '%(exception)s'), { 'volume_id': self.volume_id, 'exception': e }) return utils.format_cinder_error(500, _('Fail to update metadata')) if s_ctx['b_url'] == '': return utils.format_cinder_error( 500, _('Bottom pod endpoint incorrect')) b_headers = hclient.convert_header(t_release, b_release, request.headers) t_metadata = kw['metadata'] # add or remove key/value in the request for diff. version b_vol_req = hclient.convert_object(t_release, b_release, t_metadata, res_type=cons.RT_VOl_METADATA) b_body = jsonutils.dumps({'metadata': b_vol_req}) resp = hclient.forward_req(context, 'PUT', b_headers, s_ctx['b_url'], b_body) b_status = resp.status_code b_body_ret = jsonutils.loads(resp.content) response.status = b_status if b_status == 200: if b_body_ret.get('metadata') is not None: b_metadata_ret = b_body_ret['metadata'] vol_ret = hclient.convert_object(b_release, t_release, b_metadata_ret, res_type=cons.RT_VOl_METADATA) return {'metadata': vol_ret} return b_body_ret
def post(self, **kw): context = t_context.extract_context_from_environ() action_handle = None action_type = None for _type in self.handle_map: if _type in kw: action_handle = self.handle_map[_type] action_type = _type if not action_handle: return utils.format_cinder_error(400, _('Volume action not supported')) volume_mappings = db_api.get_bottom_mappings_by_top_id( context, self.volume_id, constants.RT_VOLUME) if not volume_mappings: return utils.format_cinder_error( 404, _('Volume %(volume_id)s could not be found.') % {'volume_id': self.volume_id}) pod_name = volume_mappings[0][0]['pod_name'] if action_type == 'os-attach': instance_uuid = kw['os-attach'].get('instance_uuid') if instance_uuid is not None: server_mappings = db_api.get_bottom_mappings_by_top_id( context, instance_uuid, constants.RT_SERVER) if not server_mappings: return utils.format_cinder_error(404, _('Server not found')) server_pod_name = server_mappings[0][0]['pod_name'] if server_pod_name != pod_name: LOG.error( _LE('Server %(server)s is in pod %(server_pod)s' 'and volume %(volume)s is in pod' '%(volume_pod)s, which ' 'are not the same.'), { 'server': instance_uuid, 'server_pod': server_pod_name, 'volume': self.volume_id, 'volume_pod': pod_name }) return utils.format_cinder_error( 400, _('Server and volume not in the same pod')) try: resp, body = action_handle(context, pod_name, kw) pecan.response.status = resp.status_code if not body: return pecan.response else: return body except Exception as e: code = 500 message = _('Action %(action)s on volume %(volume_id)s fails') % { 'action': action_type, 'volume_id': self.volume_id } if hasattr(e, 'code'): code = e.code ex_message = str(e) if ex_message: message = ex_message LOG.error(message) return utils.format_cinder_error(code, message)
def put(self, _id, **kw): context = t_context.extract_context_from_environ() # TODO(joehuang): Implement API multi-version compatibility # currently _convert_header and _convert_object are both dummy # functions and API versions are hard coded. After multi-version # compatibility is implemented, API versions will be retrieved from # top and bottom API server, also, _convert_header and _convert_object # will do the real job to convert the request header and body # according to the API versions. t_release = cons.R_MITAKA b_release = cons.R_MITAKA s_ctx = hclient.get_res_routing_ref(context, _id, request.url, cons.ST_CINDER) if not s_ctx: return utils.format_cinder_error( 404, _('Volume %s could not be found.') % _id) if s_ctx['b_url'] == '': return utils.format_cinder_error( 404, _('Bottom Pod endpoint incorrect')) b_headers = hclient.convert_header(t_release, b_release, request.headers) t_vol = kw['volume'] # add or remove key-value in the request for diff. version b_vol_req = hclient.convert_object(t_release, b_release, t_vol, res_type=cons.RT_VOLUME) b_body = jsonutils.dumps({'volume': b_vol_req}) resp = hclient.forward_req(context, 'PUT', b_headers, s_ctx['b_url'], b_body) b_status = resp.status_code b_ret_body = jsonutils.loads(resp.content) response.status = b_status if b_status == 200: if b_ret_body.get('volume') is not None: b_vol_ret = b_ret_body['volume'] ret_vol = hclient.convert_object(b_release, t_release, b_vol_ret, res_type=cons.RT_VOLUME) pod = utils.get_pod_by_top_id(context, _id) if pod: ret_vol['availability_zone'] = pod['az_name'] return {'volume': ret_vol} # resource not found but routing exist, remove the routing if b_status == 404: filters = [{ 'key': 'top_id', 'comparator': 'eq', 'value': _id }, { 'key': 'resource_type', 'comparator': 'eq', 'value': cons.RT_VOLUME }] with context.session.begin(): core.delete_resources(context, models.ResourceRouting, filters) return b_ret_body
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}
def post(self, **kw): context = t_context.extract_context_from_environ() if 'volume' not in kw: return utils.format_cinder_error( 400, _("Missing required element 'volume' in request body.")) az = kw['volume'].get('availability_zone', '') pod, pod_az = az_ag.get_pod_by_az_tenant(context, az_name=az, tenant_id=self.tenant_id) if not pod: LOG.error(_LE("Pod not configured or scheduling failure")) return utils.format_cinder_error( 500, _('Pod not configured or scheduling failure')) t_pod = db_api.get_top_pod(context) if not t_pod: LOG.error(_LE("Top Pod not configured")) return utils.format_cinder_error(500, _('Top Pod not configured')) # TODO(joehuang): get release from pod configuration, # to convert the content # b_release = pod['release'] # t_release = t_pod['release'] t_release = cons.R_MITAKA b_release = cons.R_MITAKA s_ctx = hclient.get_pod_service_ctx(context, request.url, pod['pod_name'], s_type=cons.ST_CINDER) if s_ctx['b_url'] == '': LOG.error( _LE("Bottom Pod endpoint incorrect %s") % pod['pod_name']) return utils.format_cinder_error( 500, _('Bottom Pod endpoint incorrect')) b_headers = hclient.convert_header(t_release, b_release, request.headers) t_vol = kw['volume'] # add or remove key-value in the request for diff. version b_vol_req = hclient.convert_object(t_release, b_release, t_vol, res_type=cons.RT_VOLUME) # convert az to the configured one # remove the AZ parameter to bottom request for default one b_vol_req['availability_zone'] = pod['pod_az_name'] if b_vol_req['availability_zone'] == '': b_vol_req.pop("availability_zone", None) b_body = jsonutils.dumps({'volume': b_vol_req}) resp = hclient.forward_req(context, 'POST', b_headers, s_ctx['b_url'], b_body) b_status = resp.status_code b_ret_body = jsonutils.loads(resp.content) # build routing and convert response from the bottom pod # for different version. response.status = b_status if b_status == 202: if b_ret_body.get('volume') is not None: b_vol_ret = b_ret_body['volume'] try: with context.session.begin(): core.create_resource( context, models.ResourceRouting, { 'top_id': b_vol_ret['id'], 'bottom_id': b_vol_ret['id'], 'pod_id': pod['pod_id'], 'project_id': self.tenant_id, 'resource_type': cons.RT_VOLUME }) except Exception as e: LOG.exception( _LE('Failed to create volume ' 'resource routing' 'top_id: %(top_id)s ,' 'bottom_id: %(bottom_id)s ,' 'pod_id: %(pod_id)s ,' '%(exception)s '), { 'top_id': b_vol_ret['id'], 'bottom_id': b_vol_ret['id'], 'pod_id': pod['pod_id'], 'exception': e }) return utils.format_cinder_error( 500, _('Failed to create volume resource routing')) ret_vol = hclient.convert_object(b_release, t_release, b_vol_ret, res_type=cons.RT_VOLUME) ret_vol['availability_zone'] = pod['az_name'] return {'volume': ret_vol} return b_ret_body
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}
def test_format_cinder_error(self, mock_format_error): output = utils.format_cinder_error(400, 'this is error') self.assertEqual(mock_format_error.return_value, output)