예제 #1
0
    def delete(self, context, backup, force=False):
        """Make the RPC call to delete a volume backup.

        Call backup manager to execute backup delete or force delete operation.
        :param context: running context
        :param backup: the dict of backup that is got from DB.
        :param force: indicate force delete or not
        :raises: InvalidBackup
        :raises: BackupDriverException
        :raises: ServiceNotFound
        """
        check_policy(context, 'delete')
        if not force and backup.status not in [
                fields.BackupStatus.AVAILABLE, fields.BackupStatus.ERROR
        ]:
            msg = _('Backup status must be available or error')
            raise exception.InvalidBackup(reason=msg)
        if force and not self._check_support_to_force_delete(
                context, backup.host):
            msg = _('force delete')
            raise exception.NotSupportedOperation(operation=msg)

        # Don't allow backup to be deleted if there are incremental
        # backups dependent on it.
        deltas = self.get_all(context, search_opts={'parent_id': backup.id})
        if deltas and len(deltas):
            msg = _('Incremental backups exist for this backup.')
            raise exception.InvalidBackup(reason=msg)

        backup.status = fields.BackupStatus.DELETING
        backup.host = self._get_available_backup_service_host(
            backup.host, backup.availability_zone)
        backup.save()
        self.backup_rpcapi.delete_backup(context, backup)
예제 #2
0
    def _disconnect(self):
        """Disconnect from the switch using HTTP/HTTPS protocol.

        :raises: BrocadeZoningHttpException
        """
        try:
            headers = {zone_constant.AUTH_HEADER: self.auth_header}
            response = self.connect(zone_constant.GET_METHOD,
                                    zone_constant.LOGOUT_PAGE,
                                    header=headers)
            return response
        except requests.exceptions.ConnectionError as e:
            msg = (_("Error while connecting the switch %(switch_id)s "
                     "with protocol %(protocol)s. Error: %(error)s.") % {
                         'switch_id': self.switch_ip,
                         'protocol': self.protocol,
                         'error': six.text_type(e)
                     })
            LOG.error(msg)
            raise exception.BrocadeZoningHttpException(reason=msg)
        except exception.BrocadeZoningHttpException as ex:
            msg = (_("Unexpected status code from the switch %(switch_id)s "
                     "with protocol %(protocol)s for url %(page)s. "
                     "Error: %(error)s") % {
                         'switch_id': self.switch_ip,
                         'protocol': self.protocol,
                         'page': zone_constant.LOGOUT_PAGE,
                         'error': six.text_type(ex)
                     })
            LOG.error(msg)
            raise exception.BrocadeZoningHttpException(reason=msg)
예제 #3
0
 def __init__(self, message=None, **kwargs):
     if kwargs.get('host', None):
         self.message = _("Service %(service_id)s could not be "
                          "found on host %(host)s.")
     else:
         self.message = _("Service %(service_id)s could not be found.")
     super(ServiceNotFound, self).__init__(None, **kwargs)
예제 #4
0
    def _disconnect(self):
        """Disconnect from the switch using HTTP/HTTPS protocol.

        :raises: BrocadeZoningHttpException
        """
        try:
            headers = {zone_constant.AUTH_HEADER: self.auth_header}
            response = self.connect(zone_constant.GET_METHOD,
                                    zone_constant.LOGOUT_PAGE,
                                    header=headers)
            return response
        except requests.exceptions.ConnectionError as e:
            msg = (_("Error while connecting the switch %(switch_id)s "
                     "with protocol %(protocol)s. Error: %(error)s.")
                   % {'switch_id': self.switch_ip,
                      'protocol': self.protocol,
                      'error': six.text_type(e)})
            LOG.error(msg)
            raise exception.BrocadeZoningHttpException(reason=msg)
        except exception.BrocadeZoningHttpException as ex:
            msg = (_("Unexpected status code from the switch %(switch_id)s "
                     "with protocol %(protocol)s for url %(page)s. "
                     "Error: %(error)s")
                   % {'switch_id': self.switch_ip,
                      'protocol': self.protocol,
                      'page': zone_constant.LOGOUT_PAGE,
                      'error': six.text_type(ex)})
            LOG.error(msg)
            raise exception.BrocadeZoningHttpException(reason=msg)
예제 #5
0
    def validate_update(self, body):
        update = {}
        status = body.get('status', None)
        attach_status = body.get('attach_status', None)
        migration_status = body.get('migration_status', None)

        valid = False
        if status:
            valid = True
            update = super(VolumeAdminController, self).validate_update(body)

        if attach_status:
            valid = True
            update['attach_status'] = attach_status.lower()
            if update['attach_status'] not in self.valid_attach_status:
                raise exc.HTTPBadRequest(
                    explanation=_("Must specify a valid attach status"))

        if migration_status:
            valid = True
            update['migration_status'] = migration_status.lower()
            if update['migration_status'] not in self.valid_migration_status:
                raise exc.HTTPBadRequest(
                    explanation=_("Must specify a valid migration status"))
            if update['migration_status'] == 'none':
                update['migration_status'] = None

        if not valid:
            raise exc.HTTPBadRequest(
                explanation=_("Must specify 'status', 'attach_status' "
                              "or 'migration_status' for update."))
        return update
예제 #6
0
파일: volume.py 프로젝트: HybridF5/jacket
    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            if 'consistencygroup' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('consistencygroup changed'))
            if 'glance_metadata' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('glance_metadata changed'))
            if 'snapshots' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('snapshots changed'))
            if 'metadata' in updates:
                # Metadata items that are not specified in the
                # self.metadata will be deleted
                metadata = updates.pop('metadata', None)
                self.metadata = db.volume_metadata_update(self._context,
                                                          self.id, metadata,
                                                          True)
            if self._context.is_admin and 'admin_metadata' in updates:
                metadata = updates.pop('admin_metadata', None)
                self.admin_metadata = db.volume_admin_metadata_update(
                    self._context, self.id, metadata, True)

            db.volume_update(self._context, self.id, updates)
            self.obj_reset_changes()
예제 #7
0
    def _authorize_show(self, context_project, target_project):
        """Checks if show is allowed in the current hierarchy.

        With hierarchical projects, users are allowed to perform a quota show
        operation if they have the cloud admin role or if they belong to at
        least one of the following projects: the target project, its immediate
        parent project, or the root project of its hierarchy.

        :param context_project: The project in which the user
                                is scoped to.
        :param target_project: The project in which the user wants
                               to perform a show operation.
        """
        if context_project.is_admin_project:
            # The calling project has admin privileges and should be able
            # to view all quotas.
            return
        if target_project.parent_id:
            if target_project.id != context_project.id:
                if not self._is_descendant(target_project.id,
                                           context_project.subtree):
                    msg = _("Show operations can only be made to projects in "
                            "the same hierarchy of the project in which users "
                            "are scoped to.")
                    raise webob.exc.HTTPForbidden(explanation=msg)
                if context_project.id != target_project.parent_id:
                    if context_project.parent_id:
                        msg = _("Only users with token scoped to immediate "
                                "parents or root projects are allowed to see "
                                "its children quotas.")
                        raise webob.exc.HTTPForbidden(explanation=msg)
        elif context_project.parent_id:
            msg = _("An user with a token scoped to a subproject is not "
                    "allowed to see the quota of its parents.")
            raise webob.exc.HTTPForbidden(explanation=msg)
예제 #8
0
    def _authorize_update_or_delete(self, context_project, target_project_id,
                                    parent_id):
        """Checks if update or delete are allowed in the current hierarchy.

        With hierarchical projects, only the admin of the parent or the root
        project has privilege to perform quota update and delete operations.

        :param context_project: The project in which the user is scoped to.
        :param target_project_id: The id of the project in which the
                                  user want to perform an update or
                                  delete operation.
        :param parent_id: The parent id of the project in which the user
                          want to perform an update or delete operation.
        """
        if context_project.is_admin_project:
            # The calling project has admin privileges and should be able
            # to operate on all quotas.
            return
        if context_project.parent_id and parent_id != context_project.id:
            msg = _("Update and delete quota operations can only be made "
                    "by an admin of immediate parent or by the CLOUD admin.")
            raise webob.exc.HTTPForbidden(explanation=msg)

        if context_project.id != target_project_id:
            if not self._is_descendant(target_project_id,
                                       context_project.subtree):
                msg = _("Update and delete quota operations can only be made "
                        "to projects in the same hierarchy of the project in "
                        "which users are scoped to.")
                raise webob.exc.HTTPForbidden(explanation=msg)
        else:
            msg = _("Update and delete quota operations can only be made "
                    "by an admin of immediate parent or by the CLOUD admin.")
            raise webob.exc.HTTPForbidden(explanation=msg)
