Exemple #1
0
def get_checked_field(document, name, value_type, default_value):
    """Validates and retrieves a typed field from a document.

    This function attempts to look up doc[name], and raises
    appropriate HTTP errors if the field is missing or not an
    instance of the given type.

    :param document: dict-like object
    :param name: field name
    :param value_type: expected value type, or '*' to accept any type
    :param default_value: Default value to use if the value is missing,
        or None to make the value required.
    :raises: HTTPBadRequest if the field is missing or not an
        instance of value_type
    :returns: value obtained from doc[name]
    """

    try:
        value = document[name]
    except KeyError:
        if default_value is not None:
            value = default_value
        else:
            description = _(u'Missing "{name}" field.').format(name=name)
            raise errors.HTTPBadRequestBody(description)

    # PERF(kgriffs): We do our own little spec thing because it is way
    # faster than jsonschema.
    if value_type == '*' or isinstance(value, value_type):
        return value

    description = _(u'The value of the "{name}" field must be a {vtype}.')
    description = description.format(name=name, vtype=value_type.__name__)
    raise errors.HTTPBadRequestBody(description)
Exemple #2
0
    def on_patch(self, request, response, project_id, flavor):
        """Allows one to update a flavors's pool and/or capabilities.

        This method expects the user to submit a JSON object
        containing at least one of: 'pool', 'capabilities'. If
        none are found, the request is flagged as bad. There is also
        strict format checking through the use of
        jsonschema. Appropriate errors are returned in each case for
        badly formatted input.

        :returns: HTTP | [200, 400]
        """

        LOG.debug(u'PATCH flavor - name: %s', flavor)
        data = wsgi_utils.load(request)

        EXPECT = ('pool', 'capabilities')
        if not any([(field in data) for field in EXPECT]):
            LOG.debug(u'PATCH flavor, bad params')
            raise wsgi_errors.HTTPBadRequestBody(
                'One of `pool` or `capabilities` needs '
                'to be specified')

        for field in EXPECT:
            wsgi_utils.validate(self._validators[field], data)

        fields = common_utils.fields(data,
                                     EXPECT,
                                     pred=lambda v: v is not None)

        try:
            self._ctrl.update(flavor, project=project_id, **fields)
        except errors.FlavorDoesNotExist as ex:
            LOG.exception(ex)
            raise falcon.HTTPNotFound()
Exemple #3
0
    def on_put(self, request, response, project_id, pool):
        """Registers a new pool. Expects the following input:

        ::

            {"weight": 100, "uri": ""}

        An options object may also be provided.

        :returns: HTTP | [201, 204]
        """

        LOG.debug(u'PUT pool - name: %s', pool)

        conf = self._ctrl.driver.conf
        data = wsgi_utils.load(request)
        wsgi_utils.validate(self._validators['create'], data)
        if not storage_utils.can_connect(data['uri'], conf=conf):
            raise wsgi_errors.HTTPBadRequestBody(
                'cannot connect to %s' % data['uri']
            )
        try:
            self._ctrl.create(pool, weight=data['weight'],
                              uri=data['uri'],
                              group=data.get('group'),
                              options=data.get('options', {}))
            response.status = falcon.HTTP_201
            response.location = request.path
        except errors.PoolCapabilitiesMismatch as e:
            LOG.exception(e)
            title = _(u'Unable to create pool')
            raise falcon.HTTPBadRequest(title, six.text_type(e))
        except errors.PoolAlreadyExists as e:
            LOG.exception(e)
            raise wsgi_errors.HTTPConflict(six.text_type(e))
