Example #1
0
    def finish(self):
        self.headers['X-Cocaine-Application'] = self.name

        fill_response_in(
            self.request, self.code,
            httplib.responses.get(self.code, httplib.OK),
            ''.join(self.messages), self.headers)
Example #2
0
    def process(self, request):
        try:
            payload = json.loads(request.body)
        except ValueError:
            JSONRPC._send_400_error(
                request, -32700,
                'Parse error: Invalid JSON was received by the server.')
            return

        if not all(k in payload for k in REQUEST_FIELDS):
            JSONRPC._send_400_error(
                request, -32600,
                'The JSON sent is not a valid Request object.')
            return

        name, method = payload['method'].split('.', 2)
        args = payload['params']
        chunks = payload.get('chunks', [])
        headers = {}
        if 'authorization' in request.headers:
            headers['authorization'] = request.headers['authorization']

        try:
            service = yield self.proxy.get_service(name, request)
            if service is None:
                JSONRPC._send_500_error(request, payload, 'Service not found.')
                return
        except Exception as err:
            JSONRPC._send_500_error(request, payload, err)
            return

        api = dict((data[0], data) for data in service.api.itervalues())
        if method not in api:
            JSONRPC._send_400_error(request, -32601, 'Method not found.')
            return

        name, tx_tree, rx_tree = api[method]

        try:
            for match, handle in self._protocols:
                if match(tx_tree, rx_tree):
                    result = yield handle(service, method, args, chunks,
                                          **headers)
                    break
            else:
                JSONRPC._send_400_error(request, -32000,
                                        'Protocol type is not supported.')
                return
        except Exception as err:
            JSONRPC._send_500_error(request, payload, err)
            return

        headers = httputil.HTTPHeaders(
            {'Content-Type': 'application/json-rpc'})
        body = {
            'jsonrpc': '2.0',
            'result': result,
            'id': payload['id'],
        }
        fill_response_in(request, 200, 'OK', json.dumps(body), headers)
Example #3
0
 def wrapper(self, request):
     self.requests_in_progress += 1
     self.requests_total += 1
     traceid = None
     try:
         generated_traceid = self.get_request_id(request)
         if generated_traceid is not None:
             # assume we have hexdigest form of number
             # get only 16 digits
             traceid = generated_traceid[:16]
             adaptor = ContextAdapter(self.access_log, {"trace_id": traceid})
             request.logger = adaptor
             # verify user input: request_header must be valid hexdigest
             try:
                 int(traceid, 16)
             except ValueError:
                 fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                                  httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                                  "Request-Id `%s` is not a hexdigest" % traceid,
                                  proxy_error_headers())
                 return
         else:
             request.logger = NULLLOGGER  # pylint: disable=R0204
         request.traceid = traceid
         request.tracebit = True
         request.logger.info("start request: %s %s %s", request.host, request.remote_ip, request.uri)
         yield func(self, request)
     finally:
         self.requests_in_progress -= 1
Example #4
0
 def wrapper(self, request):
     self.requests_in_progress += 1
     self.requests_total += 1
     traceid = None
     try:
         generated_traceid = self.get_request_id(request)
         if generated_traceid is not None:
             # assume we have hexdigest form of number
             # get only 16 digits
             traceid = generated_traceid[:16]
             adaptor = ContextAdapter(self.access_log,
                                      {"trace_id": traceid})
             request.logger = adaptor
             # verify user input: request_header must be valid hexdigest
             try:
                 int(traceid, 16)
             except ValueError:
                 fill_response_in(
                     request, httplib.INTERNAL_SERVER_ERROR,
                     httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                     "Request-Id `%s` is not a hexdigest" % traceid,
                     proxy_error_headers())
                 return
         else:
             request.logger = NULLLOGGER  # pylint: disable=R0204
         request.traceid = traceid
         request.logger.info("start request: %s %s %s", request.host,
                             request.remote_ip, request.uri)
         yield func(self, request)
     finally:
         self.requests_in_progress -= 1
