コード例 #1
0
ファイル: server.py プロジェクト: ahadas/ovirt-imageio
    def patch(self, ticket_id):
        # TODO: restart expire timer
        if not ticket_id:
            raise HTTPBadRequest("Ticket id is required")
        try:
            patch = self.request.json
        except ValueError as e:
            raise HTTPBadRequest("Invalid patch: %s" % e)
        try:
            timeout = patch["timeout"]
        except KeyError:
            raise HTTPBadRequest("Missing timeout key")
        try:
            timeout = int(timeout)
        except ValueError as e:
            raise HTTPBadRequest("Invalid timeout value: %s" % e)
        try:
            ticket = tickets.get(ticket_id)
        except KeyError:
            raise HTTPNotFound("No such ticket: %s" % ticket_id)

        self.log.info("[%s] EXTEND timeout=%s ticket=%s",
                      web.client_address(self.request), timeout, ticket_id)
        ticket.extend(timeout)
        return web.response()
コード例 #2
0
ファイル: server.py プロジェクト: ahadas/ovirt-imageio
 def _flush(self, ticket_id, msg):
     ticket = tickets.authorize(ticket_id, "write", 0, 0)
     self.log.info("[%s] FLUSH ticket=%s", web.client_address(self.request),
                   ticket_id)
     op = directio.Flush(ticket.url.path, clock=self.clock)
     ticket.run(op)
     return web.response()
コード例 #3
0
ファイル: server.py プロジェクト: ahadas/ovirt-imageio
    def options(self, ticket_id):
        if not ticket_id:
            raise HTTPBadRequest("Ticket id is required")

        self.log.info("[%s] OPTIONS ticket=%s",
                      web.client_address(self.request), ticket_id)
        if ticket_id == "*":
            # Reporting the meta-capabilities for all images.
            allow = ["OPTIONS", "GET", "PUT", "PATCH"]
            features = ["zero", "flush"]
        else:
            # Reporting real image capabilities per ticket.
            # This check will fail if the ticket has expired.
            ticket = tickets.authorize(ticket_id, "read", 0, 0)

            # Accessing ticket options considered as client activity.
            ticket.touch()

            allow = ["OPTIONS"]
            features = []
            if ticket.may("read"):
                allow.append("GET")
            if ticket.may("write"):
                allow.extend(("PUT", "PATCH"))
                features = ["zero", "flush"]

        return web.response(payload={
            "features": features,
            "unix_socket": self.config.images.socket,
        },
                            allow=",".join(allow))
コード例 #4
0
ファイル: server.py プロジェクト: ahadas/ovirt-imageio
    def put(self, ticket_id):
        if not ticket_id:
            raise HTTPBadRequest("Ticket id is required")
        size = self.request.content_length
        if size is None:
            raise HTTPBadRequest("Content-Length header is required")
        if size < 0:
            raise HTTPBadRequest("Invalid Content-Length header: %r" % size)
        content_range = web.content_range(self.request)
        offset = content_range.start or 0

        # For backward compatibility, we flush by default.
        flush = validate.enum(self.request.params,
                              "flush", ("y", "n"),
                              default="y")
        flush = (flush == "y")

        ticket = tickets.authorize(ticket_id, "write", offset, size)
        # TODO: cancel copy if ticket expired or revoked
        self.log.info("[%s] WRITE size=%d offset=%d flush=%s ticket=%s",
                      web.client_address(self.request), size, offset, flush,
                      ticket_id)
        op = directio.Receive(ticket.url.path,
                              self.request.body_file_raw,
                              size,
                              offset=offset,
                              flush=flush,
                              buffersize=self.config.daemon.buffer_size,
                              clock=self.clock)
        try:
            ticket.run(op)
        except errors.PartialContent as e:
            raise HTTPBadRequest(str(e))
        return web.response()
コード例 #5
0
ファイル: images.py プロジェクト: oVirt/ovirt-imageio
    def patch(self, res_id):
        """
        Proxy PATCH request to daemon.
        """
        if not self.request.content_length:
            raise exc.HTTPBadRequest("Content-Length is required")

        logging.info("[%s] PATCH ticket=%s",
                     web.client_address(self.request), self.ticket.id)

        # Notes:
        # - PATCH response is not cachable, no need for cache-control.
        # - We cannot have read_timeout since PATCH can take unpredictable
        #   time, depending on size of the modified byte range, and the storage
        #   capabillties.
        res = self.make_imaged_request(
            "PATCH",
            self.get_imaged_url(self.ticket),
            self.request.headers,
            web.CappedStream(self.request.body_file,
                             self.request.content_length),
            False,
            connection_timeout=self.config.imaged_connection_timeout_sec)

        # TODO: We expect empty response from the daemon. If we start to return
        # non-empty response, this must be changed to stream the daemon
        # response to the caller.
        return web.response(res.status_code)