Exemple #4
0
    def on_patch(self, request, response, project_id, flavor):
        """Allows one to update a flavors'pool list.

        This method expects the user to submit a JSON object
        containing 'pool list'. If none is found,
        the request is flagged as bad. There is also strict format
        checking through the use of jsonschema. Appropriate errors
        are returned in each case for badly formatted input.

        :returns: HTTP | [200, 400]
        """
        LOG.debug(u'PATCH flavor - name: %s', flavor)
        data = wsgi_utils.load(request)
        field = 'pool_list'
        if field not in data:
            LOG.debug(u'PATCH flavor, bad params')
            raise wsgi_errors.HTTPBadRequestBody(
                '`pool_list` needs to be specified')

        wsgi_utils.validate(self._validators[field], data)
        pool_list = data.get('pool_list')
        # NOTE(gengchc2): If pool_list is not None, configuration flavor is
        # used by the new schema.
        # a list of pools is required.
        if pool_list is not None:
            self._on_patch_by_pool_list(request, response, project_id, flavor,
                                        pool_list)
Exemple #5
0
    def on_put(self, request, response, project_id, pool):
        """Registers a new pool. Expects the following input:

        ::

            {"weight": 100, "uri": ""}

        An options object may also be provided.

        :returns: HTTP | [201, 204]
        """

        LOG.debug(u'PUT pool - name: %s', pool)

        conf = self._ctrl.driver.conf
        data = wsgi_utils.load(request)
        wsgi_utils.validate(self._validators['create'], data)
        if not storage_utils.can_connect(data['uri'], conf=conf):
            raise wsgi_errors.HTTPBadRequestBody('cannot connect to %s' %
                                                 data['uri'])
        self._ctrl.create(pool,
                          weight=data['weight'],
                          uri=data['uri'],
                          options=data.get('options', {}))
        response.status = falcon.HTTP_201
        response.location = request.path
Exemple #6
0
    def on_patch(self, request, response, project_id, flavor):
        """Allows one to update a flavors'pool list.

        This method expects the user to submit a JSON object
        containing 'pool_group' or 'pool list'. If none is found,
        the request is flagged as bad. There is also strict format
        checking through the use of jsonschema. Appropriate errors
        are returned in each case for badly formatted input.

        :returns: HTTP | [200, 400]
        """

        LOG.debug(u'PATCH flavor - name: %s', flavor)
        data = wsgi_utils.load(request)
        # NOTE(gengchc2): remove pool_group in R release.
        EXPECT = ('pool_group', 'pool', 'pool_list')
        if not any([(field in data) for field in EXPECT]):
            LOG.debug(u'PATCH flavor, bad params')
            raise wsgi_errors.HTTPBadRequestBody(
                '`pool_group` or `pool` or `pool_list` needs to be specified')

        for field in EXPECT:
            wsgi_utils.validate(self._validators[field], data)
        LOG.debug(u'The pool_group will be removed in Rocky release.')
        pool_group = data.get('pool_group') or data.get('pool')
        pool_list = data.get('pool_list')
        # NOTE(gengchc2): If pool_list is not None, configuration flavor is
        # used by the new schema.
        # a list of pools is required.
        if pool_list is not None:
            self._on_patch_by_pool_list(request, response, project_id, flavor,
                                        pool_list)
        else:
            self._on_patch_by_group(request, response, project_id, flavor,
                                    pool_group)
Exemple #7
0
    def on_patch(self, request, response, project_id, pool):
        """Allows one to update a pool's weight, uri, and/or options.

        This method expects the user to submit a JSON object
        containing at least one of: 'uri', 'weight', 'flavor',
        'options'.If none are found, the request is flagged as bad.
        There is also strict format checking through the use of
        jsonschema. Appropriate errors are returned in each case for
        badly formatted input.

        :returns: HTTP | 200,400
        """

        LOG.debug(u'PATCH pool - name: %s', pool)
        data = wsgi_utils.load(request)

        EXPECT = ('weight', 'uri', 'flavor', 'options')
        if not any([(field in data) for field in EXPECT]):
            LOG.debug(u'PATCH pool, bad params')
            raise wsgi_errors.HTTPBadRequestBody(
                'One of `uri`, `weight`, `flavor`,'
                ' or `options` needs '
                'to be specified'
            )

        for field in EXPECT:
            wsgi_utils.validate(self._validators[field], data)

        conf = self._ctrl.driver.conf
        if 'uri' in data and not storage_utils.can_connect(data['uri'],
                                                           conf=conf):
            raise wsgi_errors.HTTPBadRequestBody(
                'cannot connect to %s' % data['uri']
            )
        fields = common_utils.fields(data, EXPECT,
                                     pred=lambda v: v is not None)
        resp_data = None
        try:
            self._ctrl.update(pool, **fields)
            resp_data = self._ctrl.get(pool, False)
        except errors.PoolDoesNotExist as ex:
            LOG.exception('Pool "%s" does not exist', pool)
            raise wsgi_errors.HTTPNotFound(six.text_type(ex))

        resp_data['href'] = request.path
        response.body = transport_utils.to_json(resp_data)
