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(mv.VOLUME_LIST_BOOTABLE): force = utils.get_bool_param('force', req.params) if cascade or force: params = "(cascade: %(c)s, force: %(f)s)" % {'c': cascade, 'f': force} LOG.info("Delete volume with id: %(id)s %(params)s", {'id': id, 'params': params}, context=context) volume = self.volume_api.get(context, id) if force: context.authorize(policy.FORCE_DELETE_POLICY, target_obj=volume) self.volume_api.delete(context, volume, cascade=cascade, force=force) return webob.Response(status_int=202)
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} LOG.info("Delete volume with id: %(id)s %(params)s", {'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 _migrate_volume(self, req, id, body): """Migrate a volume to the specified host.""" context = req.environ['cinder.context'] self.authorize(context, 'migrate_volume') # Not found exception will be handled at the wsgi level volume = self._get(context, id) params = body['os-migrate_volume'] cluster_name, host = common.get_cluster_host(req, params, '3.16') force_host_copy = utils.get_bool_param('force_host_copy', params) lock_volume = utils.get_bool_param('lock_volume', params) self.volume_api.migrate_volume(context, volume, host, cluster_name, force_host_copy, lock_volume) return webob.Response(status_int=202)
def _migrate_volume(self, req, id, body): """Migrate a volume to the specified host.""" context = req.environ['cinder.context'] # Not found exception will be handled at the wsgi level volume = self._get(context, id) self.authorize(context, 'migrate_volume', target_obj=volume) params = body['os-migrate_volume'] cluster_name, host = common.get_cluster_host(req, params, mv.VOLUME_MIGRATE_CLUSTER) force_host_copy = utils.get_bool_param('force_host_copy', params) lock_volume = utils.get_bool_param('lock_volume', params) self.volume_api.migrate_volume(context, volume, host, cluster_name, force_host_copy, lock_volume)
def _get_clusters(self, req, detail): # Let the wsgi middleware convert NotAuthorized exceptions context = self.policy_checker(req, 'get_all') replication_data = req.api_version_request.matches( REPLICATION_DATA_MICRO_VERSION) filters = dict(req.GET) allowed = self.allowed_list_keys if not replication_data: allowed = allowed.difference(self.replication_fields) # Check filters are valid if not allowed.issuperset(filters): invalid_keys = set(filters).difference(allowed) msg = _('Invalid filter keys: %s') % ', '.join(invalid_keys) raise exception.InvalidInput(reason=msg) # Check boolean values for bool_key in ('disabled', 'is_up'): if bool_key in filters: filters[bool_key] = utils.get_bool_param(bool_key, req.GET) # For detailed view we need the services summary information filters['services_summary'] = detail clusters = objects.ClusterList.get_all(context, **filters) return clusters_view.ViewBuilder.list(clusters, detail, replication_data)
def _get_clusters(self, req, detail): # Let the wsgi middleware convert NotAuthorized exceptions context = self.policy_checker(req, 'get_all') replication_data = req.api_version_request.matches( mv.REPLICATION_CLUSTER) filters = dict(req.GET) allowed = self.allowed_list_keys if not replication_data: allowed = allowed.difference(self.replication_fields) # Check filters are valid if not allowed.issuperset(filters): invalid_keys = set(filters).difference(allowed) msg = _('Invalid filter keys: %s') % ', '.join(invalid_keys) raise exception.InvalidInput(reason=msg) # Check boolean values for bool_key in ('disabled', 'is_up'): if bool_key in filters: filters[bool_key] = utils.get_bool_param(bool_key, req.GET) # For detailed view we need the services summary information filters['services_summary'] = detail clusters = objects.ClusterList.get_all(context, **filters) return clusters_view.ViewBuilder.list(clusters, detail, replication_data)
def detail(self, request, group): """Detailed view of a single group.""" group_ref = { 'group': { 'id': group.id, 'status': group.status, 'availability_zone': group.availability_zone, 'created_at': group.created_at, 'name': group.name, 'description': group.description, 'group_type': group.group_type_id, 'volume_types': [v_type.id for v_type in group.volume_types], } } req_version = request.api_version_request # Add group_snapshot_id and source_group_id if min version is greater # than or equal to 3.14. if req_version.matches("3.14", None): group_ref['group']['group_snapshot_id'] = group.group_snapshot_id group_ref['group']['source_group_id'] = group.source_group_id # Add volumes if min version is greater than or equal to 3.25. if req_version.matches("3.25", None): if utils.get_bool_param('list_volume', request.params): group_ref['group']['volumes'] = [volume.id for volume in group.volumes] # Add replication_status if min version is greater than or equal # to 3.38. if req_version.matches("3.38", None): group_ref['group']['replication_status'] = group.replication_status return group_ref
def _migrate_volume(self, req, id, body): """Migrate a volume to the specified host.""" context = req.environ['cinder.context'] self.authorize(context, 'migrate_volume') # Not found exception will be handled at the wsgi level volume = self._get(context, id) params = body['os-migrate_volume'] try: host = params['host'] except KeyError: raise exc.HTTPBadRequest(explanation=_("Must specify 'host'.")) force_host_copy = utils.get_bool_param('force_host_copy', params) lock_volume = utils.get_bool_param('lock_volume', params) self.volume_api.migrate_volume(context, volume, host, force_host_copy, lock_volume) return webob.Response(status_int=202)
def fake_volume_create(self, context, size, name, description, snapshot=None, group_id=None, **param): vol = create_volume(DEFAULT_VOL_ID) vol['size'] = size vol['display_name'] = name vol['display_description'] = description source_volume = param.get('source_volume') or {} vol['source_volid'] = source_volume.get('id') vol['bootable'] = False vol['volume_attachment'] = [] vol['multiattach'] = utils.get_bool_param('multiattach', param) try: vol['snapshot_id'] = snapshot['id'] except (KeyError, TypeError): vol['snapshot_id'] = None vol['availability_zone'] = param.get('availability_zone', 'fakeaz') if group_id: vol['group_id'] = group_id return vol
def delete(self, req, id): """Deletes an existing qos specs.""" context = req.environ['cinder.context'] authorize(context) # Convert string to bool type in strict manner force = utils.get_bool_param('force', req.params) LOG.debug("Delete qos_spec: %(id)s, force: %(force)s", {'id': id, 'force': force}) try: qos_specs.delete(context, id, force) notifier_info = dict(id=id) rpc.get_notifier('QoSSpecs').info(context, 'qos_specs.delete', notifier_info) except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.delete', notifier_err) # Not found exception will be handled at the wsgi level raise except exception.QoSSpecsInUse as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.delete', notifier_err) if force: msg = _('Failed to disassociate qos specs.') raise webob.exc.HTTPInternalServerError(explanation=msg) msg = _('Qos specs still in use.') raise webob.exc.HTTPBadRequest(explanation=msg) return webob.Response(status_int=202)
def delete(self, req, id): """Deletes an existing qos specs.""" context = req.environ['cinder.context'] context.authorize(policy.DELETE_POLICY) # Convert string to bool type in strict manner force = utils.get_bool_param('force', req.params) LOG.debug("Delete qos_spec: %(id)s, force: %(force)s", { 'id': id, 'force': force }) try: qos_specs.delete(context, id, force) notifier_info = dict(id=id) rpc.get_notifier('QoSSpecs').info(context, 'qos_specs.delete', notifier_info) except exception.QoSSpecsNotFound as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.delete', notifier_err) # Not found exception will be handled at the wsgi level raise except exception.QoSSpecsInUse as err: notifier_err = dict(id=id, error_message=err) self._notify_qos_specs_error(context, 'qos_specs.delete', notifier_err) if force: msg = _('Failed to disassociate qos specs.') raise webob.exc.HTTPInternalServerError(explanation=msg) msg = _('Qos specs still in use.') raise webob.exc.HTTPBadRequest(explanation=msg) return webob.Response(status_int=http_client.ACCEPTED)
def _prepare_params(self, ctxt, params, allowed): if not allowed.issuperset(params): invalid_keys = set(params).difference(allowed) msg = _('Invalid filter keys: %s') % ', '.join(invalid_keys) raise exception.InvalidInput(reason=msg) if params.get('binary') not in (None, 'cinder-volume', 'cinder-scheduler'): msg = _('binary must be empty or set to cinder-volume or ' 'cinder-scheduler') raise exception.InvalidInput(reason=msg) for boolean in ('disabled', 'is_up'): if params.get(boolean) is not None: params[boolean] = utils.get_bool_param(boolean, params) resource_type = params.get('resource_type') if resource_type: resource_type = resource_type.title() types = cleanable.CinderCleanableObject.cleanable_resource_types if resource_type not in types: msg = (_('Resource type %s not valid, must be ') % resource_type) msg = utils.build_or_str(types, msg + '%s.') raise exception.InvalidInput(reason=msg) params['resource_type'] = resource_type resource_id = params.get('resource_id') if resource_id: if not uuidutils.is_uuid_like(resource_id): msg = (_('Resource ID must be a UUID, and %s is not.') % resource_id) raise exception.InvalidInput(reason=msg) # If we have the resource type but we don't have where it is # located, we get it from the DB to limit the distribution of the # request by the scheduler, otherwise it will be distributed to all # the services. location_keys = {'service_id', 'cluster_name', 'host'} if not location_keys.intersection(params): workers = db.worker_get_all(ctxt, resource_id=resource_id, binary=params.get('binary'), resource_type=resource_type) if len(workers) == 0: msg = (_('There is no resource with UUID %s pending ' 'cleanup.'), resource_id) raise exception.InvalidInput(reason=msg) if len(workers) > 1: msg = (_('There are multiple resources with UUID %s ' 'pending cleanup. Please be more specific.'), resource_id) raise exception.InvalidInput(reason=msg) worker = workers[0] params.update(service_id=worker.service_id, resource_type=worker.resource_type) return params
def _update(self, req, id, body): # Update description for a given volume type. context = req.environ['cinder.context'] authorize(context) self.assert_valid_body(body, 'volume_type') vol_type = body['volume_type'] description = vol_type.get('description') name = vol_type.get('name') is_public = vol_type.get('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: msg = _("Volume type name can not be empty.") raise webob.exc.HTTPBadRequest(explanation=msg) 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.") raise webob.exc.HTTPBadRequest(explanation=msg) if is_public is not None: is_public = utils.get_bool_param('is_public', vol_type) if name: utils.check_string_length(name, 'Type name', min_length=1, max_length=255) if description is not None: utils.check_string_length(description, 'Type description', min_length=0, max_length=255) try: volume_types.update(context, id, name, description, is_public=is_public) # Get the updated vol_type = volume_types.get_volume_type(context, id) req.cache_resource(vol_type, name='types') self._notify_volume_type_info( context, 'volume_type.update', vol_type) except exception.VolumeTypeNotFound as err: self._notify_volume_type_error( context, 'volume_type.update', err, id=id) # Not found exception will be handled at the wsgi level raise except exception.VolumeTypeExists as err: self._notify_volume_type_error( context, 'volume_type.update', err, volume_type=vol_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.VolumeTypeUpdateFailed as err: self._notify_volume_type_error( context, 'volume_type.update', err, volume_type=vol_type) raise webob.exc.HTTPInternalServerError( explanation=six.text_type(err)) return self._view_builder.show(req, vol_type)
def update(self, req, id, body): # Update description for a given group type. context = req.environ['cinder.context'] self._check_policy(context) self.assert_valid_body(body, 'group_type') grp_type = body['group_type'] description = grp_type.get('description') name = grp_type.get('name') is_public = grp_type.get('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: msg = _("Group type name can not be empty.") raise webob.exc.HTTPBadRequest(explanation=msg) if name is None and description is None and is_public is None: msg = _("Specify group type name, description or " "a combination thereof.") raise webob.exc.HTTPBadRequest(explanation=msg) if is_public is not None: is_public = utils.get_bool_param('is_public', grp_type) if name: utils.check_string_length(name, 'Type name', min_length=1, max_length=255) if description is not None: utils.check_string_length(description, 'Type description', min_length=0, max_length=255) try: group_types.update(context, id, name, description, is_public=is_public) # Get the updated grp_type = group_types.get_group_type(context, id) req.cache_resource(grp_type, name='group_types') self._notify_group_type_info( context, 'group_type.update', grp_type) except exception.GroupTypeNotFound as err: self._notify_group_type_error( context, 'group_type.update', err, id=id) raise webob.exc.HTTPNotFound(explanation=six.text_type(err)) except exception.GroupTypeExists as err: self._notify_group_type_error( context, 'group_type.update', err, group_type=grp_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.GroupTypeUpdateFailed as err: self._notify_group_type_error( context, 'group_type.update', err, group_type=grp_type) raise webob.exc.HTTPInternalServerError( explanation=six.text_type(err)) return self._view_builder.show(req, grp_type)
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 show_count = False if req_version.matches( mv.SUPPORT_COUNT_INFO) and 'with_count' in filters: show_count = utils.get_bool_param('with_count', filters) filters.pop('with_count') self._process_volume_filtering(context=context, filters=filters, req_version=req_version) # 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') self._handle_time_comparison_filters(filters) strict = req.api_version_request.matches(mv.VOLUME_LIST_BOOTABLE, 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.copy(), viewable_admin_meta=True, offset=offset) total_count = None if show_count: total_count = self.volume_api.calculate_resource_count( context, 'volume', filters) for volume in volumes: api_utils.add_visible_admin_metadata(volume) req.cache_db_volumes(volumes.objects) if is_detail: volumes = self._view_builder.detail_list(req, volumes, total_count) else: volumes = self._view_builder.summary_list(req, volumes, total_count) return volumes
def _create(self, req, body): """Creates a new volume type.""" context = req.environ['cinder.context'] context.authorize(policy.MANAGE_POLICY) self.assert_valid_body(body, 'volume_type') vol_type = body['volume_type'] name = vol_type.get('name', None) description = vol_type.get('description') specs = vol_type.get('extra_specs', {}) utils.validate_dictionary_string_length(specs) is_public = utils.get_bool_param('os-volume-type-access:is_public', vol_type, True) if name is None or len(name.strip()) == 0: msg = _("Volume type name can not be empty.") raise webob.exc.HTTPBadRequest(explanation=msg) utils.check_string_length(name, 'Type name', min_length=1, max_length=255) if description is not None: utils.check_string_length(description, 'Type description', min_length=0, max_length=255) try: volume_types.create(context, name, specs, is_public, description=description) vol_type = volume_types.get_volume_type_by_name(context, name) req.cache_resource(vol_type, name='types') self._notify_volume_type_info(context, 'volume_type.create', vol_type) except exception.VolumeTypeExists as err: self._notify_volume_type_error(context, 'volume_type.create', err, volume_type=vol_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.VolumeTypeNotFoundByName as err: self._notify_volume_type_error(context, 'volume_type.create', err, name=name) # Not found exception will be handled at the wsgi level raise return self._view_builder.show(req, vol_type)
def get_pools(self, req): """List all active pools in scheduler.""" context = req.environ['cinder.context'] authorize(context, 'get_pools') # TODO(zhiteng) Add filters support detail = utils.get_bool_param('detail', req.params) pools = self.scheduler_api.get_pools(context, filters=None) return self._view_builder.pools(req, pools, detail)
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 show_count = False if req_version.matches( mv.SUPPORT_COUNT_INFO) and 'with_count' in filters: show_count = utils.get_bool_param('with_count', filters) filters.pop('with_count') self._process_volume_filtering(context=context, filters=filters, req_version=req_version) # 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') strict = req.api_version_request.matches( mv.VOLUME_LIST_BOOTABLE, 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.copy(), viewable_admin_meta=True, offset=offset) total_count = None if show_count: total_count = self.volume_api.calculate_resource_count( context, 'volume', filters) 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, total_count) else: volumes = self._view_builder.summary_list( req, volumes, total_count) return volumes
def delete(self, req, id): """Delete a volume.""" context = req.environ['cinder.context'] cascade = utils.get_bool_param('cascade', req.params) LOG.info("Delete volume with id: %s", id) # Not found exception will be handled at the wsgi level volume = self.volume_api.get(context, id) self.volume_api.delete(context, volume, cascade=cascade) return webob.Response(status_int=http_client.ACCEPTED)
def _items(self, req, is_detail=True): """Returns a list of snapshots, transformed through view builder.""" context = req.environ['cinder.context'] req_version = req.api_version_request # Pop out non search_opts and create local variables search_opts = req.GET.copy() sort_keys, sort_dirs = common.get_sort_params(search_opts) marker, limit, offset = common.get_pagination_params(search_opts) req_version = req.api_version_request show_count = False if req_version.matches( mv.SUPPORT_COUNT_INFO) and 'with_count' in search_opts: show_count = utils.get_bool_param('with_count', search_opts) search_opts.pop('with_count') # process filters self._process_snapshot_filtering(context=context, filters=search_opts, req_version=req_version) # process snapshot filters to appropriate formats if required self._format_snapshot_filter_options(search_opts) req_version = req.api_version_request if req_version.matches(mv.SNAPSHOT_SORT, None) and 'name' in sort_keys: sort_keys[sort_keys.index('name')] = 'display_name' # NOTE(thingee): v3 API allows name instead of display_name if 'name' in search_opts: search_opts['display_name'] = search_opts.pop('name') snapshots = self.volume_api.get_all_snapshots( context, search_opts=search_opts.copy(), marker=marker, limit=limit, sort_keys=sort_keys, sort_dirs=sort_dirs, offset=offset) total_count = None if show_count: total_count = self.volume_api.calculate_resource_count( context, 'snapshot', search_opts) req.cache_db_snapshots(snapshots.objects) if is_detail: snapshots = self._view_builder.detail_list(req, snapshots.objects, total_count) else: snapshots = self._view_builder.summary_list(req, snapshots.objects, total_count) return snapshots
def delete(self, req, id): """Delete a volume.""" context = req.environ['cinder.context'] cascade = utils.get_bool_param('cascade', req.params) LOG.info(_LI("Delete volume with id: %s"), id) # Not found exception will be handled at the wsgi level volume = self.volume_api.get(context, id) self.volume_api.delete(context, volume, cascade=cascade) return webob.Response(status_int=202)
def create(self, req, body): """Creates a new group type.""" context = req.environ['cinder.context'] self._check_policy(context) self.assert_valid_body(body, 'group_type') grp_type = body['group_type'] name = grp_type.get('name', None) description = grp_type.get('description') specs = grp_type.get('group_specs', {}) is_public = utils.get_bool_param('is_public', grp_type, True) if name is None or len(name.strip()) == 0: msg = _("Group type name can not be empty.") raise webob.exc.HTTPBadRequest(explanation=msg) utils.check_string_length(name, 'Type name', min_length=1, max_length=255) if description is not None: utils.check_string_length(description, 'Type description', min_length=0, max_length=255) try: group_types.create(context, name, specs, is_public, description=description) grp_type = group_types.get_group_type_by_name(context, name) req.cache_resource(grp_type, name='group_types') self._notify_group_type_info(context, 'group_type.create', grp_type) except exception.GroupTypeExists as err: self._notify_group_type_error(context, 'group_type.create', err, group_type=grp_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.GroupTypeNotFoundByName as err: self._notify_group_type_error(context, 'group_type.create', err, name=name) raise webob.exc.HTTPNotFound(explanation=err.msg) return self._view_builder.show(req, grp_type)
def _items(self, req, is_detail=True): """Returns a list of snapshots, transformed through view builder.""" context = req.environ['cinder.context'] req_version = req.api_version_request # Pop out non search_opts and create local variables search_opts = req.GET.copy() sort_keys, sort_dirs = common.get_sort_params(search_opts) marker, limit, offset = common.get_pagination_params(search_opts) req_version = req.api_version_request show_count = False if req_version.matches( mv.SUPPORT_COUNT_INFO) and 'with_count' in search_opts: show_count = utils.get_bool_param('with_count', search_opts) search_opts.pop('with_count') # process filters self._process_snapshot_filtering(context=context, filters=search_opts, req_version=req_version) # process snapshot filters to appropriate formats if required self._format_snapshot_filter_options(search_opts) req_version = req.api_version_request if req_version.matches(mv.SNAPSHOT_SORT, None) and 'name' in sort_keys: sort_keys[sort_keys.index('name')] = 'display_name' # NOTE(thingee): v3 API allows name instead of display_name if 'name' in search_opts: search_opts['display_name'] = search_opts.pop('name') snapshots = self.volume_api.get_all_snapshots( context, search_opts=search_opts.copy(), marker=marker, limit=limit, sort_keys=sort_keys, sort_dirs=sort_dirs, offset=offset) total_count = None if show_count: total_count = self.volume_api.calculate_resource_count( context, 'snapshot', search_opts) req.cache_db_snapshots(snapshots.objects) if is_detail: snapshots = self._view_builder.detail_list(req, snapshots.objects, total_count) else: snapshots = self._view_builder.summary_list( req, snapshots.objects, total_count) return snapshots
def delete(self, req, id): """Delete a volume.""" context = req.environ['cinder.context'] cascade = utils.get_bool_param('cascade', req.params) LOG.info(_LI("Delete volume with id: %s"), id, context=context) try: volume = self.volume_api.get(context, id) self.volume_api.delete(context, volume, cascade=cascade) except exception.VolumeNotFound as error: raise exc.HTTPNotFound(explanation=error.msg) return webob.Response(status_int=202)
def _create(self, req, body): """Creates a new volume type.""" context = req.environ['cinder.context'] authorize(context) self.assert_valid_body(body, 'volume_type') vol_type = body['volume_type'] name = vol_type.get('name', None) description = vol_type.get('description') specs = vol_type.get('extra_specs', {}) utils.validate_dictionary_string_length(specs) is_public = utils.get_bool_param('os-volume-type-access:is_public', vol_type, True) if name is None or len(name.strip()) == 0: msg = _("Volume type name can not be empty.") raise webob.exc.HTTPBadRequest(explanation=msg) utils.check_string_length(name, 'Type name', min_length=1, max_length=255) if description is not None: utils.check_string_length(description, 'Type description', min_length=0, max_length=255) try: volume_types.create(context, name, specs, is_public, description=description) vol_type = volume_types.get_volume_type_by_name(context, name) req.cache_resource(vol_type, name='types') self._notify_volume_type_info( context, 'volume_type.create', vol_type) except exception.VolumeTypeExists as err: self._notify_volume_type_error( context, 'volume_type.create', err, volume_type=vol_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.VolumeTypeNotFoundByName as err: self._notify_volume_type_error( context, 'volume_type.create', err, name=name) # Not found exception will be handled at the wsgi level raise return self._view_builder.show(req, vol_type)
def stub_volume_create(self, context, size, name, description, snapshot=None, **param): vol = stub_volume(DEFAULT_VOL_ID) vol["size"] = size vol["display_name"] = name vol["display_description"] = description source_volume = param.get("source_volume") or {} vol["source_volid"] = source_volume.get("id") vol["bootable"] = False vol["volume_attachment"] = [] vol["multiattach"] = utils.get_bool_param("multiattach", param) try: vol["snapshot_id"] = snapshot["id"] except (KeyError, TypeError): vol["snapshot_id"] = None vol["availability_zone"] = param.get("availability_zone", "fakeaz") return vol
def _format_snapshot_filter_options(self, search_opts): """Convert valid filter options to correct expected format""" # Get the dict object out of queried metadata # convert metadata query value from string to dict if 'metadata' in search_opts.keys(): try: search_opts['metadata'] = ast.literal_eval( search_opts['metadata']) except (ValueError, SyntaxError): LOG.debug('Could not evaluate value %s, assuming string', search_opts['metadata']) if 'use_quota' in search_opts: search_opts['use_quota'] = utils.get_bool_param( 'use_quota', search_opts)
def fake_volume_create(self, context, size, name, description, snapshot=None, **param): vol = create_fake_volume(DEFAULT_VOL_ID) vol['size'] = size vol['display_name'] = name vol['display_description'] = description source_volume = param.get('source_volume') or {} vol['source_volid'] = source_volume.get('id') vol['bootable'] = False vol['volume_attachment'] = [] vol['multiattach'] = utils.get_bool_param('multiattach', param) try: vol['snapshot_id'] = snapshot['id'] except (KeyError, TypeError): vol['snapshot_id'] = None vol['availability_zone'] = param.get('availability_zone', 'fakeaz') return vol
def get_pools(self, req): """List all active pools in scheduler.""" context = req.environ['cinder.context'] authorize(context, 'get_pools') detail = utils.get_bool_param('detail', req.params) req_version = req.api_version_request if req_version.matches(GET_POOL_NAME_FILTER_MICRO_VERSION): filters = req.params.copy() filters.pop('detail', None) pools = self.scheduler_api.get_pools(context, filters=filters) else: pools = self.scheduler_api.get_pools(context, filters=None) return self._view_builder.pools(req, pools, detail)
def create(self, req, body): """Creates a new group type.""" context = req.environ['cinder.context'] self._check_policy(context) self.assert_valid_body(body, 'group_type') grp_type = body['group_type'] name = grp_type.get('name', None) description = grp_type.get('description') specs = grp_type.get('group_specs', {}) is_public = utils.get_bool_param('is_public', grp_type, True) if name is None or len(name.strip()) == 0: msg = _("Group type name can not be empty.") raise webob.exc.HTTPBadRequest(explanation=msg) utils.check_string_length(name, 'Type name', min_length=1, max_length=255) if description is not None: utils.check_string_length(description, 'Type description', min_length=0, max_length=255) try: group_types.create(context, name, specs, is_public, description=description) grp_type = group_types.get_group_type_by_name(context, name) req.cache_resource(grp_type, name='group_types') self._notify_group_type_info( context, 'group_type.create', grp_type) except exception.GroupTypeExists as err: self._notify_group_type_error( context, 'group_type.create', err, group_type=grp_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.GroupTypeNotFoundByName as err: self._notify_group_type_error( context, 'group_type.create', err, name=name) raise webob.exc.HTTPNotFound(explanation=err.msg) return self._view_builder.show(req, grp_type)
def get_pools(self, req): """List all active pools in scheduler.""" context = req.environ['cinder.context'] authorize(context, 'get_pools') detail = utils.get_bool_param('detail', req.params) req_version = req.api_version_request filters = req.params.copy() filters.pop('detail', None) self._process_pool_filtering(context=context, filters=filters, req_version=req_version) pools = self.scheduler_api.get_pools(context, filters=filters) return self._view_builder.pools(req, pools, detail)
def _get_backups(self, req, is_detail): """Returns a list of backups, transformed through view builder.""" context = req.environ['cinder.context'] filters = req.params.copy() req_version = req.api_version_request marker, limit, offset = common.get_pagination_params(filters) sort_keys, sort_dirs = common.get_sort_params(filters) show_count = False if req_version.matches( mv.SUPPORT_COUNT_INFO) and 'with_count' in filters: show_count = utils.get_bool_param('with_count', filters) filters.pop('with_count') self._convert_sort_name(req_version, sort_keys) self._process_backup_filtering(context=context, filters=filters, req_version=req_version) if 'name' in filters: filters['display_name'] = filters.pop('name') backups = self.backup_api.get_all( context, search_opts=filters.copy(), marker=marker, limit=limit, offset=offset, sort_keys=sort_keys, sort_dirs=sort_dirs, ) total_count = None if show_count: total_count = self.volume_api.calculate_resource_count( context, 'backup', filters) req.cache_db_backups(backups.objects) if is_detail: backups = self._view_builder.detail_list(req, backups.objects, total_count) else: backups = self._view_builder.summary_list(req, backups.objects, total_count) return backups
def show(self, req, id): """Show quota for a particular tenant This works for hierarchical and non-hierarchical projects. For hierarchical projects admin of current project, immediate parent of the project or the CLOUD admin are able to perform a show. :param req: request :param id: target project id that needs to be shown """ context = req.environ['cinder.context'] authorize_show(context) params = req.params target_project_id = id if not hasattr(params, '__call__') and 'usage' in params: usage = utils.get_bool_param('usage', params) else: usage = False if QUOTAS.using_nested_quotas(): # With hierarchical projects, only the admin of the current project # or the root project has privilege to perform quota show # operations. target_project = quota_utils.get_project_hierarchy( context, target_project_id) context_project = quota_utils.get_project_hierarchy( context, context.project_id, subtree_as_ids=True, is_admin_project=context.is_admin) self._authorize_show(context_project, target_project) try: sqlalchemy_api.authorize_project_context(context, target_project_id) except exception.NotAuthorized: raise webob.exc.HTTPForbidden() quotas = self._get_quotas(context, target_project_id, usage) return self._format_quota_set(target_project_id, quotas)
def show(self, req, id): """Show quota for a particular tenant :param req: request :param id: target project id that needs to be shown """ context = req.environ['cinder.context'] params = req.params target_project_id = id context.authorize(policy.SHOW_POLICY, target={'project_id': target_project_id}) if not hasattr(params, '__call__') and 'usage' in params: usage = utils.get_bool_param('usage', params) else: usage = False quotas = self._get_quotas(context, target_project_id, usage) return self._format_quota_set(target_project_id, quotas)
def detail(self, request, group): """Detailed view of a single group.""" context = request.environ['cinder.context'] group_ref = { 'group': { 'id': group.id, 'status': group.status, 'availability_zone': group.availability_zone, 'created_at': group.created_at, 'name': group.name, 'description': group.description, 'group_type': group.group_type_id, 'volume_types': [v_type.id for v_type in group.volume_types], } } req_version = request.api_version_request # Add group_snapshot_id and source_group_id if min version is greater # than or equal to GROUP_SNAPSHOTS. if req_version.matches(mv.GROUP_SNAPSHOTS, None): group_ref['group']['group_snapshot_id'] = group.group_snapshot_id group_ref['group']['source_group_id'] = group.source_group_id # Add volumes if min version is greater than or equal to # GROUP_VOLUME_LIST. if req_version.matches(mv.GROUP_VOLUME_LIST, None): if utils.get_bool_param('list_volume', request.params): group_ref['group']['volumes'] = [ volume.id for volume in group.volumes ] # Add replication_status if min version is greater than or equal # to GROUP_REPLICATION. if req_version.matches(mv.GROUP_REPLICATION, None): group_ref['group']['replication_status'] = group.replication_status if req_version.matches(mv.GROUP_PROJECT_ID, None): if context.authorize(policy.GROUP_ATTRIBUTES_POLICY, fatal=False): group_ref['group']['project_id'] = group.project_id return group_ref
def _get_backups(self, req, is_detail): """Returns a list of backups, transformed through view builder.""" context = req.environ['cinder.context'] filters = req.params.copy() req_version = req.api_version_request marker, limit, offset = common.get_pagination_params(filters) sort_keys, sort_dirs = common.get_sort_params(filters) show_count = False if req_version.matches( mv.SUPPORT_COUNT_INFO) and 'with_count' in filters: show_count = utils.get_bool_param('with_count', filters) filters.pop('with_count') self._convert_sort_name(req_version, sort_keys) self._process_backup_filtering(context=context, filters=filters, req_version=req_version) if 'name' in filters: filters['display_name'] = filters.pop('name') backups = self.backup_api.get_all(context, search_opts=filters.copy(), marker=marker, limit=limit, offset=offset, sort_keys=sort_keys, sort_dirs=sort_dirs, ) total_count = None if show_count: total_count = self.volume_api.calculate_resource_count( context, 'backup', filters) req.cache_db_backups(backups.objects) if is_detail: backups = self._view_builder.detail_list(req, backups.objects, total_count) else: backups = self._view_builder.summary_list(req, backups.objects, total_count) return backups
def get_pools(self, req): """List all active pools in scheduler.""" context = req.environ['cinder.context'] authorize(context, 'get_pools') detail = utils.get_bool_param('detail', req.params) req_version = req.api_version_request filters = req.params.copy() filters.pop('detail', None) self._process_pool_filtering(context=context, filters=filters, req_version=req_version) if not req_version.matches(GET_POOL_VOLUME_TYPE_FILTER_MICRO_VERSION): filters.pop('volume_type', None) pools = self.scheduler_api.get_pools(context, filters=filters) return self._view_builder.pools(req, pools, detail)
def detail(self, request, group): """Detailed view of a single group.""" context = request.environ['cinder.context'] group_ref = { 'group': { 'id': group.id, 'status': group.status, 'availability_zone': group.availability_zone, 'created_at': group.created_at, 'name': group.name, 'description': group.description, 'group_type': group.group_type_id, 'volume_types': [v_type.id for v_type in group.volume_types], } } req_version = request.api_version_request # Add group_snapshot_id and source_group_id if min version is greater # than or equal to GROUP_SNAPSHOTS. if req_version.matches(mv.GROUP_SNAPSHOTS, None): group_ref['group']['group_snapshot_id'] = group.group_snapshot_id group_ref['group']['source_group_id'] = group.source_group_id # Add volumes if min version is greater than or equal to # GROUP_VOLUME_LIST. if req_version.matches(mv.GROUP_VOLUME_LIST, None): if utils.get_bool_param('list_volume', request.params): group_ref['group']['volumes'] = [volume.id for volume in group.volumes] # Add replication_status if min version is greater than or equal # to GROUP_REPLICATION. if req_version.matches(mv.GROUP_REPLICATION, None): group_ref['group']['replication_status'] = group.replication_status if req_version.matches(mv.GROUP_GROUPSNAPSHOT_PROJECT_ID, None): if context.authorize(policy.GROUP_ATTRIBUTES_POLICY, fatal=False): group_ref['group']['project_id'] = group.project_id return group_ref
def _volume_upload_image(self, req, id, body): """Uploads the specified volume to image service.""" context = req.environ['cinder.context'] params = body['os-volume_upload_image'] req_version = req.api_version_request if not params.get("image_name"): msg = _("No image_name was specified in request.") raise webob.exc.HTTPBadRequest(explanation=msg) force = params.get('force', 'False') try: force = strutils.bool_from_string(force, strict=True) except ValueError as error: err_msg = encodeutils.exception_to_unicode(error) msg = _("Invalid value for 'force': '%s'") % err_msg raise webob.exc.HTTPBadRequest(explanation=msg) try: volume = self.volume_api.get(context, id) except exception.VolumeNotFound as error: raise webob.exc.HTTPNotFound(explanation=error.msg) authorize(context, "upload_image") # check for valid disk-format disk_format = params.get("disk_format", "raw") if not image_utils.validate_disk_format(disk_format): msg = _("Invalid disk-format '%(disk_format)s' is specified. " "Allowed disk-formats are %(allowed_disk_formats)s.") % { "disk_format": disk_format, "allowed_disk_formats": ", ".join( image_utils.VALID_DISK_FORMATS) } raise webob.exc.HTTPBadRequest(explanation=msg) image_metadata = { "container_format": params.get("container_format", "bare"), "disk_format": disk_format, "name": params["image_name"] } if req_version >= api_version_request.APIVersionRequest('3.1'): image_metadata['visibility'] = params.get('visibility', 'private') image_metadata['protected'] = params.get('protected', 'False') if image_metadata['visibility'] == 'public': authorize(context, 'upload_public') if CONF.glance_api_version != 2: # Replace visibility with is_public for Glance V1 image_metadata['is_public'] = ( image_metadata['visibility'] == 'public') image_metadata.pop('visibility', None) image_metadata['protected'] = (utils.get_bool_param( 'protected', image_metadata)) try: response = self.volume_api.copy_volume_to_image( context, volume, image_metadata, force) except exception.InvalidVolume as error: raise webob.exc.HTTPBadRequest(explanation=error.msg) except ValueError as error: raise webob.exc.HTTPBadRequest(explanation=six.text_type(error)) except messaging.RemoteError as error: msg = "%(err_type)s: %(err_msg)s" % { 'err_type': error.exc_type, 'err_msg': error.value } raise webob.exc.HTTPBadRequest(explanation=msg) except Exception as error: raise webob.exc.HTTPBadRequest(explanation=six.text_type(error)) return {'os-volume_upload_image': response}
def _volume_upload_image(self, req, id, body): """Uploads the specified volume to image service.""" context = req.environ["cinder.context"] params = body["os-volume_upload_image"] req_version = req.api_version_request if not params.get("image_name"): msg = _("No image_name was specified in request.") raise webob.exc.HTTPBadRequest(explanation=msg) force = params.get("force", "False") try: force = strutils.bool_from_string(force, strict=True) except ValueError as error: err_msg = encodeutils.exception_to_unicode(error) msg = _("Invalid value for 'force': '%s'") % err_msg raise webob.exc.HTTPBadRequest(explanation=msg) try: volume = self.volume_api.get(context, id) except exception.VolumeNotFound as error: raise webob.exc.HTTPNotFound(explanation=error.msg) authorize(context, "upload_image") # check for valid disk-format disk_format = params.get("disk_format", "raw") if not image_utils.validate_disk_format(disk_format): msg = _( "Invalid disk-format '%(disk_format)s' is specified. " "Allowed disk-formats are %(allowed_disk_formats)s." ) % {"disk_format": disk_format, "allowed_disk_formats": ", ".join(image_utils.VALID_DISK_FORMATS)} raise webob.exc.HTTPBadRequest(explanation=msg) image_metadata = { "container_format": params.get("container_format", "bare"), "disk_format": disk_format, "name": params["image_name"], } if req_version >= api_version_request.APIVersionRequest("3.1"): image_metadata["visibility"] = params.get("visibility", "private") image_metadata["protected"] = params.get("protected", "False") if image_metadata["visibility"] == "public": authorize(context, "upload_public") if CONF.glance_api_version != 2: # Replace visibility with is_public for Glance V1 image_metadata["is_public"] = image_metadata["visibility"] == "public" image_metadata.pop("visibility", None) image_metadata["protected"] = utils.get_bool_param("protected", image_metadata) try: response = self.volume_api.copy_volume_to_image(context, volume, image_metadata, force) except exception.InvalidVolume as error: raise webob.exc.HTTPBadRequest(explanation=error.msg) except ValueError as error: raise webob.exc.HTTPBadRequest(explanation=six.text_type(error)) except messaging.RemoteError as error: msg = "%(err_type)s: %(err_msg)s" % {"err_type": error.exc_type, "err_msg": error.value} raise webob.exc.HTTPBadRequest(explanation=msg) except Exception as error: raise webob.exc.HTTPBadRequest(explanation=six.text_type(error)) return {"os-volume_upload_image": response}
def update(self, req, id, body): # Update description for a given group type. context = req.environ['cinder.context'] self._check_policy(context) self.assert_valid_body(body, 'group_type') grp_type = body['group_type'] description = grp_type.get('description') name = grp_type.get('name') is_public = grp_type.get('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: msg = _("Group type name can not be empty.") raise webob.exc.HTTPBadRequest(explanation=msg) if name is None and description is None and is_public is None: msg = _("Specify group type name, description or " "a combination thereof.") raise webob.exc.HTTPBadRequest(explanation=msg) if is_public is not None: is_public = utils.get_bool_param('is_public', grp_type) if name: utils.check_string_length(name, 'Type name', min_length=1, max_length=255) if description is not None: utils.check_string_length(description, 'Type description', min_length=0, max_length=255) try: group_types.update(context, id, name, description, is_public=is_public) # Get the updated grp_type = group_types.get_group_type(context, id) req.cache_resource(grp_type, name='group_types') self._notify_group_type_info(context, 'group_type.update', grp_type) except exception.GroupTypeNotFound as err: self._notify_group_type_error(context, 'group_type.update', err, id=id) raise webob.exc.HTTPNotFound(explanation=six.text_type(err)) except exception.GroupTypeExists as err: self._notify_group_type_error(context, 'group_type.update', err, group_type=grp_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.GroupTypeUpdateFailed as err: self._notify_group_type_error(context, 'group_type.update', err, group_type=grp_type) raise webob.exc.HTTPInternalServerError( explanation=six.text_type(err)) return self._view_builder.show(req, grp_type)
def create(self, req, body): """Instruct Cinder to manage a storage object. Manages an existing backend storage object (e.g. a Linux logical volume or a SAN disk) by creating the Cinder objects required to manage it, and possibly renaming the backend storage object (driver dependent) From an API perspective, this operation behaves very much like a volume creation operation, except that properties such as image, snapshot and volume references don't make sense, because we are taking an existing storage object into Cinder management. Required HTTP Body: .. code-block:: json { 'volume': { 'host': <Cinder host on which the existing storage resides>, 'ref': <Driver-specific reference to existing storage object>, } } See the appropriate Cinder drivers' implementations of the manage_volume method to find out the accepted format of 'ref'. This API call will return with an error if any of the above elements are missing from the request, or if the 'host' element refers to a cinder host that is not registered. The volume will later enter the error state if it is discovered that 'ref' is bad. Optional elements to 'volume' are:: name A name for the new volume. description A description for the new volume. volume_type ID or name of a volume type to associate with the new Cinder volume. Does not necessarily guarantee that the managed volume will have the properties described in the volume_type. The driver may choose to fail if it identifies that the specified volume_type is not compatible with the backend storage object. metadata Key/value pairs to be associated with the new volume. availability_zone The availability zone to associate with the new volume. bootable If set to True, marks the volume as bootable. """ context = req.environ['cinder.context'] authorize_manage(context) self.assert_valid_body(body, 'volume') volume = body['volume'] self.validate_name_and_description(volume) # Check that the required keys are present, return an error if they # are not. required_keys = set(['ref', 'host']) missing_keys = list(required_keys - set(volume.keys())) if missing_keys: msg = _("The following elements are required: %s") % \ ', '.join(missing_keys) raise exc.HTTPBadRequest(explanation=msg) LOG.debug('Manage volume request body: %s', body) kwargs = {} 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'] = ( volume_types.get_by_name_or_id(context, req_volume_type)) else: kwargs['volume_type'] = {} kwargs['name'] = volume.get('name', None) kwargs['description'] = volume.get('description', None) kwargs['metadata'] = volume.get('metadata', None) kwargs['availability_zone'] = volume.get('availability_zone', None) kwargs['bootable'] = utils.get_bool_param('bootable', volume) utils.check_metadata_properties(kwargs['metadata']) # Not found exception will be handled at wsgi level new_volume = self.volume_api.manage_existing(context, volume['host'], volume['ref'], **kwargs) utils.add_visible_admin_metadata(new_volume) return self._view_builder.detail(req, new_volume)
def create(self, req, body): """Instruct Cinder to manage a storage object. Manages an existing backend storage object (e.g. a Linux logical volume or a SAN disk) by creating the Cinder objects required to manage it, and possibly renaming the backend storage object (driver dependent) From an API perspective, this operation behaves very much like a volume creation operation, except that properties such as image, snapshot and volume references don't make sense, because we are taking an existing storage object into Cinder management. Required HTTP Body: .. code-block:: json { "volume": { "host": "<Cinder host on which the existing storage resides>", "cluster": "<Cinder cluster on which the storage resides>", "ref": "<Driver-specific reference to existing storage object>" } } See the appropriate Cinder drivers' implementations of the manage_volume method to find out the accepted format of 'ref'. This API call will return with an error if any of the above elements are missing from the request, or if the 'host' element refers to a cinder host that is not registered. The volume will later enter the error state if it is discovered that 'ref' is bad. Optional elements to 'volume' are:: name A name for the new volume. description A description for the new volume. volume_type ID or name of a volume type to associate with the new Cinder volume. Does not necessarily guarantee that the managed volume will have the properties described in the volume_type. The driver may choose to fail if it identifies that the specified volume_type is not compatible with the backend storage object. metadata Key/value pairs to be associated with the new volume. availability_zone The availability zone to associate with the new volume. bootable If set to True, marks the volume as bootable. """ context = req.environ['cinder.context'] context.authorize(policy.MANAGE_POLICY) self.assert_valid_body(body, 'volume') volume = body['volume'] self.validate_name_and_description(volume) # Check that the required keys are present, return an error if they # are not. if 'ref' not in volume: raise exception.MissingRequired(element='ref') cluster_name, host = common.get_cluster_host(req, volume, mv.VOLUME_MIGRATE_CLUSTER) LOG.debug('Manage volume request body: %s', body) kwargs = {} req_volume_type = volume.get('volume_type', None) if req_volume_type: try: kwargs['volume_type'] = volume_types.get_by_name_or_id( context, req_volume_type) except exception.VolumeTypeNotFound: msg = _("Cannot find requested '%s' " "volume type") % req_volume_type raise exception.InvalidVolumeType(reason=msg) else: kwargs['volume_type'] = {} kwargs['name'] = volume.get('name', None) kwargs['description'] = volume.get('description', None) kwargs['metadata'] = volume.get('metadata', None) kwargs['availability_zone'] = volume.get('availability_zone', None) kwargs['bootable'] = utils.get_bool_param('bootable', volume) utils.check_metadata_properties(kwargs['metadata']) try: new_volume = self.volume_api.manage_existing( context, host, cluster_name, volume['ref'], **kwargs) except exception.ServiceNotFound: msg = _("%(name)s '%(value)s' not found") % { 'name': 'Host' if host else 'Cluster', 'value': host or cluster_name } raise exception.ServiceUnavailable(message=msg) utils.add_visible_admin_metadata(new_volume) return self._view_builder.detail(req, new_volume)
def create(self, req, body): """Creates a new volume.""" self.assert_valid_body(body, 'volume') LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] volume = body['volume'] # Check up front for legacy replication parameters to quick fail source_replica = volume.get('source_replica') if source_replica: msg = _("Creating a volume from a replica source was part of the " "replication v1 implementation which is no longer " "available.") raise exception.InvalidInput(reason=msg) 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: if not uuidutils.is_uuid_like(source_volid): msg = _("Source volume ID '%s' must be a " "valid UUID.") % source_volid raise exc.HTTPBadRequest(explanation=msg) # 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 kwargs['group'] = None kwargs['consistencygroup'] = None consistencygroup_id = volume.get('consistencygroup_id') if consistencygroup_id is not None: if not uuidutils.is_uuid_like(consistencygroup_id): msg = _("Consistency group ID '%s' must be a " "valid UUID.") % consistencygroup_id raise exc.HTTPBadRequest(explanation=msg) # Not found exception will be handled at the wsgi level kwargs['group'] = self.group_api.get(context, consistencygroup_id) 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'] LOG.info("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) kwargs['multiattach'] = utils.get_bool_param('multiattach', volume) 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 _update(self, req, id, body): # Update description for a given volume type. context = req.environ['cinder.context'] context.authorize(policy.MANAGE_POLICY) self.assert_valid_body(body, 'volume_type') vol_type = body['volume_type'] description = vol_type.get('description') name = vol_type.get('name') is_public = vol_type.get('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: msg = _("Volume type name can not be empty.") raise webob.exc.HTTPBadRequest(explanation=msg) 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.") raise webob.exc.HTTPBadRequest(explanation=msg) if is_public is not None: is_public = utils.get_bool_param('is_public', vol_type) if name: utils.check_string_length(name, 'Type name', min_length=1, max_length=255) if description is not None: utils.check_string_length(description, 'Type description', min_length=0, max_length=255) try: volume_types.update(context, id, name, description, is_public=is_public) # Get the updated vol_type = volume_types.get_volume_type(context, id) req.cache_resource(vol_type, name='types') self._notify_volume_type_info(context, 'volume_type.update', vol_type) except exception.VolumeTypeNotFound as err: self._notify_volume_type_error(context, 'volume_type.update', err, id=id) # Not found exception will be handled at the wsgi level raise except exception.VolumeTypeExists as err: self._notify_volume_type_error(context, 'volume_type.update', err, volume_type=vol_type) raise webob.exc.HTTPConflict(explanation=six.text_type(err)) except exception.VolumeTypeUpdateFailed as err: self._notify_volume_type_error(context, 'volume_type.update', err, volume_type=vol_type) raise webob.exc.HTTPInternalServerError( explanation=six.text_type(err)) return self._view_builder.show(req, vol_type)
def create(self, req, body): """Creates a new volume.""" LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] # NOTE (pooja_jadhav) To fix bug 1774155, scheduler hints is not # loaded as a standard extension. If user passes # OS-SCH-HNT:scheduler_hints in the request body, then it will be # validated in the create method and this method will add # scheduler_hints in body['volume']. body = scheduler_hints.create(req, body) volume = body['volume'] kwargs = {} self.validate_name_and_description(volume, check_length=False) # 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: # 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 kwargs['group'] = None kwargs['consistencygroup'] = None consistencygroup_id = volume.get('consistencygroup_id') if consistencygroup_id is not None: # Not found exception will be handled at the wsgi level kwargs['group'] = self.group_api.get(context, consistencygroup_id) 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'] LOG.info("Create volume of %s GB", size) 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) kwargs['multiattach'] = utils.get_bool_param('multiattach', volume) if kwargs.get('multiattach', False): msg = ("The option 'multiattach' " "is deprecated and will be removed in a future " "release. The default behavior going forward will " "be to specify multiattach enabled volume types.") versionutils.report_deprecated_feature(LOG, msg) 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(self, req, body): """Creates a new volume.""" self.assert_valid_body(body, 'volume') LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] 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'] = (volume_types.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: # 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: # Not found exception will be handled at the wsgi level kwargs['consistencygroup'] = \ self.consistencygroup_api.get(context, consistencygroup_id) else: kwargs['consistencygroup'] = None 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) kwargs['multiattach'] = utils.get_bool_param('multiattach', volume) 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(self, req, body): """Instruct Cinder to manage a storage object. Manages an existing backend storage object (e.g. a Linux logical volume or a SAN disk) by creating the Cinder objects required to manage it, and possibly renaming the backend storage object (driver dependent) From an API perspective, this operation behaves very much like a volume creation operation, except that properties such as image, snapshot and volume references don't make sense, because we are taking an existing storage object into Cinder management. Required HTTP Body: .. code-block:: json { 'volume': { 'host': <Cinder host on which the existing storage resides>, 'ref': <Driver-specific reference to existing storage object>, } } See the appropriate Cinder drivers' implementations of the manage_volume method to find out the accepted format of 'ref'. This API call will return with an error if any of the above elements are missing from the request, or if the 'host' element refers to a cinder host that is not registered. The volume will later enter the error state if it is discovered that 'ref' is bad. Optional elements to 'volume' are:: name A name for the new volume. description A description for the new volume. volume_type ID or name of a volume type to associate with the new Cinder volume. Does not necessarily guarantee that the managed volume will have the properties described in the volume_type. The driver may choose to fail if it identifies that the specified volume_type is not compatible with the backend storage object. metadata Key/value pairs to be associated with the new volume. availability_zone The availability zone to associate with the new volume. bootable If set to True, marks the volume as bootable. """ context = req.environ['cinder.context'] authorize_manage(context) self.assert_valid_body(body, 'volume') volume = body['volume'] self.validate_name_and_description(volume) # Check that the required keys are present, return an error if they # are not. required_keys = set(['ref', 'host']) missing_keys = list(required_keys - set(volume.keys())) if missing_keys: msg = _("The following elements are required: %s") % \ ', '.join(missing_keys) raise exc.HTTPBadRequest(explanation=msg) LOG.debug('Manage volume request body: %s', body) kwargs = {} 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'] = (volume_types.get_by_name_or_id( context, req_volume_type)) else: kwargs['volume_type'] = {} kwargs['name'] = volume.get('name', None) kwargs['description'] = volume.get('description', None) kwargs['metadata'] = volume.get('metadata', None) kwargs['availability_zone'] = volume.get('availability_zone', None) kwargs['bootable'] = utils.get_bool_param('bootable', volume) utils.check_metadata_properties(kwargs['metadata']) # Not found exception will be handled at wsgi level new_volume = self.volume_api.manage_existing(context, volume['host'], volume['ref'], **kwargs) utils.add_visible_admin_metadata(new_volume) return self._view_builder.detail(req, new_volume)
def _volume_upload_image(self, req, id, body): """Uploads the specified volume to image service.""" context = req.environ['cinder.context'] params = body['os-volume_upload_image'] req_version = req.api_version_request if not params.get("image_name"): msg = _("No image_name was specified in request.") raise webob.exc.HTTPBadRequest(explanation=msg) force = params.get('force', 'False') try: force = strutils.bool_from_string(force, strict=True) except ValueError as error: err_msg = encodeutils.exception_to_unicode(error) msg = _("Invalid value for 'force': '%s'") % err_msg raise webob.exc.HTTPBadRequest(explanation=msg) # Not found exception will be handled at the wsgi level volume = self.volume_api.get(context, id) authorize(context, "upload_image") # check for valid disk-format disk_format = params.get("disk_format", "raw") if not image_utils.validate_disk_format(disk_format): msg = _("Invalid disk-format '%(disk_format)s' is specified. " "Allowed disk-formats are %(allowed_disk_formats)s.") % { "disk_format": disk_format, "allowed_disk_formats": ", ".join( image_utils.VALID_DISK_FORMATS) } raise webob.exc.HTTPBadRequest(explanation=msg) if disk_format == "parallels": disk_format = "ploop" image_metadata = {"container_format": params.get( "container_format", "bare"), "disk_format": disk_format, "name": params["image_name"]} if volume.encryption_key_id: # Clone volume encryption key: the current key cannot # be reused because it will be deleted when the volume is # deleted. # TODO(eharney): Currently, there is no mechanism to remove # these keys, because Glance will not delete the key from # Barbican when the image is deleted. encryption_key_id = self._key_manager.store( context, self._key_manager.get(context, volume.encryption_key_id)) image_metadata['cinder_encryption_key_id'] = encryption_key_id if req_version >= api_version_request.APIVersionRequest('3.1'): image_metadata['visibility'] = params.get('visibility', 'private') image_metadata['protected'] = params.get('protected', 'False') if image_metadata['visibility'] == 'public': authorize(context, 'upload_public') if CONF.glance_api_version != 2: # Replace visibility with is_public for Glance V1 image_metadata['is_public'] = ( image_metadata['visibility'] == 'public') image_metadata.pop('visibility', None) image_metadata['protected'] = ( utils.get_bool_param('protected', image_metadata)) try: response = self.volume_api.copy_volume_to_image(context, volume, image_metadata, force) except exception.InvalidVolume as error: raise webob.exc.HTTPBadRequest(explanation=error.msg) except ValueError as error: raise webob.exc.HTTPBadRequest(explanation=six.text_type(error)) except messaging.RemoteError as error: msg = "%(err_type)s: %(err_msg)s" % {'err_type': error.exc_type, 'err_msg': error.value} raise webob.exc.HTTPBadRequest(explanation=msg) except Exception as error: raise webob.exc.HTTPBadRequest(explanation=six.text_type(error)) return {'os-volume_upload_image': response}
def _volume_upload_image(self, req, id, body): """Uploads the specified volume to image service.""" context = req.environ['cinder.context'] params = body['os-volume_upload_image'] req_version = req.api_version_request if not params.get("image_name"): msg = _("No image_name was specified in request.") raise webob.exc.HTTPBadRequest(explanation=msg) force = params.get('force', 'False') try: force = strutils.bool_from_string(force, strict=True) except ValueError as error: err_msg = encodeutils.exception_to_unicode(error) msg = _("Invalid value for 'force': '%s'") % err_msg raise webob.exc.HTTPBadRequest(explanation=msg) # Not found exception will be handled at the wsgi level volume = self.volume_api.get(context, id) authorize(context, "upload_image") # check for valid disk-format disk_format = params.get("disk_format", "raw") if not image_utils.validate_disk_format(disk_format): msg = _("Invalid disk-format '%(disk_format)s' is specified. " "Allowed disk-formats are %(allowed_disk_formats)s.") % { "disk_format": disk_format, "allowed_disk_formats": ", ".join( image_utils.VALID_DISK_FORMATS) } raise webob.exc.HTTPBadRequest(explanation=msg) image_metadata = { "container_format": params.get("container_format", "bare"), "disk_format": disk_format, "name": params["image_name"] } if volume.encryption_key_id: # Clone volume encryption key: the current key cannot # be reused because it will be deleted when the volume is # deleted. # TODO(eharney): Currently, there is no mechanism to remove # these keys, because Glance will not delete the key from # Barbican when the image is deleted. encryption_key_id = self._key_manager.store( context, self._key_manager.get(context, volume.encryption_key_id)) image_metadata['cinder_encryption_key_id'] = encryption_key_id if req_version >= api_version_request.APIVersionRequest('3.1'): image_metadata['visibility'] = params.get('visibility', 'private') image_metadata['protected'] = params.get('protected', 'False') if image_metadata['visibility'] == 'public': authorize(context, 'upload_public') if CONF.glance_api_version != 2: # Replace visibility with is_public for Glance V1 image_metadata['is_public'] = ( image_metadata['visibility'] == 'public') image_metadata.pop('visibility', None) image_metadata['protected'] = (utils.get_bool_param( 'protected', image_metadata)) try: response = self.volume_api.copy_volume_to_image( context, volume, image_metadata, force) except exception.InvalidVolume as error: raise webob.exc.HTTPBadRequest(explanation=error.msg) except ValueError as error: raise webob.exc.HTTPBadRequest(explanation=six.text_type(error)) except messaging.RemoteError as error: msg = "%(err_type)s: %(err_msg)s" % { 'err_type': error.exc_type, 'err_msg': error.value } raise webob.exc.HTTPBadRequest(explanation=msg) except Exception as error: raise webob.exc.HTTPBadRequest(explanation=six.text_type(error)) return {'os-volume_upload_image': response}
def create(self, req, body): """Creates a new volume.""" self.assert_valid_body(body, 'volume') LOG.debug('Create volume request body: %s', body) context = req.environ['cinder.context'] volume = body['volume'] # Check up front for legacy replication parameters to quick fail source_replica = volume.get('source_replica') if source_replica: msg = _("Creating a volume from a replica source was part of the " "replication v1 implementation which is no longer " "available.") raise exception.InvalidInput(reason=msg) 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: if not uuidutils.is_uuid_like(source_volid): msg = _("Source volume ID '%s' must be a " "valid UUID.") % source_volid raise exc.HTTPBadRequest(explanation=msg) # 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 kwargs['group'] = None kwargs['consistencygroup'] = None consistencygroup_id = volume.get('consistencygroup_id') if consistencygroup_id is not None: if not uuidutils.is_uuid_like(consistencygroup_id): msg = _("Consistency group ID '%s' must be a " "valid UUID.") % consistencygroup_id raise exc.HTTPBadRequest(explanation=msg) # Not found exception will be handled at the wsgi level kwargs['group'] = self.group_api.get(context, consistencygroup_id) 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'] LOG.info("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) kwargs['multiattach'] = utils.get_bool_param('multiattach', volume) 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 _prepare_params(self, ctxt, params, allowed): if not allowed.issuperset(params): invalid_keys = set(params).difference(allowed) msg = _('Invalid filter keys: %s') % ', '.join(invalid_keys) raise exception.InvalidInput(reason=msg) if params.get('binary') not in (None, constants.VOLUME_BINARY, 'cinder-scheduler'): msg = _('binary must be empty or set to cinder-volume or ' 'cinder-scheduler') raise exception.InvalidInput(reason=msg) for boolean in ('disabled', 'is_up'): if params.get(boolean) is not None: params[boolean] = utils.get_bool_param(boolean, params) resource_type = params.get('resource_type') if resource_type: resource_type = resource_type.title() types = cleanable.CinderCleanableObject.cleanable_resource_types if resource_type not in types: valid_types = utils.build_or_str(types) msg = _('Resource type %(resource_type)s not valid,' ' must be %(valid_types)s') msg = msg % { "resource_type": resource_type, "valid_types": valid_types } raise exception.InvalidInput(reason=msg) params['resource_type'] = resource_type resource_id = params.get('resource_id') if resource_id: if not uuidutils.is_uuid_like(resource_id): msg = (_('Resource ID must be a UUID, and %s is not.') % resource_id) raise exception.InvalidInput(reason=msg) # If we have the resource type but we don't have where it is # located, we get it from the DB to limit the distribution of the # request by the scheduler, otherwise it will be distributed to all # the services. location_keys = {'service_id', 'cluster_name', 'host'} if not location_keys.intersection(params): workers = db.worker_get_all(ctxt, resource_id=resource_id, binary=params.get('binary'), resource_type=resource_type) if len(workers) == 0: msg = (_('There is no resource with UUID %s pending ' 'cleanup.'), resource_id) raise exception.InvalidInput(reason=msg) if len(workers) > 1: msg = (_('There are multiple resources with UUID %s ' 'pending cleanup. Please be more specific.'), resource_id) raise exception.InvalidInput(reason=msg) worker = workers[0] params.update(service_id=worker.service_id, resource_type=worker.resource_type) return params
def _volume_upload_image(self, req, id, body): """Uploads the specified volume to image service.""" context = req.environ['cinder.context'] params = body['os-volume_upload_image'] req_version = req.api_version_request if not params.get("image_name"): msg = _("No image_name was specified in request.") raise webob.exc.HTTPBadRequest(explanation=msg) force = params.get('force', 'False') try: force = strutils.bool_from_string(force, strict=True) except ValueError as error: err_msg = encodeutils.exception_to_unicode(error) msg = _("Invalid value for 'force': '%s'") % err_msg raise webob.exc.HTTPBadRequest(explanation=msg) try: volume = self.volume_api.get(context, id) except exception.VolumeNotFound as error: raise webob.exc.HTTPNotFound(explanation=error.msg) authorize(context, "upload_image") image_metadata = {"container_format": params.get("container_format", "bare"), "disk_format": params.get("disk_format", "raw"), "name": params["image_name"]} if req_version >= api_version_request.APIVersionRequest('3.1'): image_metadata['visibility'] = params.get('visibility', 'private') image_metadata['protected'] = params.get('protected', 'False') if image_metadata['visibility'] == 'public': authorize(context, 'upload_public') if CONF.glance_api_version != 2: # Replace visibility with is_public for Glance V1 image_metadata['is_public'] = ( image_metadata['visibility'] == 'public') image_metadata.pop('visibility', None) image_metadata['protected'] = ( utils.get_bool_param('protected', image_metadata)) try: response = self.volume_api.copy_volume_to_image(context, volume, image_metadata, force) except exception.InvalidVolume as error: raise webob.exc.HTTPBadRequest(explanation=error.msg) except ValueError as error: raise webob.exc.HTTPBadRequest(explanation=six.text_type(error)) except messaging.RemoteError as error: msg = "%(err_type)s: %(err_msg)s" % {'err_type': error.exc_type, 'err_msg': error.value} raise webob.exc.HTTPBadRequest(explanation=msg) except Exception as error: raise webob.exc.HTTPBadRequest(explanation=six.text_type(error)) return {'os-volume_upload_image': response}