예제 #1
0
    def options(self, req, resp, ticket_id):
        if not ticket_id:
            raise http.Error(http.BAD_REQUEST, "Ticket id is required")

        log.info("[%s] OPTIONS ticket=%s", req.client_addr, 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.
            try:
                ticket = auth.authorize(ticket_id, "read")
            except errors.AuthorizationError as e:
                raise http.Error(http.FORBIDDEN, str(e))

            # 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"]

        resp.headers["allow"] = ",".join(allow)
        msg = {"features": features, "unix_socket": self.config.images.socket}
        resp.send_json(msg)
예제 #2
0
    def _zero(self, req, resp, 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)

        try:
            ticket = auth.authorize(ticket_id, "write")
        except errors.AuthorizationError as e:
            raise http.Error(http.FORBIDDEN, str(e))

        validate.allowed_range(offset, size, ticket)

        log.info(
            "[%s] ZERO size=%d offset=%d flush=%s ticket=%s",
            req.client_addr, size, offset, flush, ticket_id)

        op = ops.Zero(
            self._backend(req, ticket),
            size,
            offset=offset,
            flush=flush,
            buffersize=self.config.daemon.buffer_size,
            clock=req.clock)
        try:
            ticket.run(op)
        except errors.PartialContent as e:
            raise http.Error(http.BAD_REQUEST, str(e))
예제 #3
0
    def get(self, req, resp, ticket_id):
        if not ticket_id:
            raise http.Error(http.BAD_REQUEST, "Ticket id is required")

        try:
            ticket = auth.get(ticket_id)
        except KeyError:
            raise http.Error(http.NOT_FOUND,
                             "No such ticket {!r}".format(ticket_id))

        ticket_info = ticket.info()
        log.debug("[%s] GET ticket=%s", req.client_addr, ticket_info)
        resp.send_json(ticket_info)
예제 #4
0
    def get(self, req, resp):
        complete = self.file.seek(0, os.SEEK_END)
        offset = 0
        size = complete

        # Handle range request.
        if req.range:
            offset = req.range.first
            if offset < 0:
                offset += complete
                size = complete - offset
            elif req.range.last is not None:
                size = req.range.last - offset + 1
            else:
                size = complete - offset

        if offset + size > complete:
            raise http.Error(http.REQUESTED_RANGE_NOT_SATISFIABLE,
                             "Requested {} bytes, available {} bytes".format(
                                 size, complete - offset),
                             content_range="bytes */{}".format(complete -
                                                               offset))

        resp.headers["content-length"] = size

        if req.range:
            resp.status_code = http.PARTIAL_CONTENT
            resp.headers["content-range"] = "bytes %d-%d/%d" % (
                offset, offset + size - 1, complete)

        self.file.seek(offset)
        resp.write(self.file.read(size))
예제 #5
0
    def put(self, req, resp, ticket_id):
        if not ticket_id:
            raise http.Error(http.BAD_REQUEST, "Ticket id is required")

        # TODO: Reject too big ticket json. We know the size of a ticket based
        # on the size of the keys and values.
        try:
            ticket_dict = json.loads(req.read())
        except ValueError as e:
            raise http.Error(http.BAD_REQUEST,
                             "Ticket is not in a json format: {}".format(e))

        log.info("[%s] ADD ticket=%s", req.client_addr, ticket_dict)
        try:
            auth.add(ticket_dict)
        except errors.InvalidTicket as e:
            raise http.Error(http.BAD_REQUEST, "Invalid ticket: {}".format(e))
예제 #6
0
    def _start_profiling(self, clock):
        with lock:
            if yappi.is_running():
                raise http.Error(http.BAD_REQUEST,
                                 "profile is already running")

            log.info("Starting profiling using %r clock", clock)
            yappi.set_clock_type(clock)
            yappi.start(builtins=True, profile_threads=True)
예제 #7
0
    def _flush(self, req, resp, ticket_id, msg):
        try:
            ticket = auth.authorize(ticket_id, "write")
        except errors.AuthorizationError as e:
            raise http.Error(http.FORBIDDEN, str(e))

        log.info("[%s] FLUSH ticket=%s", req.client_addr, ticket_id)

        op = ops.Flush(self._backend(req, ticket), clock=req.clock)
        ticket.run(op)
예제 #8
0
    def patch(self, req, resp, ticket_id):
        if not ticket_id:
            raise http.Error(http.BAD_REQUEST, "Ticket id is required")

        # TODO: Reject requests with too big payloads. We know the maximum size
        # of a payload based on the keys and the size of offset and size.
        try:
            msg = json.loads(req.read())
        except ValueError as e:
            raise http.Error(
                http.BAD_REQUEST, "Invalid JSON message {}" .format(e))

        op = validate.enum(msg, "op", ("zero", "flush"))
        if op == "zero":
            return self._zero(req, resp, ticket_id, msg)
        elif op == "flush":
            return self._flush(req, resp, ticket_id, msg)
        else:
            raise RuntimeError("Unreachable")
예제 #9
0
 def put(self, req, resp, name):
     count = req.content_length
     with open("/dev/null", "wb") as f:
         while count:
             with req.clock.run("read"):
                 chunk = req.read(1024 * 1024)
             if not chunk:
                 raise http.Error(400, "Client disconnected")
             with req.clock.run("write"):
                 f.write(chunk)
             count -= len(chunk)
예제 #10
0
    def _stop_profiling(self):
        with lock:
            if not yappi.is_running():
                raise http.Error(http.BAD_REQUEST, "profile is not running")

            log.info("Stopping profiling, writing profile to %r",
                     self.config.profile.filename)
            yappi.stop()
            stats = yappi.get_func_stats()
            stats.save(self.config.profile.filename, type="pstat")
            yappi.clear_stats()
예제 #11
0
    def patch(self, req, resp, ticket_id):
        if not ticket_id:
            raise http.Error(http.BAD_REQUEST, "Ticket id is required")

        # TODO: Reject requests with too big payload. We know the size of a
        # ticket patch message based on the keys and values.
        try:
            patch = json.loads(req.read())
        except ValueError as e:
            raise http.Error(http.BAD_REQUEST, "Invalid patch: {}".format(e))

        timeout = validate.integer(patch, "timeout", minval=0)

        try:
            ticket = auth.get(ticket_id)
        except KeyError:
            raise http.Error(http.NOT_FOUND,
                             "No such ticket: {}".format(ticket_id))

        log.info("[%s] EXTEND timeout=%s ticket=%s", req.client_addr, timeout,
                 ticket_id)
        ticket.extend(timeout)
예제 #12
0
    def put(self, req, resp, ticket):
        if req.headers.get("expect") == "100-continue":
            resp.send_info(http.CONTINUE)

        count = req.content_length
        resp.headers["content-length"] = count

        while count:
            chunk = req.read(1024 * 1024)
            if not chunk:
                raise http.Error(http.BAD_REQUEST, "Client disconnected")
            resp.write(chunk)
            count -= len(chunk)
예제 #13
0
    def put(self, req, resp, ticket_id):
        if not ticket_id:
            raise http.Error(http.BAD_REQUEST, "Ticket id is required")

        size = req.content_length
        if size is None:
            raise http.Error(
                http.BAD_REQUEST, "Content-Length header is required")

        offset = req.content_range.first if req.content_range else 0

        # For backward compatibility, we flush by default.
        flush = validate.enum(req.query, "flush", ("y", "n"), default="y")
        flush = (flush == "y")

        try:
            ticket = auth.authorize(ticket_id, "write")
        except errors.AuthorizationError as e:
            raise http.Error(http.FORBIDDEN, str(e))

        validate.allowed_range(offset, size, ticket)

        log.info(
            "[%s] WRITE size=%d offset=%d flush=%s ticket=%s",
            req.client_addr, size, offset, flush, ticket_id)

        op = ops.Receive(
            self._backend(req, ticket),
            req,
            size,
            offset=offset,
            flush=flush,
            buffersize=self.config.daemon.buffer_size,
            clock=req.clock)
        try:
            ticket.run(op)
        except errors.PartialContent as e:
            raise http.Error(http.BAD_REQUEST, str(e))
예제 #14
0
    def post(self, req, resp):
        """
        Start of stop the profiler.
        """
        if yappi is None:
            raise http.Error(http.NOT_FOUND, "yappi is not installed")

        run = validate.enum(req.query, "run", ("y", "n"))
        if run == "y":
            clock = validate.enum(req.query,
                                  "clock", ("cpu", "wall"),
                                  default="cpu")
            self._start_profiling(clock)
        else:
            self._stop_profiling()
예제 #15
0
 def get(self, req, resp):
     # Fail after sending the first part of the response. The
     # connection shold be closed.
     resp.headers["content-length"] = 1000
     resp.write(b"Starting response...")
     raise http.Error(http.INTERNAL_SERVER_ERROR, "No more data for you!")
예제 #16
0
    def get(self, req, resp, ticket_id):
        if not ticket_id:
            raise http.Error(http.BAD_REQUEST, "Ticket id is required")

        offset = 0
        size = None
        if req.range:
            offset = req.range.first
            if offset < 0:
                # TODO: support suffix-byte-range-spec "bytes=-last".
                # See https://tools.ietf.org/html/rfc7233#section-2.1.
                raise http.Error(
                    http.REQUESTED_RANGE_NOT_SATISFIABLE,
                    "suffix-byte-range-spec not supported yet")

            if req.range.last is not None:
                # Add 1 to include the last byte in the payload.
                size = req.range.last - offset + 1
                # TODO: validate size with actual image size.

        try:
            ticket = auth.authorize(ticket_id, "read")
        except errors.AuthorizationError as e:
            raise http.Error(http.FORBIDDEN, str(e))

        backend = self._backend(req, ticket)

        if size is not None:
            validate.allowed_range(offset, size, ticket)
            validate.available_range(offset, size, ticket, backend)
        else:
            size = min(ticket.size, backend.size()) - offset

        log.info(
            "[%s] READ size=%d offset=%d ticket=%s",
            req.client_addr, size, offset, ticket_id)

        content_disposition = "attachment"
        if ticket.filename:
            content_disposition += "; filename=%s" % ticket.filename

        resp.headers["content-length"] = size
        resp.headers["content-type"] = "application/octet-stream"
        resp.headers["content-disposition"] = content_disposition

        if req.range:
            resp.status_code = http.PARTIAL_CONTENT
            resp.headers["content-range"] = "bytes %d-%d/%d" % (
                offset, offset + size - 1, ticket.size)

        op = ops.Send(
            self._backend(req, ticket),
            resp,
            size,
            offset=offset,
            buffersize=self.config.daemon.buffer_size,
            clock=req.clock)
        try:
            ticket.run(op)
        except errors.PartialContent as e:
            raise http.Error(http.BAD_REQUEST, str(e))
예제 #17
0
 def put(self, req, resp):
     offset = req.content_range.first if req.content_range else 0
     self.file.seek(offset)
     self.file.write(req.read())
     if req.length != 0:
         raise http.Error(http.BAD_REQUEST, "Unexpected EOF")
예제 #18
0
    def get(self, req, resp):
        if yappi is None:
            raise http.Error(http.NOT_FOUND, "yappi is not installed")

        msg = {"running": yappi.is_running()}
        resp.send_json(msg)
예제 #19
0
 def get(self, req, resp, name):
     if name not in req.context:
         raise http.Error(http.NOT_FOUND, "No such name {!r}".format(name))
     value = req.context[name]
     resp.headers["content-length"] = len(value)
     resp.write(value)
예제 #20
0
 def get(self, req, resp, name):
     raise http.Error(http.FORBIDDEN, "No data for you!")
예제 #21
0
 def put(self, req, resp):
     # Fail after reading the entire request payload, so the server
     # should keep the connection open.
     req.read()
     raise http.Error(http.FORBIDDEN, "No data for you!")
예제 #22
0
 def put(self, req, resp, name):
     # Raising without reading payload wil fail with EPIPE on the
     # client side. If the client is careful, it will get error 403.
     raise http.Error(http.FORBIDDEN, "No data for you!")