コード例 #6
0
ファイル: server.py プロジェクト: ahadas/ovirt-imageio
 def get(self, ticket_id):
     if not ticket_id:
         raise HTTPBadRequest("Ticket id is required")
     try:
         ticket = tickets.get(ticket_id)
     except KeyError:
         raise HTTPNotFound("No such ticket %r" % ticket_id)
     ticket_info = ticket.info()
     self.log.debug("[%s] GET ticket=%s", web.client_address(self.request),
                    ticket_info)
     return web.response(payload=ticket_info)
コード例 #7
0
ファイル: images.py プロジェクト: oVirt/ovirt-imageio
    def options(self, res_id):
        """
        Proxy OPTIONS request to daemon.
        """
        logging.info("[%s] OPTIONS", web.client_address(self.request))

        allow = {"GET", "PUT", "PATCH", "OPTIONS"}
        features = {"zero", "flush"}
        # Reporting the meta-capabilities for all images
        if res_id == "*":
            return web.response(payload={"features": list(features)},
                                allow=','.join(allow))

        ticket = auth.authorize_request(res_id, self.request)

        try:
            res = self.make_imaged_request(
                "OPTIONS",
                self.get_imaged_url(ticket),
                self.request.headers,
                None,
                False,
                connection_timeout=self.config.imaged_connection_timeout_sec,
                read_timeout=self.config.imaged_read_timeout_sec)
        except exc.HTTPMethodNotAllowed:
            # An old daemon - we estimate its methods. We also assume that
            # the ticket is readable and writable, since only the daemon
            # knows about that.
            logging.info("The daemon does not support OPTIONS, "
                         "returning an estimation")
            return web.response(payload={"features": []},
                                allow="OPTIONS,GET,PUT")

        if res.status_code != httplib.OK:
            raise exc.HTTPInternalServerError(
                "Got unexpected response from host: %d %s" %
                (res.status_code, res.content))
        try:
            daemon_allow = set(res.headers.get("Allow").split(","))
        except KeyError:
            raise exc.HTTPInternalServerError(
                "Got invalid response from host: missing Allow header")

        try:
            daemon_features = set(res.json()["features"])
        except (ValueError, KeyError):
            raise exc.HTTPInternalServerError(
                "Got invalid response from host: "
                "invalid JSON or missing 'features'")

        allow = allow.intersection(daemon_allow)
        features = features.intersection(daemon_features)
        return web.response(payload={"features": list(features)},
                            allow=','.join(allow))
コード例 #8
0
ファイル: server.py プロジェクト: ahadas/ovirt-imageio
    def delete(self, ticket_id):
        """
        Delete a ticket if exists.

        Note that DELETE is idempotent;  the client can issue multiple DELETE
        requests in case of network failures. See
        https://tools.ietf.org/html/rfc7231#section-4.2.2.
        """
        # TODO: cancel requests using deleted tickets
        self.log.info("[%s] REMOVE ticket=%s",
                      web.client_address(self.request), ticket_id)
        if ticket_id:
            try:
                tickets.remove(ticket_id)
            except KeyError:
                log.debug("Ticket %s does not exists", ticket_id)
        else:
            tickets.clear()
        return web.response(status=204)
コード例 #9
0
ファイル: server.py プロジェクト: ahadas/ovirt-imageio
    def put(self, ticket_id):
        # TODO
        # - reject invalid or expired ticket
        # - start expire timer
        if not ticket_id:
            raise HTTPBadRequest("Ticket id is required")

        try:
            ticket_dict = self.request.json
        except ValueError as e:
            raise HTTPBadRequest("Ticket is not in a json format: %s" % e)

        self.log.info("[%s] ADD ticket=%s", web.client_address(self.request),
                      ticket_dict)
        try:
            tickets.add(ticket_dict)
        except errors.InvalidTicket as e:
            raise HTTPBadRequest("Invalid ticket: %s" % e)

        return web.response()
コード例 #10
0
ファイル: server.py プロジェクト: ahadas/ovirt-imageio
    def get(self, ticket_id):
        # TODO: cancel copy if ticket expired or revoked
        if not ticket_id:
            raise HTTPBadRequest("Ticket id is required")
        # TODO: support partial range (e.g. bytes=0-*)

        offset = 0
        size = None
        if self.request.range:
            offset = self.request.range.start
            if self.request.range.end is not None:
                size = self.request.range.end - offset

        ticket = tickets.authorize(ticket_id, "read", offset, size)
        if size is None:
            size = ticket.size - offset
        self.log.info("[%s] READ size=%d offset=%d ticket=%s",
                      web.client_address(self.request), size, offset,
                      ticket_id)
        op = directio.Send(ticket.url.path,
                           None,
                           size,
                           offset=offset,
                           buffersize=self.config.daemon.buffer_size,
                           clock=self.clock)
        content_disposition = "attachment"
        if ticket.filename:
            filename = ticket.filename.encode("utf-8")
            content_disposition += "; filename=%s" % filename
        resp = webob.Response(
            status=206 if self.request.range else 200,
            app_iter=ticket.bind(op),
            content_type="application/octet-stream",
            content_length=str(size),
            content_disposition=content_disposition,
        )
        if self.request.range:
            content_range = self.request.range.content_range(ticket.size)
            resp.headers["content-range"] = str(content_range)

        return resp