Example #5
0
        def on_error(app, err, extra_msg, code=httplib.INTERNAL_SERVER_ERROR):
            if len(extra_msg) > 0 and not extra_msg.endswith(' '):
                extra_msg += ' '
            request.logger.error("%s: %s%s", app.id, extra_msg, err)

            message = "UID %s: application `%s` error: %s" % (request.traceid, app.name, str(err))
            fill_response_in(request, code, httplib.responses[code], message, proxy_error_headers(app.name))
Example #6
0
    def ping(self, request):
        if self.locator_status:
            fill_response_in(request, httplib.OK, "OK", "OK")
            return

        fill_response_in(request, httplib.SERVICE_UNAVAILABLE,
                         httplib.responses[httplib.SERVICE_UNAVAILABLE],
                         "Failed", proxy_error_headers())
Example #7
0
    def ping(self, request):
        if self.locator_status:
            fill_response_in(request, httplib.OK, "OK", "OK")
            return

        fill_response_in(request, httplib.SERVICE_UNAVAILABLE,
                         httplib.responses[httplib.SERVICE_UNAVAILABLE],
                         "Failed", proxy_error_headers())
Example #8
0
        def on_error(app, err, extra_msg, code=httplib.INTERNAL_SERVER_ERROR):
            if len(extra_msg) > 0 and not extra_msg.endswith(' '):
                extra_msg += ' '
            request.logger.error("%s: %s%s", app.id, extra_msg, err)

            message = "UID %s: application `%s` error: %s" % (
                request.traceid, app.name, str(err))
            fill_response_in(request, code, httplib.responses[code], message,
                             proxy_error_headers(app.name))
Example #9
0
    def __init__(self, request, name, code, headers):
        super(ChunkedBodyProcessor, self).__init__(
            request, name, code, headers)

        self.headers.add('Transfer-Encoding', 'chunked')
        self.headers['X-Cocaine-Application'] = self.name

        fill_response_in(self.request, self.code,
                         httplib.responses.get(self.code, httplib.OK),
                         ''.join(self.messages), self.headers, chunked=True)
Example #10
0
 def _send_500_error(request, payload, err):
     headers = httputil.HTTPHeaders(
         {'Content-Type': 'application/json-rpc'})
     body = {
         'jsonrpc': '2.0',
         'error': str(err),
         'id': payload['id'],
     }
     fill_response_in(request, 500, 'Internal Server Error',
                      json.dumps(body), headers)
Example #11
0
    def __init__(self, request, name, code, headers):
        super(ChunkedBodyProcessor, self).__init__(request, name, code,
                                                   headers)

        self.headers.add('Transfer-Encoding', 'chunked')
        self.headers['X-Cocaine-Application'] = self.name

        fill_response_in(self.request,
                         self.code,
                         httplib.responses.get(self.code, httplib.OK),
                         ''.join(self.messages),
                         self.headers,
                         chunked=True)
Example #12
0
    def process(self, request):
        name, event = extract_app_and_event(request)
        timeout = self.proxy.get_timeout(name, event)
        # as MDS proxy bypasses the mechanism of routing groups
        # the proxy is responsible to provide this feature
        name = self.proxy.resolve_group_to_version(name)
        headers = request.headers
        namespace = headers["X-Srw-Namespace"]
        key = headers["X-Srw-Key"]

        mds_request_headers = httputil.HTTPHeaders()
        if "Authorization" in request.headers:
            mds_request_headers["Authorization"] = request.headers["Authorization"]

        traceid = getattr(request, "traceid", None)
        if traceid is not None:
            mds_request_headers["X-Request-Id"] = traceid

        srw_request = HTTPRequest("%s/exec-%s/%s/%s/stid/%s?timeout=%d" % (self.srw_host, namespace, name, event, key, timeout),
                                  method="POST",
                                  headers=mds_request_headers,
                                  body=msgpack.packb(pack_httprequest(request)),
                                  allow_ipv6=True,
                                  request_timeout=timeout)

        try:
            # NOTE: we can do it in a streaming way
            resp = yield self.srw_httpclient.fetch(srw_request)
            code, reply_headers, body = decode_chunked_encoded_reply(resp)
            fill_response_in(request, code,
                             httplib.responses.get(code, httplib.OK),
                             body, reply_headers)
        except HTTPError as err:
            if err.code == 404:
                raise PluginNoSuchApplication("worker was not found")

            if err.code == 500:
                raise PluginApplicationError(42, 42, "worker replied with error")

            if err.code == 401:
                fill_response_in(request, err.code,
                                 httplib.responses.get(err.code, httplib.OK),
                                 err.response.body, err.response.headers)
                return

            raise err