예제 #9
0
    def validate_update(self, body):
        update = {}
        status = body.get('status', None)
        attach_status = body.get('attach_status', None)
        migration_status = body.get('migration_status', None)

        valid = False
        if status:
            valid = True
            update = super(VolumeAdminController, self).validate_update(body)

        if attach_status:
            valid = True
            update['attach_status'] = attach_status.lower()
            if update['attach_status'] not in self.valid_attach_status:
                raise exc.HTTPBadRequest(
                    explanation=_("Must specify a valid attach status"))

        if migration_status:
            valid = True
            update['migration_status'] = migration_status.lower()
            if update['migration_status'] not in self.valid_migration_status:
                raise exc.HTTPBadRequest(
                    explanation=_("Must specify a valid migration status"))
            if update['migration_status'] == 'none':
                update['migration_status'] = None

        if not valid:
            raise exc.HTTPBadRequest(
                explanation=_("Must specify 'status', 'attach_status' "
                              "or 'migration_status' for update."))
        return update
예제 #10
0
    def delete(self, req, id, body):
        """Delete a consistency group."""
        LOG.debug('delete called for member %s', id)
        context = req.environ['storage.context']
        force = False
        if body:
            if not self.is_valid_body(body, 'consistencygroup'):
                msg = _("Missing required element 'consistencygroup' in "
                        "request body.")
                raise exc.HTTPBadRequest(explanation=msg)

            cg_body = body['consistencygroup']
            try:
                force = strutils.bool_from_string(cg_body.get('force', False),
                                                  strict=True)
            except ValueError:
                msg = _("Invalid value '%s' for force.") % force
                raise exc.HTTPBadRequest(explanation=msg)

        LOG.info(_LI('Delete consistency group with id: %s'),
                 id,
                 context=context)

        try:
            group = self.consistencygroup_api.get(context, id)
            self.consistencygroup_api.delete(context, group, force)
        except exception.ConsistencyGroupNotFound as error:
            raise exc.HTTPNotFound(explanation=error.msg)
        except exception.InvalidConsistencyGroup as error:
            raise exc.HTTPBadRequest(explanation=error.msg)

        return webob.Response(status_int=202)
예제 #11
0
파일: api.py 프로젝트: HybridF5/jacket
    def delete(self, context, backup, force=False):
        """Make the RPC call to delete a volume backup.

        Call backup manager to execute backup delete or force delete operation.
        :param context: running context
        :param backup: the dict of backup that is got from DB.
        :param force: indicate force delete or not
        :raises: InvalidBackup
        :raises: BackupDriverException
        :raises: ServiceNotFound
        """
        check_policy(context, 'delete')
        if not force and backup.status not in [fields.BackupStatus.AVAILABLE,
                                               fields.BackupStatus.ERROR]:
            msg = _('Backup status must be available or error')
            raise exception.InvalidBackup(reason=msg)
        if force and not self._check_support_to_force_delete(context,
                                                             backup.host):
            msg = _('force delete')
            raise exception.NotSupportedOperation(operation=msg)

        # Don't allow backup to be deleted if there are incremental
        # backups dependent on it.
        deltas = self.get_all(context, search_opts={'parent_id': backup.id})
        if deltas and len(deltas):
            msg = _('Incremental backups exist for this backup.')
            raise exception.InvalidBackup(reason=msg)

        backup.status = fields.BackupStatus.DELETING
        backup.host = self._get_available_backup_service_host(
            backup.host, backup.availability_zone)
        backup.save()
        self.backup_rpcapi.delete_backup(context, backup)
예제 #12
0
def _copy_volume_with_file(src, dest, size_in_m):
    src_handle = src
    if isinstance(src, six.string_types):
        src_handle = _open_volume_with_path(src, 'rb')

    dest_handle = dest
    if isinstance(dest, six.string_types):
        dest_handle = _open_volume_with_path(dest, 'wb')

    if not src_handle:
        raise exception.DeviceUnavailable(
            _("Failed to copy volume, source device unavailable."))

    if not dest_handle:
        raise exception.DeviceUnavailable(
            _("Failed to copy volume, destination device unavailable."))

    start_time = timeutils.utcnow()

    _transfer_data(src_handle, dest_handle, size_in_m * units.Mi, units.Mi * 4)

    duration = max(1, timeutils.delta_seconds(start_time, timeutils.utcnow()))

    if isinstance(src, six.string_types):
        src_handle.close()
    if isinstance(dest, six.string_types):
        dest_handle.close()

    mbps = (size_in_m / duration)
    LOG.info(
        _LI("Volume copy completed (%(size_in_m).2f MB at "
            "%(mbps).2f MB/s)."), {
                'size_in_m': size_in_m,
                'mbps': mbps
            })
예제 #13
0
파일: utils.py 프로젝트: bopopescu/jacket
def validate_integer(value, name, min_value=None, max_value=None):
    """Make sure that value is a valid integer, potentially within range.

    :param value: the value of the integer
    :param name: the name of the integer
    :param min_length: the min_length of the integer
    :param max_length: the max_length of the integer
    :returns: integer
    """
    try:
        value = int(value)
    except (TypeError, ValueError, UnicodeEncodeError):
        raise webob.exc.HTTPBadRequest(explanation=(
            _('%s must be an integer.') % name))

    if min_value is not None and value < min_value:
        raise webob.exc.HTTPBadRequest(
            explanation=(_('%(value_name)s must be >= %(min_value)d') %
                         {'value_name': name, 'min_value': min_value}))
    if max_value is not None and value > max_value:
        raise webob.exc.HTTPBadRequest(
            explanation=(_('%(value_name)s must be <= %(max_value)d') %
                         {'value_name': name, 'max_value': max_value}))

    return value
예제 #14
0
    def delete(self, req, id):
        """Deletes an existing qos specs."""
        context = req.environ['storage.context']
        authorize(context)

        force = req.params.get('force', None)

        # Convert string to bool type in strict manner
        force = strutils.bool_from_string(force)
        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)
            raise webob.exc.HTTPNotFound(explanation=six.text_type(err))
        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)
예제 #15
0
파일: api.py 프로젝트: bopopescu/jacket
    def _validate_remove_volumes(self, volumes, remove_volumes_list, group):
        # Validate volumes in remove_volumes.
        remove_volumes_new = ""
        for volume in volumes:
            if volume['id'] in remove_volumes_list:
                if volume['status'] not in VALID_REMOVE_VOL_FROM_CG_STATUS:
                    msg = (_("Cannot remove volume %(volume_id)s from "
                             "consistency group %(group_id)s because volume "
                             "is in an invalid state: %(status)s. Valid "
                             "states are: %(valid)s.") % {
                                 'volume_id': volume['id'],
                                 'group_id': group.id,
                                 'status': volume['status'],
                                 'valid': VALID_REMOVE_VOL_FROM_CG_STATUS
                             })
                    raise exception.InvalidVolume(reason=msg)
                # Volume currently in CG. It will be removed from CG.
                if remove_volumes_new:
                    remove_volumes_new += ","
                remove_volumes_new += volume['id']

        for rem_vol in remove_volumes_list:
            if rem_vol not in remove_volumes_new:
                msg = (_("Cannot remove volume %(volume_id)s from "
                         "consistency group %(group_id)s because it "
                         "is not in the group.") % {
                             'volume_id': rem_vol,
                             'group_id': group.id
                         })
                raise exception.InvalidVolume(reason=msg)

        return remove_volumes_new
예제 #16
0
def fetch_verify_image(context,
                       image_service,
                       image_id,
                       dest,
                       user_id=None,
                       project_id=None,
                       size=None,
                       run_as_root=True):
    fetch(context, image_service, image_id, dest, None, None)

    with fileutils.remove_path_on_error(dest):
        data = qemu_img_info(dest, run_as_root=run_as_root)
        fmt = data.file_format
        if fmt is None:
            raise exception.ImageUnacceptable(
                reason=_("'qemu-img info' parsing failed."), image_id=image_id)

        backing_file = data.backing_file
        if backing_file is not None:
            raise exception.ImageUnacceptable(
                image_id=image_id,
                reason=(_("fmt=%(fmt)s backed by: %(backing_file)s") % {
                    'fmt': fmt,
                    'backing_file': backing_file
                }))

        # NOTE(xqueralt): If the image virtual size doesn't fit in the
        # requested volume there is no point on resizing it because it will
        # generate an unusable image.
        if size is not None and data.virtual_size > size:
            params = {'image_size': data.virtual_size, 'volume_size': size}
            reason = _("Size is %(image_size)dGB and doesn't fit in a "
                       "volume of size %(volume_size)dGB.") % params
            raise exception.ImageUnacceptable(image_id=image_id, reason=reason)
