Exemplo n.º 1
0
    def post(self):
        """
        Add subscription
        """
        data = request.get_json()
        subscription = SubscriptionSchema.validate(data)
        if subscription["batch_max_number_of_messages"] > subscription[
                "prefetch_count"]:
            raise SchemaError(
                "Batch max number of messages should be <= prefetch count!")
        subscription.update({
            "org_name": g.org_name,
            "app_name": g.app_name,
            "app_key": settings.LEEK_AGENT_API_SECRET,
            "api_url": settings.LEEK_API_URL
        })

        # Check subscription already exist
        exist, _ = utils.lookup_subscription(subscription["app_name"],
                                             subscription["app_env"])
        if exist:
            return responses.subscription_already_exist

        # Ensure connection
        try:
            connection = Connection(subscription["broker"])
            connection.ensure_connection(max_retries=2)
            connection.release()
        except AccessRefused:
            return responses.wrong_access_refused
        except Exception:
            return responses.broker_not_reachable

        # Add subscription
        with open(SUBSCRIPTIONS_FILE, "r+") as subscriptions_file:
            subscriptions = json.load(subscriptions_file)
            subscriptions.append(subscription)
            subscriptions_file.seek(0)
            json.dump(subscriptions, subscriptions_file)

        return {
            "name": utils.infer_subscription_name(subscription),
            **subscription
        }, 200
Exemplo n.º 2
0
def revoke(app_name, app_env, task_uuid: Union[str, List[str]], args):
    # Check if agent is local
    if not settings.LEEK_ENABLE_AGENT:
        return responses.control_operations_not_supported

    # Retrieve subscription
    found, subscription = lookup_subscription(app_name, app_env)
    if not found:
        return responses.task_retry_subscription_not_found

    # Prepare connection/producer
    # noinspection PyBroadException
    try:
        connection = Connection(subscription["broker"])
        connection.ensure_connection(max_retries=2)
        producer = connection.Producer()
    except AccessRefused:
        return responses.wrong_access_refused
    except Exception:
        return responses.broker_not_reachable

    arguments = {
        "task_id": task_uuid,
        **args,
    }

    # noinspection PyBroadException
    try:
        broadcast_worker_command("revoke", arguments, producer)
    except Exception as ex:
        logger.error(ex)
        return responses.task_revocation_failed
    connection.release()

    revocation_count = len(task_uuid) if isinstance(task_uuid, List) else 1
    return {"acknowledged": True, "revocation_count": revocation_count}, 200
Exemplo n.º 3
0
def get_fanout_queue_drift(index_alias, app_name, app_env):
    # Check if agent is local
    if not settings.LEEK_ENABLE_AGENT:
        return None, 200

    query = {
        "sort": {
            "timestamp": "desc"
        },
        "query": {
            "bool": {
                "must": [
                    {
                        "match": {
                            "app_env": app_env
                        }
                    },
                ]
            }
        }
    }
    connection = es.connection
    try:
        d = connection.search(index=index_alias, body=query, size=1)
        if len(d["hits"]["hits"]):
            latest_event_timestamp = d["hits"]["hits"][0]["_source"][
                "timestamp"]
        else:
            latest_event_timestamp = None
    except es_exceptions.ConnectionError as e:
        logger.warning(e.info)
        return responses.search_backend_unavailable
    except es_exceptions.NotFoundError:
        return responses.application_not_found

    # Retrieve subscription
    found, subscription = lookup_subscription(app_name, app_env)
    if not found:
        return responses.task_retry_subscription_not_found

    # Prepare connection/producer
    # noinspection PyBroadException
    try:
        connection = Connection(subscription["broker"])
        connection.ensure_connection(max_retries=2)
    except AccessRefused:
        return responses.wrong_access_refused
    except Exception:
        return responses.broker_not_reachable

    try:
        name, messages, consumers = connection.channel().queue_declare(
            queue=subscription["queue"], passive=True)
        result = {
            "queue_name": name,
            "messages_count": messages,
            "consumers_count": consumers,
            "latest_event_timestamp": latest_event_timestamp
        }
    except NotFound as ex:
        logger.error(ex)
    else:
        connection.release()
        return result, 200