Example #13
0
    def fetch_mds_endpoints(self, request, srw_request):
        try:
            # NOTE: we can do it in a streaming way
            resp = yield self.srw_httpclient.fetch(srw_request)
            body = resp.buffer.read(None)
            if self.is_stid_request(request):
                raise gen.Return(self.decode_mulca_dist_info(body))
            else:
                raise gen.Return(self.decode_mds_dist_info(body))

        except HTTPError as err:
            if err.code == 404:
                raise PluginNoSuchApplication("404")

            if err.code == 500:
                raise PluginApplicationError(42, 42, "500")

            if err.code == 401:
                fill_response_in(request, err.code,
                                 httplib.responses.get(err.code, httplib.OK),
                                 err.response.body, err.response.headers)
                return

            raise err
    def fetch_mds_endpoints(self, request, srw_request):
        try:
            # NOTE: we can do it in a streaming way
            resp = yield self.srw_httpclient.fetch(srw_request)
            body = resp.buffer.read(None)
            if self.is_stid_request(request):
                raise gen.Return(self.decode_mulca_dist_info(body))
            else:
                raise gen.Return(self.decode_mds_dist_info(body))

        except HTTPError as err:
            if err.code == 404:
                raise PluginNoSuchApplication("404")

            if err.code == 500:
                raise PluginApplicationError(42, 42, "500")

            if err.code == 401:
                fill_response_in(request, err.code,
                                 httplib.responses.get(err.code, httplib.OK),
                                 err.response.body, err.response.headers)
                return

            raise err
Example #15
0
    def __call__(self, request):
        for plugin in self.plugins:
            if plugin.match(request):
                request.logger.info('processed by %s plugin', plugin.name())
                try:
                    yield plugin.process(request)
                except PluginNoSuchApplication as err:
                    fill_response_in(request, NO_SUCH_APP, "No such application",
                                     str(err), proxy_error_headers())
                except PluginApplicationError:
                    message = "application error"
                    fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                                     httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                                     message, proxy_error_headers())
                except ProxyInvalidRequest:
                    if request.path == "/ping":
                        self.ping(request)
                    else:
                        fill_response_in(request, httplib.NOT_FOUND, httplib.responses[httplib.NOT_FOUND],
                                         "Invalid url", proxy_error_headers())
                except Exception as err:
                    request.logger.exception('plugin %s returned error: %s', plugin.name(), err)
                    message = "unknown error"
                    fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                                     httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                                     message, proxy_error_headers())
                return

        try:
            name, event = extract_app_and_event(request)
        except ProxyInvalidRequest:
            if request.path == "/ping":
                self.ping(request)
            else:
                fill_response_in(request, httplib.NOT_FOUND, httplib.responses[httplib.NOT_FOUND],
                                 "Invalid url", proxy_error_headers())
            return

        self.setup_tracing(request, name)

        if self.sticky_header in request.headers:
            seed = request.headers.get(self.sticky_header)
            seed_value = header_to_seed(seed)
            request.logger.info('sticky_header has been found: name %s, value %s, seed %d', name, seed, seed_value)
            name = self.resolve_group_to_version(name, seed_value)

        app = yield self.get_service(name, request)

        if app is None:
            message = "current application %s is unavailable" % name
            fill_response_in(request, NO_SUCH_APP, "No Such Application",
                             message, proxy_error_headers(name))
            return

        try:
            # TODO: attempts should be configurable
            yield self.process(request, name, app, event, pack_httprequest(request), self.reelect_app, 2)
        except Exception as err:
            request.logger.exception("error during processing request %s", err)
            fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                             httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                             "UID %s: %s" % (request.traceid, str(err)), proxy_error_headers(name))

        request.logger.info("exit from process")
