---
# System prepended metadata

title: Launching a Job

---

# Launching a Job

Here we will look at launching a job. We will not go line-by-line in the
code, but will be making direct references to the code and classes used as of
the writing of this document.

Note that different "kinds" of jobs work a bit differently, and while we may
focus on this later in the document, we currently limit ourselves to focusing on
a normal `JobTemplate`, being run by a normal user, over a normal inventory and
playbook. Think: stupidest, most common, simple case.

Our journey will begin with the call into the API, ignoring how the API is set
up and routed. In `awx.api.views`, we are interested in the `JobTemplateLaunch`
view class. (Note that for now, we will also ignore the Django Rest Framework
class hierarchy, except to say that this class extends
[`RetrieveAPIView`](https://www.django-rest-framework.org/api-guide/generic-views/#retrieveapiview)
from Django Rest Framework, along with a few other classes found in
`awx.api.generics`. The Django Rest Framework API guide goes into sufficient
detail for understanding the base classes of these classes). The AWX-specific
subclasses of these base classes will be discussed in a different document.

A few asides and random things worth noting:

- Although I don't quite _fully_ understand the purpose yet, `update_raw_data`
  seems to only be used when the Browsable API is being used. (A `Renderer` sets
  a flag, and `GenericAPIView#get_serializer` only calls the method if that flag
  exists). Thus, we will ignore it for now.

- The `BaseSerializer` that all the other serializers inherit from implements a
  `validate` override which loads up an instance of the model (if one does not
  already exist) and then calls its `full_clean` method (the default Django
  validation method). It then turns any `DjangoValidationError` into a
  DRF `ValidationError`.

## From `POST` to `Job`

A request comes into the framework and eventually gets routed to the
`JobTemplateLaunch` view. The `post` method is called, and immediately the data
is manipulated slightly for backwards compatibility (the
`modernize_launch_payload` method). Then the `JobLaunchSerializer` is
instantiated and used to validate the raw data.

Note that during this process, the model we are working with is `JobTemplate`,
not `Job`, even though we are launching a job. The job itself actually gets
launched as a method on the `JobTemplate` model (or rather, its superclass),
which we will discuss later.

Importantly it is given a `context` which includes the `JobTemplate` being
launched.

We'll deep-dive the serializer a bit, and then return to the view.

### The `JobLaunchSerializer`

`JobLaunchSerializer` is where the request is parsed and validated. Basic
understanding of Django Rest Framework is useful to understand how the
serializer works (I spent 2-3 days starting a personal project with DRF and
although there is a lot of magic, I already feel more comfortable with what is
going on). For example, the `JobLaunchSerializer` has a line:

```python
defaults = serializers.SerializerMethodField()`
```

There is magic in DRF that that will automatically route this field to a
`get_<fieldname>` method (in this case `get_defaults`). In the response, a
`defaults` field will be added to the JSON (or whatever format is being used,
DRF is flexible here), and the value of that field will be the return value of
the `get_defaults` method. All of this is explained well in the DRF
documentation and API guide, so I won't go into it more here.

DRF does the same kind of magic for validation methods, allowing you to write a
method such as `validate_<fieldname>` and have it called when the field is being
validated.

By far, the biggest part of the `JobLaunchSerializer` is the `validate` method
which encompasses the bulk of the validation logic. It is fairly straightforward
(if a bit lengthy), once you know where the job template is coming from (the
context) and how. However, it does call into the `JobTemplate`'s
`_accept_or_ignore_job_kwargs` method, which has a fair bit of gnarly logic in
it. We will come back to this later once we understand the hierarchy of the
`JobTemplate` class and the classes it inherits from.

### The `JobTemplateLaunch` View

Returning to the `JobTemplateLaunch` view, it's clear that upon invalid data, a
`400` response is returned, and if the user does not have `add` permissions on
on `JobLaunchConfig` (discussed later), a `PermissionDenied` exception is
raised, resulting in a `403` response.

### Unified Jobs, Jobs, and Polymorphism

We will now begin to dig into the job-related models and their
relationships. The relationships between these models is complicated by the fact
that model polymorphism is used (via the `django-polymorphic` package).

Although at first glance you would think that `Job` is the base class for all
... jobs ... alas, it is not. The base class is `UnifiedJob`, which is a
polymorphic model. This means that if you do something like
`UnifiedJob.objects.all()`, you will get a queryset of all the different kinds
of jobs, including `Job`, `AdHocCommand`, `ProjectUpdate`, `InventoryUpdate`,
`SystemJob`, and `WorkflowJob`. :exploding_head:

[To be continued...]

### The Task Subsystem (here we go...)

Once the job lands in the database, the view calls `UnifiedJob#signal_start`,
which then sets the `status` field of the job to `pending`, emits a websocket
message (we'll discuss this in detail later), and finally we hit this block (in
`awx/main/models/unified.jobs.py`):

```python
if self.dependencies_processed:
    ScheduleTaskManager().schedule()
else:
    ScheduleDependencyManager().schedule()
```

Note that `self.dependencies_processed` depends on the kind of job being run,
and properties of that job. For a normal `Job` like we are following here, it
depends on if the project the `Job` belongs to is set to be updated on launch.
If it is, the dependency manager is used and the project update is a dependency
of the `Job`. Here we wish to follow the simple route; we will assume the
project is _not_ configured to run on job launch. Thus the path we follow is
`ScheduleTaskManager().schedule()`.

Moving on to `ScheduleTaskManager` (which we find in
`awx/main/utils/common.py`), we can follow a small hierarchy of classes
and ultimately find that we are doing this:

```python
connection.on_commit(lambda: self.manager.delay())
```

> []