Exemplo n.º 4
0
def retry_task(app_name, task_doc):
    if task_doc.get("state") not in STATES_TERMINAL:
        return responses.task_retry_state_precondition_failed

    # Check if task is routable
    if not task_doc.get("exchange", "tasks") and not task_doc.get("routing_key"):
        return responses.task_not_routable

    # Check if agent is local
    if not settings.LEEK_ENABLE_AGENT:
        return responses.control_operations_not_supported

    # Retrieve subscription
    found, subscription = lookup_subscription(app_name, task_doc['app_env'])
    if not found:
        return responses.task_retry_subscription_not_found

    # Prepare connection/producer
    # noinspection PyBroadException
    try:
        connection = Connection(subscription["broker"])
        connection.ensure_connection(max_retries=2)
        producer = connection.Producer()
    except AccessRefused:
        return responses.wrong_access_refused
    except Exception:
        return responses.broker_not_reachable

    # Prepare args
    argsrepr = task_doc.get("args") or "()"
    kwargsrepr = task_doc.get("kwargs") or "{}"
    # noinspection PyBroadException
    try:
        args = ast.literal_eval(argsrepr)
        kwargs = ast.literal_eval(kwargsrepr)
    except Exception:
        return responses.malformed_args_or_kwarg_repr

    # Prepare task ids
    task_id = uuid()
    if not task_doc.get("root_id"):
        root_id = task_id
    else:
        root_id = None

    headers = {
        "lang": "py",
        "task": task_doc["name"],
        "id": task_id,
        "shadow": None,
        "eta": None,
        "expires": None,
        "group": None,
        "group_index": None,
        "retries": 0,
        "timelimit": [None, None],
        "root_id": root_id,
        "parent_id": task_doc.get("parent_id"),
        "argsrepr": argsrepr,
        "kwargsrepr": kwargsrepr,
        "origin": "leek@control",
        "ignore_result": True,
    }
    properties = {
        "correlation_id": task_id,
        "reply_to": '',
    }
    body = (
        args, kwargs, {
            "callbacks": None,
            "errbacks": None,
            "chain": None,
            "chord": None,
        },
    )
    # Queue actual task
    try:
        producer.publish(
            body,
            exchange=task_doc["exchange"],
            routing_key=task_doc["routing_key"],
            serializer="json",
            compression=None,
            retry=False,
            delivery_mode=2,  # Persistent
            headers=headers,
            **properties
        )
    except Exception as ex:
        logger.error(ex)
        return responses.task_retry_failed
    # Send task-sent event
    sent_event = {
        "type": "task-sent",
        "uuid": task_id,
        "root_id": root_id,
        "parent_id": task_doc.get("parent_id"),
        "name": task_doc["name"],
        "args": argsrepr,
        "kwargs": kwargsrepr,
        "retries": 0,
        "eta": None,
        "expires": None,
        # --
        "queue": task_doc["queue"],
        "exchange": task_doc["exchange"],
        "routing_key": task_doc["routing_key"],
        # --
        "hostname": "leek@control",
        "utcoffset": time.timezone // 3600,
        "pid": 1,
        "clock": 1,
        "timestamp": time.time(),
    }
    # noinspection PyBroadException
    try:
        producer.publish(
            sent_event,
            routing_key=subscription["routing_key"],
            exchange=subscription["exchange"],
            retry=False,
            serializer="json",
            headers={"hostname": "leek@control"},
            delivery_mode=2,
            expiration=60 * 60 * 24 * 2  # EXPIRES IN 2 DAYS
        )
    except Exception as ex:
        logger.warning(f"Failed to send `task-sent` event for the retried task! with exception: {ex}")
    connection.release()
    return {"task_id": task_id}, 200
Exemplo n.º 5
0
def get_fanout_queue_drift(index_alias, app_name, app_env):
    # Check if agent is local
    if not settings.LEEK_ENABLE_AGENT:
        return None, 200

    query = {
        "sort": {
            "timestamp": "desc"
        },
        "query": {
            "bool": {
                "must": [
                    {
                        "match": {
                            "app_env": app_env
                        }
                    },
                ]
            }
        }
    }
    connection = es.connection
    try:
        d = connection.search(index=index_alias, body=query, size=1)
        if len(d["hits"]["hits"]):
            latest_event_timestamp = d["hits"]["hits"][0]["_source"][
                "timestamp"]
        else:
            latest_event_timestamp = None
    except es_exceptions.ConnectionError as e:
        logger.warning(e.info)
        return responses.search_backend_unavailable
    except es_exceptions.NotFoundError:
        return responses.application_not_found

    # Retrieve subscription
    found, subscription = lookup_subscription(app_name, app_env)
    if not found:
        return responses.task_retry_subscription_not_found

    # Prepare connection/producer
    # noinspection PyBroadException
    try:
        connection = Connection(subscription["broker"])
        connection.ensure_connection(max_retries=2)
    except AccessRefused:
        return responses.wrong_access_refused
    except Exception:
        return responses.broker_not_reachable

    try:
        name, messages, consumers = 0, 0, 0
        if connection.transport.driver_type == "redis":
            # Events over redis transport are not durable because celery sends them in fanout mode.
            # Therefore, if we try to get events queue depth, the client will raise 404 error.
            # If you want the events to be persisted, use RabbitMQ instead!
            pass
        else:
            name, messages, consumers = connection.channel().queue_declare(
                queue=subscription["queue"], passive=True)
        result = {
            "queue_name": name,
            "messages_count": messages,
            "consumers_count": consumers,
            "latest_event_timestamp": latest_event_timestamp
        }
    except NotFound as ex:
        logger.error(ex)
    else:
        connection.release()
        return result, 200
