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
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