Example #16
0
 def _send_400_error(request, code, message):
     headers = httputil.HTTPHeaders(
         {'Content-Type': 'application/json-rpc'})
     body = {'code': code, 'message': message}
     fill_response_in(request, 400, 'Bad JSON-RPC request',
                      json.dumps(body), headers)
Example #17
0
    def __call__(self, request):
        for plugin in self.plugins:
            if plugin.match(request):
                request.logger.info('processed by %s plugin', plugin.name())
                try:
                    yield plugin.process(request)
                except PluginNoSuchApplication as err:
                    fill_response_in(request, NO_SUCH_APP, "No such application",
                                     str(err), proxy_error_headers())
                except PluginApplicationError:
                    message = "application error"
                    fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                                     httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                                     message, proxy_error_headers())
                except ProxyInvalidRequest:
                    if request.path == "/ping":
                        self.ping(request)
                    else:
                        fill_response_in(request, httplib.NOT_FOUND, httplib.responses[httplib.NOT_FOUND],
                                         "Invalid url", proxy_error_headers())
                except Exception as err:
                    request.logger.exception('plugin %s returned error: %s', plugin.name(), err)
                    message = "unknown error"
                    fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                                     httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                                     message, proxy_error_headers())
                return

        try:
            name, event = extract_app_and_event(request)
        except ProxyInvalidRequest:
            if request.path == "/ping":
                self.ping(request)
            else:
                fill_response_in(request, httplib.NOT_FOUND, httplib.responses[httplib.NOT_FOUND],
                                 "Invalid url", proxy_error_headers())
            return

        if getattr(request, "traceid", None) is not None:
            tracing_chance = self.sampled_apps.get(name, self.default_tracing_chance)
            rolled_dice = random.uniform(0, 100)
            request.logger.debug("tracing_chance %f, rolled dice %f", tracing_chance, rolled_dice)
            if tracing_chance < rolled_dice:
                request.logger.info('stop tracing the request')
                request.logger = NULLLOGGER
                request.traceid = None

        if self.sticky_header in request.headers:
            seed = request.headers.get(self.sticky_header)
            seed_value = header_to_seed(seed)
            request.logger.info('sticky_header has been found: name %s, value %s, seed %d', name, seed, seed_value)
            name = self.resolve_group_to_version(name, seed_value)

        app = yield self.get_service(name, request)

        if app is None:
            message = "current application %s is unavailable" % name
            fill_response_in(request, NO_SUCH_APP, "No Such Application",
                             message, proxy_error_headers(name))
            return

        try:
            yield self.process(request, name, app, event, pack_httprequest(request), self.reelect_app)
        except Exception as err:
            request.logger.exception("error during processing request %s", err)
            fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                             httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                             "UID %s: %s" % (request.traceid, str(err)), proxy_error_headers(name))

        request.logger.info("exit from process")