Exemple #8
0
def validate(validator, document):
    """Verifies a document against a schema.

    :param validator: a validator to use to check validity
    :type validator: jsonschema.Draft4Validator
    :param document: document to check
    :type document: dict
    :raises: errors.HTTPBadRequestBody
    """
    try:
        validator.validate(document)
    except jsonschema.ValidationError as ex:
        raise errors.HTTPBadRequestBody('{0}: {1}'.format(ex.args, ex.message))
Exemple #9
0
def deserialize(stream, len):
    """Deserializes JSON from a file-like stream.

    This function deserializes JSON from a stream, including
    translating read and parsing errors to HTTP error types.

    :param stream: file-like object from which to read an object or
        array of objects.
    :param len: number of bytes to read from stream
    :raises HTTPBadRequest: if the request is invalid
    :raises HTTPServiceUnavailable: if the http service is unavailable
    """

    if len is None:
        description = _(u'Request body can not be empty')
        raise errors.HTTPBadRequestBody(description)

    try:
        # TODO(kgriffs): read_json should stream the resulting list
        # of messages, returning a generator rather than buffering
        # everything in memory (bp/streaming-serialization).
        return utils.read_json(stream, len)

    except utils.MalformedJSON as ex:
        LOG.debug(ex)
        description = _(u'Request body could not be parsed.')
        raise errors.HTTPBadRequestBody(description)

    except utils.OverflowedJSONInteger as ex:
        LOG.debug(ex)
        description = _(u'JSON contains integer that is too large.')
        raise errors.HTTPBadRequestBody(description)

    except Exception:
        # Error while reading from the network/server
        description = _(u'Request body could not be read.')
        LOG.exception(description)
        raise errors.HTTPServiceUnavailable(description)
Exemple #10
0
def load(req):
    """Reads request body, raising an exception if it is not JSON.

    :param req: The request object to read from
    :type req: falcon.Request
    :return: a dictionary decoded from the JSON stream
    :rtype: dict
    :raises: errors.HTTPBadRequestBody
    """
    try:
        return utils.read_json(req.stream, req.content_length)
    except (utils.MalformedJSON, utils.OverflowedJSONInteger) as ex:
        LOG.exception(ex)
        raise errors.HTTPBadRequestBody('JSON could not be parsed.')
Exemple #11
0
    def on_patch(self, request, response, project_id, flavor):
        """Allows one to update a flavors's pool_group.

        This method expects the user to submit a JSON object
        containing 'pool_group'. If none is found, the request is flagged
        as bad. There is also strict format checking through the use of
        jsonschema. Appropriate errors are returned in each case for
        badly formatted input.

        :returns: HTTP | [200, 400]
        """

        LOG.debug(u'PATCH flavor - name: %s', flavor)
        data = wsgi_utils.load(request)

        EXPECT = ('pool_group', 'pool')
        if not any([(field in data) for field in EXPECT]):
            LOG.debug(u'PATCH flavor, bad params')
            raise wsgi_errors.HTTPBadRequestBody(
                '`pool_group` or `pool` needs to be specified')

        for field in EXPECT:
            wsgi_utils.validate(self._validators[field], data)

        fields = common_utils.fields(data,
                                     EXPECT,
                                     pred=lambda v: v is not None)
        # NOTE(wanghao): remove this in Newton.
        if fields.get('pool') and fields.get('pool_group') is None:
            fields['pool_group'] = fields.get('pool')
            fields.pop('pool')

        resp_data = None
        try:
            self._ctrl.update(flavor, project=project_id, **fields)
            resp_data = self._ctrl.get(flavor, project=project_id)
            capabilities = self._pools_ctrl.capabilities(
                group=resp_data['pool_group'])
            resp_data['capabilities'] = [
                str(cap).split('.')[-1] for cap in capabilities
            ]
        except errors.FlavorDoesNotExist as ex:
            LOG.exception(ex)
            raise wsgi_errors.HTTPNotFound(six.text_type(ex))
        resp_data['href'] = request.path
        response.body = transport_utils.to_json(resp_data)