Exemplo n.º 6
0
def retry_tasks(app_name, app_env, tasks_docs: List[dict], dry_run=True):
    # Check if agent is local
    if not settings.LEEK_ENABLE_AGENT:
        return responses.control_operations_not_supported

    # Retrieve subscription
    found, subscription = lookup_subscription(app_name, app_env)
    if not found:
        return responses.task_retry_subscription_not_found

    # Prepare connection/producer
    # noinspection PyBroadException
    try:
        connection = Connection(subscription["broker"])
        connection.ensure_connection(max_retries=2)
        producer = connection.Producer()
    except AccessRefused:
        return responses.wrong_access_refused
    except Exception:
        return responses.broker_not_reachable

    origin = f"[email protected].{uuid()}"
    ineligible_tasks_ids = []
    eligible_tasks_ids = []

    tasks = []

    for task_doc in tasks_docs:
        task = task_doc["_source"]
        # Check if task is in terminal state
        if task.get("state") not in STATES_TERMINAL:
            # Task state precondition failed
            ineligible_tasks_ids.append(task["uuid"])

        # Check if task is routable
        elif not task.get("exchange", "tasks") and not task.get("routing_key"):
            # Task not routable
            ineligible_tasks_ids.append(task["uuid"])

        else:
            # Prepare args
            argsrepr = task.get("args") or "()"
            kwargsrepr = task.get("kwargs") or "{}"
            # noinspection PyBroadException
            try:
                args = ast.literal_eval(argsrepr)
                kwargs = ast.literal_eval(kwargsrepr)
            except Exception:
                ineligible_tasks_ids.append(task["uuid"])
                continue

            # Prepare task ids
            task_id = uuid()
            if not task.get("root_id"):
                root_id = task_id
            else:
                root_id = None

            eligible_tasks_ids.append(task["uuid"])

            if dry_run is True:
                continue

            # Prepare task
            tasks.append(
                dict(
                    queue=task["queue"],
                    exchange=task["exchange"],
                    routing_key=task["routing_key"],
                    original_id=task["uuid"],
                    headers={
                        "lang": "py",
                        "task": task["name"],
                        "id": task_id,
                        "shadow": None,
                        "eta": None,
                        "expires": None,
                        "group": None,
                        "group_index": None,
                        "retries": 0,
                        "timelimit": [None, None],
                        "root_id": root_id,
                        "parent_id": task.get("parent_id"),
                        "argsrepr": argsrepr,
                        "kwargsrepr": kwargsrepr,
                        "origin": origin,
                        "ignore_result": True,
                    },
                    properties={
                        "correlation_id": task_id,
                        "reply_to": '',
                    },
                    body=(
                        args,
                        kwargs,
                        {
                            "callbacks": None,
                            "errbacks": None,
                            "chain": None,
                            "chord": None,
                        },
                    ),
                ))

    if dry_run is True:
        return {
            "eligible_tasks_count": len(eligible_tasks_ids),
            "ineligible_tasks_count": len(ineligible_tasks_ids),
            "ineligible_tasks_ids": ineligible_tasks_ids
        }, 200

    succeeded_retries = []
    failed_retries = []
    for task in tasks:
        # Queue actual task
        try:
            producer.publish(
                task["body"],
                exchange=task["exchange"],
                routing_key=task["routing_key"],
                serializer="json",
                compression=None,
                retry=False,
                delivery_mode=2,  # Persistent
                headers=task["headers"],
                **task["properties"])
            succeeded_retries.append(task["original_id"])
        except Exception as ex:
            logger.error(ex)
            failed_retries.append(task["original_id"])
            continue

        # Send task-sent event
        headers = task["headers"]
        sent_event = {
            "type": "task-sent",
            "uuid": headers["id"],
            "root_id": headers["root_id"],
            "parent_id": headers["parent_id"],
            "name": headers["task"],
            "args": headers["argsrepr"],
            "kwargs": headers["kwargsrepr"],
            "retries": 0,
            "eta": None,
            "expires": None,
            # --
            "queue": task["queue"],
            "exchange": task["exchange"],
            "routing_key": task["routing_key"],
            # --
            "hostname": origin,
            "utcoffset": time.timezone // 3600,
            "pid": 1,
            "clock": 1,
            "timestamp": time.time(),
        }
        # noinspection PyBroadException
        try:
            producer.publish(
                sent_event,
                routing_key=subscription["routing_key"],
                exchange=subscription["exchange"],
                retry=False,
                serializer="json",
                headers={"hostname": origin},
                delivery_mode=2,
                expiration=60 * 60 * 24 * 2  # EXPIRES IN 2 DAYS
            )
        except Exception as ex:
            logger.warning(
                f"Failed to send `task-sent` event for the retried task! with exception: {ex}"
            )

    connection.release()
    return {
        "succeeded_retries_count": len(succeeded_retries),
        "failed_retries_count": len(failed_retries),
        "failed_retries": failed_retries,
        "origin": origin
    }, 200