Example #18
0
    def process(self,
                request,
                name,
                app,
                event,
                data,
                reelect_app_fn,
                timeout=None):
        if timeout is None:
            timeout = self.get_timeout(name, event)
        request.logger.info(
            "start processing event `%s` for an app `%s` (appid: %s) after %.3f ms with timeout %f",
            event, app.name, app.id,
            request.request_time() * 1000, timeout)
        # allow to reconnect this amount of times.
        attempts = 2  # make it configurable

        parentid = 0

        if request.traceid is not None:
            traceid = int(request.traceid, 16)
            trace = Trace(traceid=traceid, spanid=traceid, parentid=parentid)
        else:
            trace = None

        headers = {}
        if 'authorization' in request.headers:
            headers['authorization'] = request.headers['authorization']

        while attempts > 0:
            body_parts = []
            attempts -= 1
            try:
                request.logger.debug("%s: enqueue event (attempt %d)", app.id,
                                     attempts)
                channel = yield app.enqueue(event, trace=trace, **headers)
                request.logger.debug("%s: send event data (attempt %d)",
                                     app.id, attempts)
                yield channel.tx.write(msgpack.packb(data), trace=trace)
                yield channel.tx.close(trace=trace)
                request.logger.debug(
                    "%s: waiting for a code and headers (attempt %d)", app.id,
                    attempts)
                code_and_headers = yield channel.rx.get(timeout=timeout)
                request.logger.debug(
                    "%s: code and headers have been received (attempt %d)",
                    app.id, attempts)
                code, raw_headers = msgpack.unpackb(code_and_headers)
                headers = httputil.HTTPHeaders(raw_headers)

                cocaine_http_proto_version = headers.get(
                    X_COCAINE_HTTP_PROTO_VERSION)
                if cocaine_http_proto_version is None or cocaine_http_proto_version == "1.0":
                    cocaine_http_proto_version = "1.0"

                    def stop_condition(body):
                        return isinstance(body, EmptyResponse)
                elif cocaine_http_proto_version == "1.1":

                    def stop_condition(body):
                        return isinstance(body,
                                          EmptyResponse) or len(body) == 0
                else:
                    raise Exception(
                        "unsupported X-Cocaine-HTTP-Proto-Version: %s" %
                        cocaine_http_proto_version)

                while True:
                    body = yield channel.rx.get(timeout=timeout)
                    if stop_condition(body):
                        request.logger.info("%s: body finished (attempt %d)",
                                            app.id, attempts)
                        break

                    request.logger.debug(
                        "%s: received %d bytes as a body chunk (attempt %d)",
                        app.id, len(body), attempts)
                    body_parts.append(body)
            except gen.TimeoutError as err:
                request.logger.error("%s %s:  %s", app.id, name, err)
                message = "UID %s: application `%s` error: TimeoutError" % (
                    request.traceid, name)
                fill_response_in(request, httplib.GATEWAY_TIMEOUT,
                                 httplib.responses[httplib.GATEWAY_TIMEOUT],
                                 message, proxy_error_headers(name))

            except (DisconnectionError, StreamClosedError) as err:
                self.requests_disconnections += 1
                # Probably it's dangerous to retry requests all the time.
                # I must find the way to determine whether it failed during writing
                # or reading a reply. And retry only writing fails.
                request.logger.error("%s: %s", app.id, err)
                if attempts <= 0:
                    request.logger.error("%s: no more attempts", app.id)
                    fill_response_in(
                        request, httplib.INTERNAL_SERVER_ERROR,
                        httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                        "UID %s: Connection problem" % request.traceid,
                        proxy_error_headers(name))
                    return

                # Seems on_close callback is not called in case of connecting through IPVS
                # We detect disconnection here to avoid unnecessary errors.
                # Try to reconnect here and give the request a go
                try:
                    start_time = time.time()
                    reconn_timeout = timeout - request.request_time()
                    request.logger.info("%s: connecting with timeout %.fms",
                                        app.id, reconn_timeout * 1000)
                    yield gen.with_timeout(start_time + reconn_timeout,
                                           app.connect(request.traceid))
                    reconn_time = time.time() - start_time
                    request.logger.info("%s: connecting took %.3fms", app.id,
                                        reconn_time * 1000)
                except Exception as err:
                    if attempts <= 0:
                        # we have no attempts more, so quit here
                        request.logger.error("%s: %s (no attempts left)",
                                             app.id, err)
                        message = "UID %s: application `%s` error: %s" % (
                            request.traceid, name, str(err))
                        fill_response_in(
                            request, httplib.INTERNAL_SERVER_ERROR,
                            httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                            message, proxy_error_headers(name))
                        return

                    request.logger.error(
                        "%s: unable to reconnect: %s (%d attempts left)", err,
                        attempts)
                # We have an attempt to process request again.
                # Jump to the begining of `while attempts > 0`, either we connected successfully
                # or we were failed to connect
                continue

            except ServiceError as err:
                # if the application has been restarted, we get broken pipe code
                # and system category
                if err.category in SYSTEMCATEGORY and err.code == EAPPSTOPPED:
                    request.logger.error(
                        "%s: the application has been restarted", app.id)
                    app.disconnect()
                    continue

                elif err.category in OVERSEERCATEGORY and err.code == EQUEUEISFULL:
                    request.logger.error(
                        "%s: queue is full. Pick another application instance",
                        app.id)
                    app = yield reelect_app_fn(request, app)
                    continue

                request.logger.error("%s: service error: [%d, %d] %s", app.id,
                                     err.category, err.code, err.reason)
                message = "UID %s: application `%s` error: %s" % (
                    request.traceid, name, str(err))
                fill_response_in(
                    request, httplib.INTERNAL_SERVER_ERROR,
                    httplib.responses[httplib.INTERNAL_SERVER_ERROR], message,
                    proxy_error_headers(name))

            except Exception as err:
                request.logger.exception("%s: %s", app.id, err)
                message = "UID %s: unknown `%s` error: %s" % (request.traceid,
                                                              name, str(err))
                fill_response_in(
                    request, httplib.INTERNAL_SERVER_ERROR,
                    httplib.responses[httplib.INTERNAL_SERVER_ERROR], message,
                    proxy_error_headers(name))
            else:
                message = ''.join(body_parts)
                headers['X-Cocaine-Application'] = name
                fill_response_in(request, code,
                                 httplib.responses.get(code, httplib.OK),
                                 message, headers)
            # to return from all errors except Disconnection
            # or receiving a good reply
            return