예제 #17
0
파일: utils.py 프로젝트: HybridF5/jacket
def _copy_volume_with_file(src, dest, size_in_m):
    src_handle = src
    if isinstance(src, six.string_types):
        src_handle = _open_volume_with_path(src, 'rb')

    dest_handle = dest
    if isinstance(dest, six.string_types):
        dest_handle = _open_volume_with_path(dest, 'wb')

    if not src_handle:
        raise exception.DeviceUnavailable(
            _("Failed to copy volume, source device unavailable."))

    if not dest_handle:
        raise exception.DeviceUnavailable(
            _("Failed to copy volume, destination device unavailable."))

    start_time = timeutils.utcnow()

    _transfer_data(src_handle, dest_handle, size_in_m * units.Mi, units.Mi * 4)

    duration = max(1, timeutils.delta_seconds(start_time, timeutils.utcnow()))

    if isinstance(src, six.string_types):
        src_handle.close()
    if isinstance(dest, six.string_types):
        dest_handle.close()

    mbps = (size_in_m / duration)
    LOG.info(_LI("Volume copy completed (%(size_in_m).2f MB at "
                 "%(mbps).2f MB/s)."),
             {'size_in_m': size_in_m, 'mbps': mbps})
예제 #18
0
파일: quotas.py 프로젝트: HybridF5/jacket
    def _authorize_update_or_delete(self, context_project,
                                    target_project_id,
                                    parent_id):
        """Checks if update or delete are allowed in the current hierarchy.

        With hierarchical projects, only the admin of the parent or the root
        project has privilege to perform quota update and delete operations.

        :param context_project: The project in which the user is scoped to.
        :param target_project_id: The id of the project in which the
                                  user want to perform an update or
                                  delete operation.
        :param parent_id: The parent id of the project in which the user
                          want to perform an update or delete operation.
        """
        if context_project.is_admin_project:
            # The calling project has admin privileges and should be able
            # to operate on all quotas.
            return
        if context_project.parent_id and parent_id != context_project.id:
            msg = _("Update and delete quota operations can only be made "
                    "by an admin of immediate parent or by the CLOUD admin.")
            raise webob.exc.HTTPForbidden(explanation=msg)

        if context_project.id != target_project_id:
            if not self._is_descendant(target_project_id,
                                       context_project.subtree):
                msg = _("Update and delete quota operations can only be made "
                        "to projects in the same hierarchy of the project in "
                        "which users are scoped to.")
                raise webob.exc.HTTPForbidden(explanation=msg)
        else:
            msg = _("Update and delete quota operations can only be made "
                    "by an admin of immediate parent or by the CLOUD admin.")
            raise webob.exc.HTTPForbidden(explanation=msg)
예제 #19
0
    def delete(self, req, id, body):
        """Delete a consistency group."""
        LOG.debug('delete called for member %s', id)
        context = req.environ['storage.context']
        force = False
        if body:
            if not self.is_valid_body(body, 'consistencygroup'):
                msg = _("Missing required element 'consistencygroup' in "
                        "request body.")
                raise exc.HTTPBadRequest(explanation=msg)

            cg_body = body['consistencygroup']
            try:
                force = strutils.bool_from_string(cg_body.get('force', False),
                                                  strict=True)
            except ValueError:
                msg = _("Invalid value '%s' for force.") % force
                raise exc.HTTPBadRequest(explanation=msg)

        LOG.info(_LI('Delete consistency group with id: %s'), id,
                 context=context)

        try:
            group = self.consistencygroup_api.get(context, id)
            self.consistencygroup_api.delete(context, group, force)
        except exception.ConsistencyGroupNotFound as error:
            raise exc.HTTPNotFound(explanation=error.msg)
        except exception.InvalidConsistencyGroup as error:
            raise exc.HTTPBadRequest(explanation=error.msg)

        return webob.Response(status_int=202)
예제 #20
0
    def _check_image_metadata(self, context, image_id, size):
        """Checks image existence and validates that the image metadata."""

        # Check image existence
        if image_id is None:
            return

        # NOTE(harlowja): this should raise an error if the image does not
        # exist, this is expected as it signals that the image_id is missing.
        image_meta = self.image_service.show(context, image_id)

        # check whether image is active
        if image_meta['status'] != 'active':
            msg = _('Image %(image_id)s is not active.')\
                % {'image_id': image_id}
            raise exception.InvalidInput(reason=msg)

        # Check image size is not larger than volume size.
        image_size = utils.as_int(image_meta['size'], quiet=False)
        image_size_in_gb = (image_size + GB - 1) // GB
        if image_size_in_gb > size:
            msg = _('Size of specified image %(image_size)sGB'
                    ' is larger than volume size %(volume_size)sGB.')
            msg = msg % {'image_size': image_size_in_gb, 'volume_size': size}
            raise exception.InvalidInput(reason=msg)

        # Check image min_disk requirement is met for the particular volume
        min_disk = image_meta.get('min_disk', 0)
        if size < min_disk:
            msg = _('Volume size %(volume_size)sGB cannot be smaller'
                    ' than the image minDisk size %(min_disk)sGB.')
            msg = msg % {'volume_size': size, 'min_disk': min_disk}
            raise exception.InvalidInput(reason=msg)
예제 #21
0
파일: quotas.py 프로젝트: HybridF5/jacket
    def _authorize_show(self, context_project, target_project):
        """Checks if show is allowed in the current hierarchy.

        With hierarchical projects, users are allowed to perform a quota show
        operation if they have the cloud admin role or if they belong to at
        least one of the following projects: the target project, its immediate
        parent project, or the root project of its hierarchy.

        :param context_project: The project in which the user
                                is scoped to.
        :param target_project: The project in which the user wants
                               to perform a show operation.
        """
        if context_project.is_admin_project:
            # The calling project has admin privileges and should be able
            # to view all quotas.
            return
        if target_project.parent_id:
            if target_project.id != context_project.id:
                if not self._is_descendant(target_project.id,
                                           context_project.subtree):
                    msg = _("Show operations can only be made to projects in "
                            "the same hierarchy of the project in which users "
                            "are scoped to.")
                    raise webob.exc.HTTPForbidden(explanation=msg)
                if context_project.id != target_project.parent_id:
                    if context_project.parent_id:
                        msg = _("Only users with token scoped to immediate "
                                "parents or root projects are allowed to see "
                                "its children quotas.")
                        raise webob.exc.HTTPForbidden(explanation=msg)
        elif context_project.parent_id:
            msg = _("An user with a token scoped to a subproject is not "
                    "allowed to see the quota of its parents.")
            raise webob.exc.HTTPForbidden(explanation=msg)
예제 #22
0
    def update(self, req, type_id, id, body=None):
        """Update encryption specs for a given volume type."""
        context = req.environ['storage.context']
        authorize(context)

        self.assert_valid_body(body, 'encryption')

        if len(body) > 1:
            expl = _('Request body contains too many items.')
            raise webob.exc.HTTPBadRequest(explanation=expl)

        self._check_type(context, type_id)

        if self._encrypted_type_in_use(context, type_id):
            expl = _('Cannot update encryption specs. Volume type in use.')
            raise webob.exc.HTTPBadRequest(explanation=expl)

        encryption_specs = body['encryption']
        self._check_encryption_input(encryption_specs, create=False)

        db.volume_type_encryption_update(context, type_id, encryption_specs)
        notifier_info = dict(type_id=type_id, id=id)
        notifier = rpc.get_notifier('volumeTypeEncryption')
        notifier.info(context, 'volume_type_encryption.update', notifier_info)

        return body
예제 #23
0
    def _set_bootable(self, req, id, body):
        """Update bootable status of a volume."""
        context = req.environ['storage.context']
        try:
            volume = self.volume_api.get(context, id)
        except exception.VolumeNotFound as error:
            raise webob.exc.HTTPNotFound(explanation=error.msg)

        try:
            bootable = body['os-set_bootable']['bootable']
        except KeyError:
            msg = _("Must specify bootable in request.")
            raise webob.exc.HTTPBadRequest(explanation=msg)

        try:
            bootable = strutils.bool_from_string(bootable,
                                                 strict=True)
        except ValueError as error:
            err_msg = encodeutils.exception_to_unicode(error)
            msg = _("Invalid value for 'bootable': '%s'") % err_msg
            raise webob.exc.HTTPBadRequest(explanation=msg)

        update_dict = {'bootable': bootable}

        self.volume_api.update(context, volume, update_dict)
        return webob.Response(status_int=200)
