Example #1
0
def dispatch_job(job: Job) -> Job:
    """
    Send a job to a queue.

    Decides which queue a job should be sent to and sends it.
    The queue can depend upon both the project and the account (either the
    account that the project is linked to, or the default account of the job
    creator).
    """
    if not JobMethod.is_member(job.method):
        raise ValueError("Unknown job method '{}'".format(job.method))

    if job.method in [JobMethod.series.value, JobMethod.chain.value]:
        # Dispatch the first child; subsequent children
        # will get dispatched via the when the parent is checked
        if job.children:
            dispatch_job(job.children.first())
    elif job.method == JobMethod.parallel.value:
        # Dispatch all child jobs simultaneously
        for child in job.children.all():
            dispatch_job(child)
    else:
        # TODO: Implement as in docstring but using zones (and a default queue per zone)
        queue, _ = Queue.get_or_create(account_name="stencila", queue_name="default")
        job.queue = queue

        # TODO: Send the task to the account's vhost on the broker
        # currently, this just sends to the stencila vhost
        task = signature(
            job.method,
            kwargs=job.params,
            queue=job.queue.name,
            task_id=str(job.id),
            app=celery,
        )
        task.apply_async()

        job.status = JobStatus.DISPATCHED.value

    job.save()
    return job
Example #2
0
File: jobs.py Project: jlbrewe/hub
def dispatch_job(job: Job) -> Job:
    """
    Send a job to a queue.

    Decides which queue a job should be sent to and sends it.
    The queue can depend upon both the project and the account (either the
    account that the project is linked to, or the default account of the job
    creator).
    """
    if not JobMethod.is_member(job.method):
        raise ValueError("Unknown job method '{}'".format(job.method))

    if job.method in settings.JOB_METHODS_STAFF_ONLY and (
            not job.creator or not job.creator.is_staff):
        raise PermissionDenied

    if JobMethod.is_compound(job.method):
        children = job.children.all().order_by("id")
        if len(children) == 0:
            # If there are no children (e.g. a pull job for a project with no sources)
            # then job is immediately finished
            job.runtime = 0
            job.is_active = False
            job.status = JobStatus.SUCCESS.value
        else:
            if job.method == JobMethod.parallel.value:
                # Dispatch all child jobs simultaneously
                for child in children:
                    dispatch_job(child)
            else:
                # Dispatch the first child; subsequent children
                # will be status WAITING and will get dispatched later
                # on update of the parent.
                for index, child in enumerate(children):
                    if index == 0:
                        dispatch_job(child)
                    else:
                        child.is_active = True
                        child.status = JobStatus.WAITING.value
                        child.save()

            job.is_active = True
            job.status = JobStatus.DISPATCHED.value
    else:
        # Find queues that have active workers on them
        # order by descending priority
        queues = list(
            Queue.objects.filter(
                workers__in=Worker.objects.filter(
                    # Has not finished
                    finished__isnull=True,
                    # Has been updated in the last x minutes
                    updated__gte=timezone.now() -
                    datetime.timedelta(minutes=15),
                ), ).order_by("priority"))

        # Fallback to the default Stencila queue
        # Apart from anything else having this fallback is useful in development
        # because if means that the `overseer` service does not need to be running
        # in order keep track of the numbers of workers listening on each queue
        # (during development `worker`s listen to the default queue)
        if len(queues) == 0:
            logger.warning("No queues found with active workers")
            queue, _ = Queue.get_or_create(account_name="stencila",
                                           queue_name="default")
        else:
            if job.creator is None or job.project is None:
                # Jobs created by anonymous users go on the lowest
                # priority queue
                priority = 1
            else:
                # The priority of other jobs is determined by the
                # account tier of the project
                priority = job.project.account.tier.id
            queue = queues[min(len(queues), priority) - 1]

        # Add the job's project id, key and secrets to it's kwargs.
        # Doing this here ensures it is done for all jobs
        # and avoids putting the secrets in the job's `params` field.
        kwargs = dict(**job.params) if job.params else {}
        kwargs["project"] = job.project.id if job.project else None
        kwargs["key"] = job.key
        kwargs["secrets"] = job.secrets

        # Send the job to the queue
        task = signature(
            job.method,
            kwargs=kwargs,
            queue=queue.name,
            task_id=str(job.id),
            app=app,
        )
        task.apply_async()

        job.queue = queue
        job.is_active = True
        job.status = JobStatus.DISPATCHED.value

    job.save()
    return job