Example #19
0
    def __call__(self, request):
        for plugin in self.plugins:
            if plugin.match(request):
                request.logger.info('processed by %s plugin', plugin.name())
                try:
                    yield plugin.process(request)
                except PluginNoSuchApplication as err:
                    fill_response_in(request, NO_SUCH_APP,
                                     "No such application", str(err),
                                     proxy_error_headers())
                except PluginApplicationError:
                    message = "application error"
                    fill_response_in(
                        request, httplib.INTERNAL_SERVER_ERROR,
                        httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                        message, proxy_error_headers())
                except ProxyInvalidRequest:
                    if request.path == "/ping":
                        self.ping(request)
                    else:
                        fill_response_in(request, httplib.NOT_FOUND,
                                         httplib.responses[httplib.NOT_FOUND],
                                         "Invalid url", proxy_error_headers())
                except Exception as err:
                    request.logger.exception('plugin %s returned error: %s',
                                             plugin.name(), err)
                    message = "unknown error"
                    fill_response_in(
                        request, httplib.INTERNAL_SERVER_ERROR,
                        httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                        message, proxy_error_headers())
                return

        try:
            name, event = extract_app_and_event(request)
        except ProxyInvalidRequest:
            if request.path == "/ping":
                self.ping(request)
            else:
                fill_response_in(request, httplib.NOT_FOUND,
                                 httplib.responses[httplib.NOT_FOUND],
                                 "Invalid url", proxy_error_headers())
            return

        if getattr(request, "traceid", None) is not None:
            tracing_chance = self.sampled_apps.get(name,
                                                   self.default_tracing_chance)
            rolled_dice = random.uniform(0, 100)
            request.logger.debug("tracing_chance %f, rolled dice %f",
                                 tracing_chance, rolled_dice)
            if tracing_chance < rolled_dice:
                request.logger.info('stop tracing the request')
                request.logger = NULLLOGGER
                request.traceid = None

        if self.sticky_header in request.headers:
            seed = request.headers.get(self.sticky_header)
            seed_value = header_to_seed(seed)
            request.logger.info(
                'sticky_header has been found: name %s, value %s, seed %d',
                name, seed, seed_value)
            name = self.resolve_group_to_version(name, seed_value)

        app = yield self.get_service(name, request)

        if app is None:
            message = "current application %s is unavailable" % name
            fill_response_in(request, NO_SUCH_APP, "No Such Application",
                             message, proxy_error_headers(name))
            return

        try:
            yield self.process(request, name, app, event,
                               pack_httprequest(request), self.reelect_app)
        except Exception as err:
            request.logger.exception("error during processing request %s", err)
            fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                             httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                             "UID %s: %s" % (request.traceid, str(err)),
                             proxy_error_headers(name))

        request.logger.info("exit from process")