예제 #24
0
파일: tsm.py 프로젝트: HybridF5/jacket
    def restore(self, backup, volume_id, volume_file):
        """Restore the given volume backup from TSM server.

        :param backup: backup information for volume
        :param volume_id: volume id
        :param volume_file: file object representing the volume
        :raises: InvalidBackup
        """

        # backup_path is the path that was originally backed up.
        backup_path, backup_mode = _get_backup_metadata(backup, 'restore')

        LOG.debug('Starting restore of backup from TSM '
                  'to volume %(volume_id)s, '
                  'backup: %(backup_id)s, '
                  'mode: %(mode)s.',
                  {'volume_id': volume_id,
                   'backup_id': backup.id,
                   'mode': backup_mode})

        # volume_path is the path to restore into.  This may
        # be different than the original volume.
        volume_path, unused = _get_volume_realpath(volume_file,
                                                   volume_id)

        restore_path = _create_unique_device_link(backup.id,
                                                  volume_path,
                                                  volume_id,
                                                  backup_mode)

        try:
            self._do_restore(backup_path, restore_path, volume_id, backup_mode)
        except processutils.ProcessExecutionError as exc:
            err = (_('restore: %(vol_id)s failed to run dsmc '
                     'on %(bpath)s.\n'
                     'stdout: %(out)s\n stderr: %(err)s')
                   % {'vol_id': volume_id,
                      'bpath': restore_path,
                      'out': exc.stdout,
                      'err': exc.stderr})
            LOG.error(err)
            raise exception.InvalidBackup(reason=err)
        except exception.Error as exc:
            err = (_('restore: %(vol_id)s failed to run dsmc '
                     'due to invalid arguments '
                     'on %(bpath)s.\n'
                     'stdout: %(out)s\n stderr: %(err)s')
                   % {'vol_id': volume_id,
                      'bpath': restore_path,
                      'out': exc.stdout,
                      'err': exc.stderr})
            LOG.error(err)
            raise exception.InvalidBackup(reason=err)

        finally:
            _cleanup_device_hardlink(restore_path, volume_path, volume_id)

        LOG.debug('Restore %(backup_id)s to %(volume_id)s finished.',
                  {'backup_id': backup.id,
                   'volume_id': volume_id})
예제 #25
0
    def _set_bootable(self, req, id, body):
        """Update bootable status of a volume."""
        context = req.environ['storage.context']
        try:
            volume = self.volume_api.get(context, id)
        except exception.VolumeNotFound as error:
            raise webob.exc.HTTPNotFound(explanation=error.msg)

        try:
            bootable = body['os-set_bootable']['bootable']
        except KeyError:
            msg = _("Must specify bootable in request.")
            raise webob.exc.HTTPBadRequest(explanation=msg)

        try:
            bootable = strutils.bool_from_string(bootable, strict=True)
        except ValueError as error:
            err_msg = encodeutils.exception_to_unicode(error)
            msg = _("Invalid value for 'bootable': '%s'") % err_msg
            raise webob.exc.HTTPBadRequest(explanation=msg)

        update_dict = {'bootable': bootable}

        self.volume_api.update(context, volume, update_dict)
        return webob.Response(status_int=200)
예제 #26
0
    def update(self, req, id, body):
        """Update a snapshot."""
        context = req.environ['storage.context']

        if not body:
            msg = _("Missing request body")
            raise exc.HTTPBadRequest(explanation=msg)

        if 'snapshot' not in body:
            msg = (_("Missing required element '%s' in request body") %
                   'snapshot')
            raise exc.HTTPBadRequest(explanation=msg)

        snapshot = body['snapshot']
        update_dict = {}

        valid_update_keys = (
            'name',
            'description',
            'display_name',
            'display_description',
        )
        self.validate_name_and_description(snapshot)

        # NOTE(thingee): v2 API allows name instead of display_name
        if 'name' in snapshot:
            snapshot['display_name'] = snapshot.pop('name')

        # NOTE(thingee): v2 API allows description instead of
        # display_description
        if 'description' in snapshot:
            snapshot['display_description'] = snapshot.pop('description')

        for key in valid_update_keys:
            if key in snapshot:
                update_dict[key] = snapshot[key]

        try:
            snapshot = self.volume_api.get_snapshot(context, id)
            volume_utils.notify_about_snapshot_usage(context, snapshot,
                                                     'update.start')
            self.volume_api.update_snapshot(context, snapshot, update_dict)

            # NOTE(laoyi) need to rename
            try:
                if 'display_name' in update_dict:
                    self.volume_api.rename_snapshot(context, snapshot,
                                                  update_dict['display_name'])
            except Exception:
                pass
        except exception.SnapshotNotFound as error:
            raise exc.HTTPNotFound(explanation=error.msg)

        snapshot.update(update_dict)
        req.cache_db_snapshot(snapshot)
        volume_utils.notify_about_snapshot_usage(context, snapshot,
                                                 'update.end')

        return self._view_builder.detail(req, snapshot)
예제 #27
0
    def update(self, req, id, body):
        """Update a snapshot."""
        context = req.environ['storage.context']

        if not body:
            msg = _("Missing request body")
            raise exc.HTTPBadRequest(explanation=msg)

        if 'snapshot' not in body:
            msg = (_("Missing required element '%s' in request body") %
                   'snapshot')
            raise exc.HTTPBadRequest(explanation=msg)

        snapshot = body['snapshot']
        update_dict = {}

        valid_update_keys = (
            'name',
            'description',
            'display_name',
            'display_description',
        )
        self.validate_name_and_description(snapshot)

        # NOTE(thingee): v2 API allows name instead of display_name
        if 'name' in snapshot:
            snapshot['display_name'] = snapshot.pop('name')

        # NOTE(thingee): v2 API allows description instead of
        # display_description
        if 'description' in snapshot:
            snapshot['display_description'] = snapshot.pop('description')

        for key in valid_update_keys:
            if key in snapshot:
                update_dict[key] = snapshot[key]

        try:
            snapshot = self.volume_api.get_snapshot(context, id)
            volume_utils.notify_about_snapshot_usage(context, snapshot,
                                                     'update.start')
            self.volume_api.update_snapshot(context, snapshot, update_dict)

            # NOTE(laoyi) need to rename
            try:
                if 'display_name' in update_dict:
                    self.volume_api.rename_snapshot(
                        context, snapshot, update_dict['display_name'])
            except Exception:
                pass
        except exception.SnapshotNotFound as error:
            raise exc.HTTPNotFound(explanation=error.msg)

        snapshot.update(update_dict)
        req.cache_db_snapshot(snapshot)
        volume_utils.notify_about_snapshot_usage(context, snapshot,
                                                 'update.end')

        return self._view_builder.detail(req, snapshot)
예제 #28
0
    def _update_snapshot_status(self, req, id, body):
        """Update database fields related to status of a snapshot.

           Intended for creation of snapshots, so snapshot state
           must start as 'creating' and be changed to 'available',
           'creating', or 'error'.
        """

        context = req.environ['storage.context']
        authorize(context, 'update_snapshot_status')

        LOG.debug("body: %s", body)
        try:
            status = body['os-update_snapshot_status']['status']
        except KeyError:
            msg = _("'status' must be specified.")
            raise webob.exc.HTTPBadRequest(explanation=msg)

        # Allowed state transitions
        status_map = {'creating': ['creating', 'available', 'error'],
                      'deleting': ['deleting', 'error_deleting']}

        current_snapshot = storage.Snapshot.get_by_id(context, id)

        if current_snapshot.status not in status_map:
            msg = _("Snapshot status %(cur)s not allowed for "
                    "update_snapshot_status") % {
                        'cur': current_snapshot.status}
            raise webob.exc.HTTPBadRequest(explanation=msg)

        if status not in status_map[current_snapshot.status]:
            msg = _("Provided snapshot status %(provided)s not allowed for "
                    "snapshot with status %(current)s.") % \
                {'provided': status,
                 'current': current_snapshot.status}
            raise webob.exc.HTTPBadRequest(explanation=msg)

        update_dict = {'id': id,
                       'status': status}

        progress = body['os-update_snapshot_status'].get('progress', None)
        if progress:
            # This is expected to be a string like '73%'
            msg = _('progress must be an integer percentage')
            try:
                integer = int(progress[:-1])
            except ValueError:
                raise webob.exc.HTTPBadRequest(explanation=msg)
            if integer < 0 or integer > 100 or progress[-1] != '%':
                raise webob.exc.HTTPBadRequest(explanation=msg)

            update_dict.update({'progress': progress})

        LOG.info(_LI("Updating snapshot %(id)s with info %(dict)s"),
                 {'id': id, 'dict': update_dict})

        current_snapshot.update(update_dict)
        current_snapshot.save()
        return webob.Response(status_int=202)
예제 #29
0
    def _create(self, req, body):
        """Creates a new volume type."""
        context = req.environ['storage.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', {})
        is_public = vol_type.get('os-volume-type-access:is_public', 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)

        if not utils.is_valid_boolstr(is_public):
            msg = _("Invalid value '%s' for is_public. Accepted values: "
                    "True or False.") % is_public
            raise webob.exc.HTTPBadRequest(explanation=msg)

        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)
            raise webob.exc.HTTPNotFound(explanation=err.msg)

        return self._view_builder.show(req, vol_type)