コード例 #11
0
ファイル: server.py プロジェクト: ahadas/ovirt-imageio
    def _zero(self, ticket_id, msg):
        size = validate.integer(msg, "size", minval=0)
        offset = validate.integer(msg, "offset", minval=0, default=0)
        flush = validate.boolean(msg, "flush", default=False)

        ticket = tickets.authorize(ticket_id, "write", offset, size)

        self.log.info("[%s] ZERO size=%d offset=%d flush=%s ticket=%s",
                      web.client_address(self.request), size, offset, flush,
                      ticket_id)
        op = directio.Zero(ticket.url.path,
                           size,
                           offset=offset,
                           flush=flush,
                           buffersize=self.config.daemon.buffer_size,
                           clock=self.clock,
                           sparse=ticket.sparse)
        try:
            ticket.run(op)
        except errors.PartialContent as e:
            raise HTTPBadRequest(str(e))
        return web.response()
コード例 #12
0
ファイル: images.py プロジェクト: oVirt/ovirt-imageio
    def put(self, res_id):
        """ Handles sending data to host for PUT or PATCH.
        :param request: http request object
        :type request: webob.Request
        :return: http response object
        :rtype: webob.Response
        """
        imaged_url = self.get_imaged_url(self.ticket)
        if "flush" in self.request.params:
            imaged_url += "?flush=" + self.request.params["flush"]

        headers = self.get_default_headers(res_id)
        if 'Content-Length' not in self.request.headers:
            raise exc.HTTPBadRequest("Content-Length header is required")
        headers['Content-Length'] = self.request.headers['Content-Length']
        if 'Content-Range' in self.request.headers:
            headers['Content-Range'] = self.request.headers['Content-Range']

        max_transfer_bytes = int(headers['Content-Length'])
        body = web.CappedStream(self.request.body_file, max_transfer_bytes)
        stream = False

        content_range = web.content_range(self.request)
        offset = content_range.start or 0
        flush = self.request.params.get("flush", "y") == "y"
        logging.info("[%s] WRITE size=%d offset=%d flush=%s ticket=%s",
                     web.client_address(self.request), max_transfer_bytes, offset,
                     flush, self.ticket.id)

        imaged_response = self.make_imaged_request(
            self.request.method, imaged_url, headers, body, stream,
            connection_timeout=self.config.imaged_connection_timeout_sec,
            read_timeout=self.config.imaged_read_timeout_sec)

        response = web.response(imaged_response.status_code)
        response.headers['Cache-Control'] = 'no-cache, no-store'

        return response
コード例 #13
0
ファイル: images.py プロジェクト: oVirt/ovirt-imageio
    def get(self, res_id):
        imaged_url = self.get_imaged_url(self.ticket)

        headers = self.get_default_headers(res_id)
        # Note that webob request.headers is case-insensitive.
        if 'Range' in self.request.headers:
            headers['Range'] = self.request.headers['Range']

        body = ""
        stream = True  # Don't let Requests read entire body into memory

        logging.info("[%s] READ ticket=%s",
                     web.client_address(self.request), self.ticket.id)

        imaged_response = self.make_imaged_request(
            self.request.method, imaged_url, headers, body, stream,
            connection_timeout=self.config.imaged_connection_timeout_sec,
            read_timeout=self.config.imaged_read_timeout_sec)

        response = web.response(imaged_response.status_code)
        response.headers['Cache-Control'] = 'no-cache, no-store'
        response.headers['Content-Range'] = \
            imaged_response.headers.get('Content-Range', '')
        disposition = imaged_response.headers.get('Content-Disposition')
        if disposition is not None:
            response.headers['Content-Disposition'] = disposition


        max_transfer_bytes = int(imaged_response.headers.get('Content-Length'))
        response.body_file = web.CappedStream(RequestStreamAdapter(
            imaged_response.iter_content(4096, False)),
            max_transfer_bytes)
        response.headers['Content-Length'] = str(max_transfer_bytes)
        logging.debug("Resource %s: transferring %d bytes from host",
                      res_id, max_transfer_bytes)

        return response