Example #20
0
    def process(self, request, name, app, event, data, reelect_app_fn, timeout=None):
        if timeout is None:
            timeout = self.get_timeout(name, event)
        request.logger.info("start processing event `%s` for an app `%s` (appid: %s) after %.3f ms with timeout %f",
                            event, app.name, app.id, request.request_time() * 1000, timeout)
        # allow to reconnect this amount of times.
        attempts = 2  # make it configurable

        parentid = 0

        if request.traceid is not None:
            traceid = int(request.traceid, 16)
            trace = Trace(traceid=traceid, spanid=traceid, parentid=parentid)
        else:
            trace = None

        headers = {}
        if 'authorization' in request.headers:
            headers['authorization'] = request.headers['authorization']

        while attempts > 0:
            body_parts = []
            attempts -= 1
            try:
                request.logger.debug("%s: enqueue event (attempt %d)", app.id, attempts)
                channel = yield app.enqueue(event, trace=trace, **headers)
                request.logger.debug("%s: send event data (attempt %d)", app.id, attempts)
                yield channel.tx.write(msgpack.packb(data), trace=trace)
                yield channel.tx.close(trace=trace)
                request.logger.debug("%s: waiting for a code and headers (attempt %d)",
                                     app.id, attempts)
                code_and_headers = yield channel.rx.get(timeout=timeout)
                request.logger.debug("%s: code and headers have been received (attempt %d)",
                                     app.id, attempts)
                code, raw_headers = msgpack.unpackb(code_and_headers)
                headers = httputil.HTTPHeaders(raw_headers)

                cocaine_http_proto_version = headers.get(X_COCAINE_HTTP_PROTO_VERSION)
                if cocaine_http_proto_version is None or cocaine_http_proto_version == "1.0":
                    cocaine_http_proto_version = "1.0"

                    def stop_condition(body):
                        return isinstance(body, EmptyResponse)
                elif cocaine_http_proto_version == "1.1":
                    def stop_condition(body):
                        return isinstance(body, EmptyResponse) or len(body) == 0
                else:
                    raise Exception("unsupported X-Cocaine-HTTP-Proto-Version: %s" % cocaine_http_proto_version)

                while True:
                    body = yield channel.rx.get(timeout=timeout)
                    if stop_condition(body):
                        request.logger.info("%s: body finished (attempt %d)", app.id, attempts)
                        break

                    request.logger.debug("%s: received %d bytes as a body chunk (attempt %d)",
                                         app.id, len(body), attempts)
                    body_parts.append(body)
            except gen.TimeoutError as err:
                request.logger.error("%s %s:  %s", app.id, name, err)
                message = "UID %s: application `%s` error: TimeoutError" % (request.traceid, name)
                fill_response_in(request, httplib.GATEWAY_TIMEOUT,
                                 httplib.responses[httplib.GATEWAY_TIMEOUT],
                                 message, proxy_error_headers(name))

            except (DisconnectionError, StreamClosedError) as err:
                self.requests_disconnections += 1
                # Probably it's dangerous to retry requests all the time.
                # I must find the way to determine whether it failed during writing
                # or reading a reply. And retry only writing fails.
                request.logger.error("%s: %s", app.id, err)
                if attempts <= 0:
                    request.logger.error("%s: no more attempts", app.id)
                    fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                                     httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                                     "UID %s: Connection problem" % request.traceid,
                                     proxy_error_headers(name))
                    return

                # Seems on_close callback is not called in case of connecting through IPVS
                # We detect disconnection here to avoid unnecessary errors.
                # Try to reconnect here and give the request a go
                try:
                    start_time = time.time()
                    reconn_timeout = timeout - request.request_time()
                    request.logger.info("%s: connecting with timeout %.fms", app.id, reconn_timeout * 1000)
                    yield gen.with_timeout(start_time + reconn_timeout, app.connect(request.traceid))
                    reconn_time = time.time() - start_time
                    request.logger.info("%s: connecting took %.3fms", app.id, reconn_time * 1000)
                except Exception as err:
                    if attempts <= 0:
                        # we have no attempts more, so quit here
                        request.logger.error("%s: %s (no attempts left)", app.id, err)
                        message = "UID %s: application `%s` error: %s" % (request.traceid, name, str(err))
                        fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                                         httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                                         message, proxy_error_headers(name))
                        return

                    request.logger.error("%s: unable to reconnect: %s (%d attempts left)",
                                         err, attempts)
                # We have an attempt to process request again.
                # Jump to the begining of `while attempts > 0`, either we connected successfully
                # or we were failed to connect
                continue

            except ServiceError as err:
                # if the application has been restarted, we get broken pipe code
                # and system category
                if err.category in SYSTEMCATEGORY and err.code == EAPPSTOPPED:
                    request.logger.error("%s: the application has been restarted", app.id)
                    app.disconnect()
                    continue

                elif err.category in OVERSEERCATEGORY and err.code == EQUEUEISFULL:
                    request.logger.error("%s: queue is full. Pick another application instance", app.id)
                    app = yield reelect_app_fn(request, app)
                    continue

                request.logger.error("%s: service error: [%d, %d] %s", app.id, err.category, err.code, err.reason)
                message = "UID %s: application `%s` error: %s" % (request.traceid, name, str(err))
                fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                                 httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                                 message, proxy_error_headers(name))

            except Exception as err:
                request.logger.exception("%s: %s", app.id, err)
                message = "UID %s: unknown `%s` error: %s" % (request.traceid, name, str(err))
                fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                                 httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                                 message, proxy_error_headers(name))
            else:
                message = ''.join(body_parts)
                headers['X-Cocaine-Application'] = name
                fill_response_in(request, code,
                                 httplib.responses.get(code, httplib.OK),
                                 message, headers)
            # to return from all errors except Disconnection
            # or receiving a good reply
            return