예제 #30
0
    def restore(self, backup, volume_id, volume_file):
        """Restore the given volume backup from backup repository."""
        backup_id = backup['id']
        container = backup['container']
        object_prefix = backup['service_metadata']
        LOG.debug('starting restore of backup %(object_prefix)s '
                  'container: %(container)s, to volume %(volume_id)s, '
                  'backup: %(backup_id)s.',
                  {
                      'object_prefix': object_prefix,
                      'container': container,
                      'volume_id': volume_id,
                      'backup_id': backup_id,
                  })
        metadata = self._read_metadata(backup)
        metadata_version = metadata['version']
        LOG.debug('Restoring backup version %s', metadata_version)
        try:
            restore_func = getattr(self, self.DRIVER_VERSION_MAPPING.get(
                metadata_version))
        except TypeError:
            err = (_('No support to restore backup version %s')
                   % metadata_version)
            raise exception.InvalidBackup(reason=err)

        # Build a list of backups based on parent_id. A full backup
        # will be the last one in the list.
        backup_list = []
        backup_list.append(backup)
        current_backup = backup
        while current_backup.parent_id:
            prev_backup = storage.Backup.get_by_id(self.context,
                                                   current_backup.parent_id)
            backup_list.append(prev_backup)
            current_backup = prev_backup

        # Do a full restore first, then layer the incremental backups
        # on top of it in order.
        index = len(backup_list) - 1
        while index >= 0:
            backup1 = backup_list[index]
            index = index - 1
            metadata = self._read_metadata(backup1)
            restore_func(backup1, volume_id, metadata, volume_file)

            volume_meta = metadata.get('volume_meta', None)
            try:
                if volume_meta:
                    self.put_metadata(volume_id, volume_meta)
                else:
                    LOG.debug("No volume metadata in this backup.")
            except exception.BackupMetadataUnsupportedVersion:
                msg = _("Metadata restore failed due to incompatible version.")
                LOG.error(msg)
                raise exception.BackupOperationError(msg)

        LOG.debug('restore %(backup_id)s to %(volume_id)s finished.',
                  {'backup_id': backup_id, 'volume_id': volume_id})
예제 #31
0
파일: hosts.py 프로젝트: HybridF5/jacket
    def show(self, req, id):
        """Shows the volume usage info given by hosts.

        :param context: security context
        :param host: hostname
        :returns: expected to use HostShowTemplate.
            ex.::

                {'host': {'resource':D},..}
                D: {'host': 'hostname','project': 'admin',
                    'volume_count': 1, 'total_volume_gb': 2048}
        """
        host = id
        context = req.environ['storage.context']
        if not context.is_admin:
            msg = _("Describe-resource is admin only functionality")
            raise webob.exc.HTTPForbidden(explanation=msg)

        try:
            host_ref = storage.Service.get_by_host_and_topic(
                context, host, CONF.volume_topic)
        except exception.ServiceNotFound:
            raise webob.exc.HTTPNotFound(explanation=_("Host not found"))

        # Getting total available/used resource
        # TODO(jdg): Add summary info for Snapshots
        volume_refs = db.volume_get_all_by_host(context, host_ref.host)
        (count, sum) = db.volume_data_get_for_host(context,
                                                   host_ref.host)

        snap_count_total = 0
        snap_sum_total = 0
        resources = [{'resource': {'host': host, 'project': '(total)',
                                   'volume_count': str(count),
                                   'total_volume_gb': str(sum),
                                   'snapshot_count': str(snap_count_total),
                                   'total_snapshot_gb': str(snap_sum_total)}}]

        project_ids = [v['project_id'] for v in volume_refs]
        project_ids = list(set(project_ids))
        for project_id in project_ids:
            (count, sum) = db.volume_data_get_for_project(context, project_id)
            (snap_count, snap_sum) = (
                storage.Snapshot.snapshot_data_get_for_project(context,
                                                               project_id))
            resources.append(
                {'resource':
                    {'host': host,
                     'project': project_id,
                     'volume_count': str(count),
                     'total_volume_gb': str(sum),
                     'snapshot_count': str(snap_count),
                     'total_snapshot_gb': str(snap_sum)}})
            snap_count_total += int(snap_count)
            snap_sum_total += int(snap_sum)
        resources[0]['resource']['snapshot_count'] = str(snap_count_total)
        resources[0]['resource']['total_snapshot_gb'] = str(snap_sum_total)
        return {"host": resources}
예제 #32
0
    def update(self, req, id, body):
        """Update the consistency group.

        Expected format of the input parameter 'body':
        {
            "consistencygroup":
            {
                "name": "my_cg",
                "description": "My consistency group",
                "add_volumes": "volume-uuid-1,volume-uuid-2,..."
                "remove_volumes": "volume-uuid-8,volume-uuid-9,..."
            }
        }
        """
        LOG.debug('Update called for consistency group %s.', id)

        if not body:
            msg = _("Missing request body.")
            raise exc.HTTPBadRequest(explanation=msg)

        self.assert_valid_body(body, 'consistencygroup')
        context = req.environ['storage.context']

        consistencygroup = body.get('consistencygroup', None)
        self.validate_name_and_description(consistencygroup)
        name = consistencygroup.get('name', None)
        description = consistencygroup.get('description', None)
        add_volumes = consistencygroup.get('add_volumes', None)
        remove_volumes = consistencygroup.get('remove_volumes', None)

        if (not name and not description and not add_volumes
                and not remove_volumes):
            msg = _("Name, description, add_volumes, and remove_volumes "
                    "can not be all empty in the request body.")
            raise exc.HTTPBadRequest(explanation=msg)

        LOG.info(_LI("Updating consistency group %(id)s with name %(name)s "
                     "description: %(description)s add_volumes: "
                     "%(add_volumes)s remove_volumes: %(remove_volumes)s."), {
                         'id': id,
                         'name': name,
                         'description': description,
                         'add_volumes': add_volumes,
                         'remove_volumes': remove_volumes
                     },
                 context=context)

        try:
            group = self.consistencygroup_api.get(context, id)
            self.consistencygroup_api.update(context, group, name, description,
                                             add_volumes, remove_volumes)
        except exception.ConsistencyGroupNotFound as error:
            raise exc.HTTPNotFound(explanation=error.msg)
        except exception.InvalidConsistencyGroup as error:
            raise exc.HTTPBadRequest(explanation=error.msg)

        return webob.Response(status_int=202)
예제 #33
0
파일: tsm.py 프로젝트: HybridF5/jacket
    def delete(self, backup):
        """Delete the given backup from TSM server.

        :param backup: backup information for volume
        :raises: InvalidBackup
        """

        delete_attrs = {'Total number of objects deleted': '1'}
        delete_path, backup_mode = _get_backup_metadata(backup, 'restore')

        LOG.debug('Delete started for backup: %(backup)s, mode: %(mode)s.',
                  {'backup': backup.id,
                   'mode': backup_mode})

        try:
            out, err = utils.execute('dsmc',
                                     'delete',
                                     'backup',
                                     '-quiet',
                                     '-noprompt',
                                     '-objtype=%s' % backup_mode,
                                     '-password=%s' % self.tsm_password,
                                     delete_path,
                                     run_as_root=True,
                                     check_exit_code=False)

        except processutils.ProcessExecutionError as exc:
            err = (_('delete: %(vol_id)s failed to run dsmc with '
                     'stdout: %(out)s\n stderr: %(err)s')
                   % {'vol_id': backup.volume_id,
                      'out': exc.stdout,
                      'err': exc.stderr})
            LOG.error(err)
            raise exception.InvalidBackup(reason=err)
        except exception.Error as exc:
            err = (_('delete: %(vol_id)s failed to run dsmc '
                     'due to invalid arguments with '
                     'stdout: %(out)s\n stderr: %(err)s')
                   % {'vol_id': backup.volume_id,
                      'out': exc.stdout,
                      'err': exc.stderr})
            LOG.error(err)
            raise exception.InvalidBackup(reason=err)

        success = _check_dsmc_output(out, delete_attrs)
        if not success:
            # log error if tsm cannot delete the backup object
            # but do not raise exception so that storage backup
            # object can be removed.
            LOG.error(_LE('delete: %(vol_id)s failed with '
                          'stdout: %(out)s\n stderr: %(err)s'),
                      {'vol_id': backup.volume_id,
                       'out': out,
                       'err': err})

        LOG.debug('Delete %s finished.', backup['id'])