Exemple #12
0
    def on_patch(self, req, resp, project_id, topic_name):
        """Allows one to update a topic's metadata.

        This method expects the user to submit a JSON object. There is also
        strict format checking through the use of
        jsonschema. Appropriate errors are returned in each case for
        badly formatted input.

        :returns: HTTP | 200,400,409,503
        """
        LOG.debug(u'PATCH topic - name: %s', topic_name)

        try:
            # Place JSON size restriction before parsing
            self._validate.queue_metadata_length(req.content_length)
        except validation.ValidationFailed as ex:
            LOG.debug(ex)
            raise wsgi_errors.HTTPBadRequestBody(six.text_type(ex))

        # NOTE(flwang): See below link to get more details about draft 10,
        # tools.ietf.org/html/draft-ietf-appsawg-json-patch-10
        content_types = {
            'application/openstack-messaging-v2.0-json-patch': 10,
        }

        if req.content_type not in content_types:
            headers = {'Accept-Patch':
                       ', '.join(sorted(content_types.keys()))}
            msg = _("Accepted media type for PATCH: %s.")
            LOG.debug(msg, headers)
            raise wsgi_errors.HTTPUnsupportedMediaType(msg % headers)

        if req.content_length:
            try:
                changes = utils.read_json(req.stream, req.content_length)
                changes = wsgi_utils.sanitize(changes, doctype=list)
            except utils.MalformedJSON as ex:
                LOG.debug(ex)
                description = _(u'Request body could not be parsed.')
                raise wsgi_errors.HTTPBadRequestBody(description)

            except utils.OverflowedJSONInteger as ex:
                LOG.debug(ex)
                description = _(u'JSON contains integer that is too large.')
                raise wsgi_errors.HTTPBadRequestBody(description)

            except Exception:
                # Error while reading from the network/server
                description = _(u'Request body could not be read.')
                LOG.exception(description)
                raise wsgi_errors.HTTPServiceUnavailable(description)
        else:
            msg = _("PATCH body could not be empty for update.")
            LOG.debug(msg)
            raise wsgi_errors.HTTPBadRequestBody(msg)

        try:
            changes = self._validate.queue_patching(req, changes)

            # NOTE(Eva-i): using 'get_metadata' instead of 'get', so
            # QueueDoesNotExist error will be thrown in case of non-existent
            # queue.
            metadata = self._topic_controller.get_metadata(topic_name,
                                                           project=project_id)
            reserved_metadata = _get_reserved_metadata(self._validate)
            for change in changes:
                change_method_name = '_do_%s' % change['op']
                change_method = getattr(self, change_method_name)
                change_method(req, metadata, reserved_metadata, change)

            self._validate.queue_metadata_putting(metadata)

            self._topic_controller.set_metadata(topic_name,
                                                metadata,
                                                project_id)
        except storage_errors.DoesNotExist as ex:
            LOG.debug(ex)
            raise wsgi_errors.HTTPNotFound(six.text_type(ex))
        except validation.ValidationFailed as ex:
            LOG.debug(ex)
            raise wsgi_errors.HTTPBadRequestBody(six.text_type(ex))
        except wsgi_errors.HTTPConflict:
            raise
        except Exception:
            description = _(u'Topic could not be updated.')
            LOG.exception(description)
            raise wsgi_errors.HTTPServiceUnavailable(description)
        for meta, value in _get_reserved_metadata(self._validate).items():
            if not metadata.get(meta):
                metadata[meta] = value
        resp.body = utils.to_json(metadata)