Example #21
0
    def __call__(self, request):
        for plugin in self.plugins:
            if plugin.match(request):
                request.logger.info('processed by %s plugin', plugin.name())
                try:
                    yield plugin.process(request)
                except PluginNoSuchApplication as err:
                    fill_response_in(request, NO_SUCH_APP,
                                     "No such application", str(err),
                                     proxy_error_headers())
                except PluginApplicationError:
                    message = "application error"
                    fill_response_in(
                        request, httplib.INTERNAL_SERVER_ERROR,
                        httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                        message, proxy_error_headers())
                except ProxyInvalidRequest:
                    if request.path == "/ping":
                        self.ping(request)
                    else:
                        fill_response_in(request, httplib.NOT_FOUND,
                                         httplib.responses[httplib.NOT_FOUND],
                                         "Invalid url", proxy_error_headers())
                except Exception as err:
                    request.logger.exception('plugin %s returned error: %s',
                                             plugin.name(), err)
                    message = "unknown error"
                    fill_response_in(
                        request, httplib.INTERNAL_SERVER_ERROR,
                        httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                        message, proxy_error_headers())
                return

        try:
            name, event = extract_app_and_event(request)
        except ProxyInvalidRequest:
            if request.path == "/ping":
                self.ping(request)
            else:
                fill_response_in(request, httplib.NOT_FOUND,
                                 httplib.responses[httplib.NOT_FOUND],
                                 "Invalid url", proxy_error_headers())
            return

        self.setup_tracing(request, name)

        if self.sticky_header in request.headers:
            seed = request.headers.get(self.sticky_header)
            seed_value = header_to_seed(seed)
            request.logger.info(
                'sticky_header has been found: name %s, value %s, seed %d',
                name, seed, seed_value)
            name = self.resolve_group_to_version(name, seed_value)

        app = yield self.get_service(name, request)

        if app is None:
            message = "current application %s is unavailable" % name
            fill_response_in(request, NO_SUCH_APP, "No Such Application",
                             message, proxy_error_headers(name))
            return

        try:
            # TODO: attempts should be configurable
            yield self.process(request, name, app, event,
                               pack_httprequest(request), self.reelect_app, 2)
        except Exception as err:
            request.logger.exception("error during processing request %s", err)
            fill_response_in(request, httplib.INTERNAL_SERVER_ERROR,
                             httplib.responses[httplib.INTERNAL_SERVER_ERROR],
                             "UID %s: %s" % (request.traceid, str(err)),
                             proxy_error_headers(name))

        request.logger.info("exit from process")
Example #22
0
    def finish(self):
        self.headers['X-Cocaine-Application'] = self.name

        fill_response_in(self.request, self.code,
                         httplib.responses.get(self.code, httplib.OK),
                         ''.join(self.messages), self.headers)