예제 #34
0
    def update(self, req, id, body):
        """Update the consistency group.

        Expected format of the input parameter 'body':
        {
            "consistencygroup":
            {
                "name": "my_cg",
                "description": "My consistency group",
                "add_volumes": "volume-uuid-1,volume-uuid-2,..."
                "remove_volumes": "volume-uuid-8,volume-uuid-9,..."
            }
        }
        """
        LOG.debug('Update called for consistency group %s.', id)

        if not body:
            msg = _("Missing request body.")
            raise exc.HTTPBadRequest(explanation=msg)

        self.assert_valid_body(body, 'consistencygroup')
        context = req.environ['storage.context']

        consistencygroup = body.get('consistencygroup', None)
        self.validate_name_and_description(consistencygroup)
        name = consistencygroup.get('name', None)
        description = consistencygroup.get('description', None)
        add_volumes = consistencygroup.get('add_volumes', None)
        remove_volumes = consistencygroup.get('remove_volumes', None)

        if (not name and not description and not add_volumes
                and not remove_volumes):
            msg = _("Name, description, add_volumes, and remove_volumes "
                    "can not be all empty in the request body.")
            raise exc.HTTPBadRequest(explanation=msg)

        LOG.info(_LI("Updating consistency group %(id)s with name %(name)s "
                     "description: %(description)s add_volumes: "
                     "%(add_volumes)s remove_volumes: %(remove_volumes)s."),
                 {'id': id, 'name': name,
                  'description': description,
                  'add_volumes': add_volumes,
                  'remove_volumes': remove_volumes},
                 context=context)

        try:
            group = self.consistencygroup_api.get(context, id)
            self.consistencygroup_api.update(
                context, group, name, description,
                add_volumes, remove_volumes)
        except exception.ConsistencyGroupNotFound as error:
            raise exc.HTTPNotFound(explanation=error.msg)
        except exception.InvalidConsistencyGroup as error:
            raise exc.HTTPBadRequest(explanation=error.msg)

        return webob.Response(status_int=202)
예제 #35
0
 def validate_update(self, body):
     update = {}
     try:
         update['status'] = body['status'].lower()
     except (TypeError, KeyError):
         raise exc.HTTPBadRequest(explanation=_("Must specify 'status'"))
     if update['status'] not in self.valid_status:
         raise exc.HTTPBadRequest(
             explanation=_("Must specify a valid status"))
     return update
예제 #36
0
 def validate_update(self, body):
     update = {}
     try:
         update['status'] = body['status'].lower()
     except (TypeError, KeyError):
         raise exc.HTTPBadRequest(explanation=_("Must specify 'status'"))
     if update['status'] not in self.valid_status:
         raise exc.HTTPBadRequest(
             explanation=_("Must specify a valid status"))
     return update
예제 #37
0
    def find_retype_host(self,
                         context,
                         request_spec,
                         filter_properties=None,
                         migration_policy='never'):
        """Find a host that can accept the volume with its new type."""
        filter_properties = filter_properties or {}
        current_host = request_spec['volume_properties']['host']

        # The volume already exists on this host, and so we shouldn't check if
        # it can accept the volume again in the CapacityFilter.
        filter_properties['vol_exists_on'] = current_host

        weighed_hosts = self._get_weighted_candidates(context, request_spec,
                                                      filter_properties)
        if not weighed_hosts:
            raise exception.NoValidHost(
                reason=_('No valid hosts for volume '
                         '%(id)s with type %(type)s') % {
                             'id': request_spec['volume_id'],
                             'type': request_spec['volume_type']
                         })

        for weighed_host in weighed_hosts:
            host_state = weighed_host.obj
            if host_state.host == current_host:
                return host_state

        if utils.extract_host(current_host, 'pool') is None:
            # legacy volumes created before pool is introduced has no pool
            # info in host.  But host_state.host always include pool level
            # info. In this case if above exact match didn't work out, we
            # find host_state that are of the same host of volume being
            # retyped. In other words, for legacy volumes, retyping could
            # cause migration between pools on same host, which we consider
            # it is different from migration between hosts thus allow that
            # to happen even migration policy is 'never'.
            for weighed_host in weighed_hosts:
                host_state = weighed_host.obj
                backend = utils.extract_host(host_state.host, 'backend')
                if backend == current_host:
                    return host_state

        if migration_policy == 'never':
            raise exception.NoValidHost(
                reason=_('Current host not valid for '
                         'volume %(id)s with type '
                         '%(type)s, migration not '
                         'allowed') % {
                             'id': request_spec['volume_id'],
                             'type': request_spec['volume_type']
                         })

        top_host = self._choose_top_host(weighed_hosts, request_spec)
        return top_host.obj
예제 #38
0
파일: api.py 프로젝트: bopopescu/jacket
    def delete(self, context, group, force=False):
        if not group.host:
            self.update_quota(context, group, -1, group.project_id)

            LOG.debug(
                "No host for consistency group %s. Deleting from "
                "the database.", group.id)
            group.destroy()

            return

        if not force and group.status not in ([
                c_fields.ConsistencyGroupStatus.AVAILABLE,
                c_fields.ConsistencyGroupStatus.ERROR
        ]):
            msg = _("Consistency group status must be available or error, "
                    "but current status is: %s") % group.status
            raise exception.InvalidConsistencyGroup(reason=msg)

        cgsnapshots = storage.CGSnapshotList.get_all_by_group(
            context.elevated(), group.id)
        if cgsnapshots:
            msg = _("Consistency group %s still has dependent "
                    "cgsnapshots.") % group.id
            LOG.error(msg)
            raise exception.InvalidConsistencyGroup(reason=msg)

        volumes = self.db.volume_get_all_by_group(context.elevated(), group.id)

        if volumes and not force:
            msg = _("Consistency group %s still contains volumes. "
                    "The force flag is required to delete it.") % group.id
            LOG.error(msg)
            raise exception.InvalidConsistencyGroup(reason=msg)

        for volume in volumes:
            if volume['attach_status'] == "attached":
                msg = _("Volume in consistency group %s is attached. "
                        "Need to detach first.") % group.id
                LOG.error(msg)
                raise exception.InvalidConsistencyGroup(reason=msg)

            snapshots = storage.SnapshotList.get_all_for_volume(
                context, volume['id'])
            if snapshots:
                msg = _("Volume in consistency group still has "
                        "dependent snapshots.")
                LOG.error(msg)
                raise exception.InvalidConsistencyGroup(reason=msg)

        group.status = c_fields.ConsistencyGroupStatus.DELETING
        group.terminated_at = timeutils.utcnow()
        group.save()

        self.jacket_rpcapi.delete_consistencygroup(context, group)
예제 #39
0
    def _volume_upload_image(self, req, id, body):
        """Uploads the specified volume to image service."""
        context = req.environ['storage.context']
        params = body['os-volume_upload_image']
        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"]
        }

        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}
예제 #40
0
def remove_volume_type_access(context, volume_type_id, project_id):
    """Remove access to volume type for project_id."""
    if volume_type_id is None:
        msg = _("volume_type_id cannot be None")
        raise exception.InvalidVolumeType(reason=msg)
    elevated = context if context.is_admin else context.elevated()
    if is_public_volume_type(elevated, volume_type_id):
        msg = _("Type access modification is not applicable to public volume "
                "type.")
        raise exception.InvalidVolumeType(reason=msg)
    return db.volume_type_access_remove(elevated, volume_type_id, project_id)
예제 #41
0
def upload_volume(context,
                  image_service,
                  image_meta,
                  volume_path,
                  volume_format='raw',
                  run_as_root=True):
    image_id = image_meta['id']
    if (image_meta['disk_format'] == volume_format):
        LOG.debug("%s was %s, no need to convert to %s", image_id,
                  volume_format, image_meta['disk_format'])
        if os.name == 'nt' or os.access(volume_path, os.R_OK):
            with open(volume_path, 'rb') as image_file:
                image_service.update(context, image_id, {}, image_file)
        else:
            with utils.temporary_chown(volume_path):
                with open(volume_path) as image_file:
                    image_service.update(context, image_id, {}, image_file)
        return

    with temporary_file() as tmp:
        LOG.debug("%s was %s, converting to %s", image_id, volume_format,
                  image_meta['disk_format'])

        data = qemu_img_info(volume_path, run_as_root=run_as_root)
        backing_file = data.backing_file
        fmt = data.file_format
        if backing_file is not None:
            # Disallow backing files as a security measure.
            # This prevents a user from writing an image header into a raw
            # volume with a backing file pointing to data they wish to
            # access.
            raise exception.ImageUnacceptable(
                image_id=image_id,
                reason=_("fmt=%(fmt)s backed by:%(backing_file)s") % {
                    'fmt': fmt,
                    'backing_file': backing_file
                })

        convert_image(volume_path,
                      tmp,
                      image_meta['disk_format'],
                      run_as_root=run_as_root)

        data = qemu_img_info(tmp, run_as_root=run_as_root)
        if data.file_format != image_meta['disk_format']:
            raise exception.ImageUnacceptable(
                image_id=image_id,
                reason=_("Converted to %(f1)s, but format is now %(f2)s") % {
                    'f1': image_meta['disk_format'],
                    'f2': data.file_format
                })

        with open(tmp, 'rb') as image_file:
            image_service.update(context, image_id, {}, image_file)
예제 #42
0
    def create_from_src(self, req, body):
        """Create a new consistency group from a source.

        The source can be a CG snapshot or a CG. Note that
        this does not require volume_types as the "create"
        API above.
        """
        LOG.debug('Creating new consistency group %s.', body)
        self.assert_valid_body(body, 'consistencygroup-from-src')

        context = req.environ['storage.context']
        consistencygroup = body['consistencygroup-from-src']
        self.validate_name_and_description(consistencygroup)
        name = consistencygroup.get('name', None)
        description = consistencygroup.get('description', None)
        cgsnapshot_id = consistencygroup.get('cgsnapshot_id', None)
        source_cgid = consistencygroup.get('source_cgid', None)
        if not cgsnapshot_id and not source_cgid:
            msg = _("Either 'cgsnapshot_id' or 'source_cgid' must be "
                    "provided to create consistency group %(name)s "
                    "from source.") % {'name': name}
            raise exc.HTTPBadRequest(explanation=msg)

        if cgsnapshot_id and source_cgid:
            msg = _("Cannot provide both 'cgsnapshot_id' and 'source_cgid' "
                    "to create consistency group %(name)s from "
                    "source.") % {'name': name}
            raise exc.HTTPBadRequest(explanation=msg)

        if cgsnapshot_id:
            LOG.info(_LI("Creating consistency group %(name)s from "
                         "cgsnapshot %(snap)s."),
                     {'name': name, 'snap': cgsnapshot_id},
                     context=context)
        elif source_cgid:
            LOG.info(_LI("Creating consistency group %(name)s from "
                         "source consistency group %(source_cgid)s."),
                     {'name': name, 'source_cgid': source_cgid},
                     context=context)

        try:
            new_consistencygroup = self.consistencygroup_api.create_from_src(
                context, name, description, cgsnapshot_id, source_cgid)
        except exception.InvalidConsistencyGroup as error:
            raise exc.HTTPBadRequest(explanation=error.msg)
        except exception.CgSnapshotNotFound as error:
            raise exc.HTTPNotFound(explanation=error.msg)
        except exception.ConsistencyGroupNotFound as error:
            raise exc.HTTPNotFound(explanation=error.msg)
        except exception.CinderException as error:
            raise exc.HTTPBadRequest(explanation=error.msg)

        retval = self._view_builder.summary(req, new_consistencygroup)
        return retval
예제 #43
0
    def _volume_upload_image(self, req, id, body):
        """Uploads the specified volume to image service."""
        context = req.environ['storage.context']
        params = body['os-volume_upload_image']
        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"]}

        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}
예제 #44
0
def remove_volume_type_access(context, volume_type_id, project_id):
    """Remove access to volume type for project_id."""
    if volume_type_id is None:
        msg = _("volume_type_id cannot be None")
        raise exception.InvalidVolumeType(reason=msg)
    elevated = context if context.is_admin else context.elevated()
    if is_public_volume_type(elevated, volume_type_id):
        msg = _("Type access modification is not applicable to public volume "
                "type.")
        raise exception.InvalidVolumeType(reason=msg)
    return db.volume_type_access_remove(elevated, volume_type_id, project_id)
예제 #45
0
    def create(self, req, body):
        """Creates a new snapshot."""
        kwargs = {}
        context = req.environ['storage.context']

        self.assert_valid_body(body, 'snapshot')

        snapshot = body['snapshot']
        kwargs['metadata'] = snapshot.get('metadata', None)

        try:
            volume_id = snapshot['volume_id']
        except KeyError:
            msg = _("'volume_id' must be specified")
            raise exc.HTTPBadRequest(explanation=msg)

        try:
            volume = self.volume_api.get(context, volume_id)
        except exception.VolumeNotFound as error:
            raise exc.HTTPNotFound(explanation=error.msg)
        force = snapshot.get('force', False)
        msg = _LI("Create snapshot from volume %s")
        LOG.info(msg, volume_id, context=context)
        self.validate_name_and_description(snapshot)

        # NOTE(thingee): v2 API allows name instead of display_name
        if 'name' in snapshot:
            snapshot['display_name'] = snapshot.pop('name')

        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 exception.InvalidParameterValue(err=msg)

        if force:
            new_snapshot = self.volume_api.create_snapshot_force(
                context,
                volume,
                snapshot.get('display_name'),
                snapshot.get('description'),
                **kwargs)
        else:
            new_snapshot = self.volume_api.create_snapshot(
                context,
                volume,
                snapshot.get('display_name'),
                snapshot.get('description'),
                **kwargs)
        req.cache_db_snapshot(new_snapshot)

        return self._view_builder.detail(req, new_snapshot)
예제 #46
0
    def create(self):
        try:
            ssh = paramiko.SSHClient()
            if ',' in self.hosts_key_file:
                files = self.hosts_key_file.split(',')
                for f in files:
                    ssh.load_host_keys(f)
            else:
                ssh.load_host_keys(self.hosts_key_file)
            # If strict_ssh_host_key_policy is set we want to reject, by
            # default if there is not entry in the known_hosts file.
            # Otherwise we use AutoAddPolicy which accepts on the first
            # Connect but fails if the keys change.  load_host_keys can
            # handle hashed known_host entries.
            if self.strict_ssh_host_key_policy:
                ssh.set_missing_host_key_policy(paramiko.RejectPolicy())
            else:
                ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            if self.password:
                ssh.connect(self.ip,
                            port=self.port,
                            username=self.login,
                            password=self.password,
                            timeout=self.conn_timeout)
            elif self.privatekey:
                pkfile = os.path.expanduser(self.privatekey)
                privatekey = paramiko.RSAKey.from_private_key_file(pkfile)
                ssh.connect(self.ip,
                            port=self.port,
                            username=self.login,
                            pkey=privatekey,
                            timeout=self.conn_timeout)
            else:
                msg = _("Specify a password or private_key")
                raise exception.CinderException(msg)

            # Paramiko by default sets the socket timeout to 0.1 seconds,
            # ignoring what we set through the sshclient. This doesn't help for
            # keeping long lived connections. Hence we have to bypass it, by
            # overriding it after the transport is initialized. We are setting
            # the sockettimeout to None and setting a keepalive packet so that,
            # the server will keep the connection open. All that does is send
            # a keepalive packet every ssh_conn_timeout seconds.
            if self.conn_timeout:
                transport = ssh.get_transport()
                transport.sock.settimeout(None)
                transport.set_keepalive(self.conn_timeout)
            return ssh
        except Exception as e:
            msg = _("Error connecting via ssh: %s") % six.text_type(e)
            LOG.error(msg)
            raise paramiko.SSHException(msg)
예제 #47
0
    def reenable(self, context, vol):
        if vol['replication_status'] == 'disabled':
            msg = _("Replication is not enabled")
            raise exception.ReplicationError(reason=msg, volume_id=vol['id'])
        if vol['replication_status'] not in REENABLE_PROCEED_STATUS:
            msg = _("Replication status for volume must be inactive,"
                    " active-stopped, or error, but current status "
                    "is: %s") % vol['replication_status']
            raise exception.ReplicationError(reason=msg, volume_id=vol['id'])

        volume_utils.notify_about_replication_usage(context, vol, 'sync')
        self.jacket_rpcapi.reenable_replication(context, vol)
예제 #48
0
파일: backup.py 프로젝트: bopopescu/jacket
    def decode_record(backup_url):
        """Deserialize backup metadata from string into a dictionary.

        :raises: InvalidInput
        """
        try:
            return jsonutils.loads(base64.decode_as_text(backup_url))
        except TypeError:
            msg = _("Can't decode backup record.")
        except ValueError:
            msg = _("Can't parse backup record.")
        raise exception.InvalidInput(reason=msg)
예제 #49
0
    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            if 'cgsnapshots' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('cgsnapshots changed'))
            if 'volumes' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('volumes changed'))

            db.consistencygroup_update(self._context, self.id, updates)
            self.obj_reset_changes()
예제 #50
0
    def create(self):
        try:
            ssh = paramiko.SSHClient()
            if ',' in self.hosts_key_file:
                files = self.hosts_key_file.split(',')
                for f in files:
                    ssh.load_host_keys(f)
            else:
                ssh.load_host_keys(self.hosts_key_file)
            # If strict_ssh_host_key_policy is set we want to reject, by
            # default if there is not entry in the known_hosts file.
            # Otherwise we use AutoAddPolicy which accepts on the first
            # Connect but fails if the keys change.  load_host_keys can
            # handle hashed known_host entries.
            if self.strict_ssh_host_key_policy:
                ssh.set_missing_host_key_policy(paramiko.RejectPolicy())
            else:
                ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            if self.password:
                ssh.connect(self.ip,
                            port=self.port,
                            username=self.login,
                            password=self.password,
                            timeout=self.conn_timeout)
            elif self.privatekey:
                pkfile = os.path.expanduser(self.privatekey)
                privatekey = paramiko.RSAKey.from_private_key_file(pkfile)
                ssh.connect(self.ip,
                            port=self.port,
                            username=self.login,
                            pkey=privatekey,
                            timeout=self.conn_timeout)
            else:
                msg = _("Specify a password or private_key")
                raise exception.CinderException(msg)

            # Paramiko by default sets the socket timeout to 0.1 seconds,
            # ignoring what we set through the sshclient. This doesn't help for
            # keeping long lived connections. Hence we have to bypass it, by
            # overriding it after the transport is initialized. We are setting
            # the sockettimeout to None and setting a keepalive packet so that,
            # the server will keep the connection open. All that does is send
            # a keepalive packet every ssh_conn_timeout seconds.
            if self.conn_timeout:
                transport = ssh.get_transport()
                transport.sock.settimeout(None)
                transport.set_keepalive(self.conn_timeout)
            return ssh
        except Exception as e:
            msg = _("Error connecting via ssh: %s") % six.text_type(e)
            LOG.error(msg)
            raise paramiko.SSHException(msg)
예제 #51
0
파일: backup.py 프로젝트: HybridF5/jacket
    def decode_record(backup_url):
        """Deserialize backup metadata from string into a dictionary.

        :raises: InvalidInput
        """
        try:
            return jsonutils.loads(base64.decode_as_text(backup_url))
        except TypeError:
            msg = _("Can't decode backup record.")
        except ValueError:
            msg = _("Can't parse backup record.")
        raise exception.InvalidInput(reason=msg)
예제 #52
0
    def save(self):
        updates = self.cinder_obj_get_changes()
        if updates:
            if 'cgsnapshots' in updates:
                raise exception.ObjectActionError(
                    action='save', reason=_('cgsnapshots changed'))
            if 'volumes' in updates:
                raise exception.ObjectActionError(action='save',
                                                  reason=_('volumes changed'))

            db.consistencygroup_update(self._context, self.id, updates)
            self.obj_reset_changes()
예제 #53
0
    def host_passes(self, host_state, filter_properties):
        context = filter_properties['context']
        host = volume_utils.extract_host(host_state.host, 'host')

        scheduler_hints = filter_properties.get('scheduler_hints') or {}
        instance_uuid = scheduler_hints.get(HINT_KEYWORD, None)

        # Without 'local_to_instance' hint
        if not instance_uuid:
            return True

        if not uuidutils.is_uuid_like(instance_uuid):
            raise exception.InvalidUUID(uuid=instance_uuid)

        # TODO(adrienverge): Currently it is not recommended to allow instance
        # migrations for hypervisors where this hint will be used. In case of
        # instance migration, a previously locally-created volume will not be
        # automatically migrated. Also in case of instance migration during the
        # volume's scheduling, the result is unpredictable. A future
        # enhancement would be to subscribe to Nova migration events (e.g. via
        # Ceilometer).

        # First, lookup for already-known information in local cache
        if instance_uuid in self._cache:
            return self._cache[instance_uuid] == host

        if not self._nova_has_extended_server_attributes(context):
            LOG.warning(
                _LW('Hint "%s" dropped because '
                    'ExtendedServerAttributes not active in Nova.'),
                HINT_KEYWORD)
            raise exception.CinderException(
                _('Hint "%s" not supported.') % HINT_KEYWORD)

        server = nova.API().get_server(context,
                                       instance_uuid,
                                       privileged_user=True,
                                       timeout=REQUESTS_TIMEOUT)

        if not hasattr(server, INSTANCE_HOST_PROP):
            LOG.warning(
                _LW('Hint "%s" dropped because Nova did not return '
                    'enough information. Either Nova policy needs to '
                    'be changed or a privileged account for Nova '
                    'should be specified in conf.'), HINT_KEYWORD)
            raise exception.CinderException(
                _('Hint "%s" not supported.') % HINT_KEYWORD)

        self._cache[instance_uuid] = getattr(server, INSTANCE_HOST_PROP)

        # Match if given instance is hosted on host
        return self._cache[instance_uuid] == host
예제 #54
0
파일: manager.py 프로젝트: bopopescu/jacket
    def export_record(self, context, backup):
        """Export all volume backup metadata details to allow clean import.

        Export backup metadata so it could be re-imported into the database
        without any prerequisite in the backup database.

        :param context: running context
        :param backup: backup object to export
        :returns: backup_record - a description of how to import the backup
        :returns: contains 'backup_url' - how to import the backup, and
        :returns: 'backup_service' describing the needed driver.
        :raises: InvalidBackup
        """
        LOG.info(_LI('Export record started, backup: %s.'), backup.id)

        expected_status = fields.BackupStatus.AVAILABLE
        actual_status = backup.status
        if actual_status != expected_status:
            err = (_('Export backup aborted, expected backup status '
                     '%(expected_status)s but got %(actual_status)s.') % {
                         'expected_status': expected_status,
                         'actual_status': actual_status
                     })
            raise exception.InvalidBackup(reason=err)

        backup_record = {}
        backup_record['backup_service'] = backup.service
        backup_service = self._map_service_to_driver(backup.service)
        configured_service = self.driver_name
        if backup_service != configured_service:
            err = (_('Export record aborted, the backup service currently'
                     ' configured [%(configured_service)s] is not the'
                     ' backup service that was used to create this'
                     ' backup [%(backup_service)s].') % {
                         'configured_service': configured_service,
                         'backup_service': backup_service
                     })
            raise exception.InvalidBackup(reason=err)

        # Call driver to create backup description string
        try:
            backup_service = self.service.get_backup_driver(context)
            driver_info = backup_service.export_record(backup)
            backup_url = backup.encode_record(driver_info=driver_info)
            backup_record['backup_url'] = backup_url
        except Exception as err:
            msg = six.text_type(err)
            raise exception.InvalidBackup(reason=msg)

        LOG.info(_LI('Export record finished, backup %s exported.'), backup.id)
        return backup_record
예제 #55
0
파일: volumes.py 프로젝트: HybridF5/jacket
    def _image_uuid_from_href(self, image_href):
        # If the image href was generated by nova api, strip image_href
        # down to an id.
        try:
            image_uuid = image_href.split('/').pop()
        except (TypeError, AttributeError):
            msg = _("Invalid imageRef provided.")
            raise exc.HTTPBadRequest(explanation=msg)

        if not uuidutils.is_uuid_like(image_uuid):
            msg = _("Invalid imageRef provided.")
            raise exc.HTTPBadRequest(explanation=msg)

        return image_uuid
예제 #56
0
파일: volumes.py 프로젝트: bopopescu/jacket
    def _image_uuid_from_href(self, image_href):
        # If the image href was generated by nova api, strip image_href
        # down to an id.
        try:
            image_uuid = image_href.split('/').pop()
        except (TypeError, AttributeError):
            msg = _("Invalid imageRef provided.")
            raise exc.HTTPBadRequest(explanation=msg)

        if not uuidutils.is_uuid_like(image_uuid):
            msg = _("Invalid imageRef provided.")
            raise exc.HTTPBadRequest(explanation=msg)

        return image_uuid
예제 #57
0
    def create(self, req, body):
        """Creates a new snapshot."""
        kwargs = {}
        context = req.environ['storage.context']

        if not self.is_valid_body(body, 'snapshot'):
            raise exc.HTTPUnprocessableEntity()

        snapshot = body['snapshot']
        kwargs['metadata'] = snapshot.get('metadata', None)

        try:
            volume_id = snapshot['volume_id']
        except KeyError:
            msg = _("'volume_id' must be specified")
            raise exc.HTTPBadRequest(explanation=msg)

        try:
            volume = self.volume_api.get(context, volume_id)
        except exception.NotFound:
            raise exc.HTTPNotFound()

        force = snapshot.get('force', False)
        msg = _LI("Create snapshot from volume %s")
        LOG.info(msg, volume_id, context=context)

        if not utils.is_valid_boolstr(force):
            msg = _("Invalid value '%s' for force. ") % force
            raise exception.InvalidParameterValue(err=msg)

        if strutils.bool_from_string(force):
            new_snapshot = self.volume_api.create_snapshot_force(
                context,
                volume,
                snapshot.get('display_name'),
                snapshot.get('display_description'),
                **kwargs)
        else:
            new_snapshot = self.volume_api.create_snapshot(
                context,
                volume,
                snapshot.get('display_name'),
                snapshot.get('display_description'),
                **kwargs)
        req.cache_db_snapshot(new_snapshot)

        retval = _translate_snapshot_detail_view(new_snapshot)

        return {'snapshot': retval}