blob: 28a21a1694dca2b45e99a452993757c3ca96683f [file] [log] [blame] [view]
# Detailed Description of LUCI Swarmbucket CQ Flow
### Gerrit -> CQ -> Buildbucket
What to do with a CL is defined by a `cq.cfg` ([example](https://chromium.googlesource.com/infra/infra/+/master/config/cq.cfg)) file. In particular, it says what builders to trigger in what buckets. Each build is parameterized with a JSON blob. `buildset` is a string of the form `patch/gerrit/chromium-review.googlesource.com/<CL id>/<Patch id>`. We can use this form to search for builds from a CL using a query like [this](https://apis-explorer.appspot.com/apis-explorer/?base=https://cr-buildbucket.appspot.com/_ah/api#p/buildbucket/v1/buildbucket.search?bucket=luci.infra.try&tag=buildset%253Apatch%252Fgerrit%252Fchromium-review.googlesource.com%252F375219%252F1&_h=3&).
Here are buildbucket builds that correspond to the CL in the results from the query above. Buildbucket field `parameters_json` contains a string that looks like this:
```json
{
"builder_name": "Infra Win Tester",
"changes": [
{
"author": {
"email": "martiniss@chromium.org"
},
"repo_url": "https:\/\/chromium.googlesource.com\/infra\/infra"
}
],
"properties": {
"attempt_start_ts": 1.472083514e+15,
"category": "cq",
"event.change.id": "infra%2Finfra~master~I2582d437489ec9688788cbaea026d8eba7e790c3",
"event.change.number": 375219,
"event.change.url": "https:\/\/chromium-review.googlesource.com\/#\/c\/375219",
"event.patchSet.ref": "refs\/changes\/19\/375219\/1",
"gerrit": "https:\/\/chromium-review.googlesource.com",
"master": "luci.infra.try",
"patch_project": "infra\/infra",
"patch_storage": "gerrit",
"reason": "CQ",
"revision": "HEAD"
}
}
```
`properties` are generated in `get_try_job_properties` [here](https://chrome-internal.googlesource.com/infra/infra_internal/+/master/commit_queue/pending_manager/gerrit.py) and they all end up available from recipes, as you'll see below.
### Buildbucket -> Swarming
At this point we have a Buildbucket build posted to the buildbucket that has a JSON blob of particular form as its `parameters_json` field.
Configuration for buckets are defined in `cr-buildbucket.cfg`.
`cq.cfg` and `cr-buildbucket.cfg` are in different locations because bucket config is same for entire repo/project, while there could be several CQs for individual branches in the repo. As an example:
```python
buckets {
name: "luci.infra.try"
swarming {
hostname: "chromium-swarm.appspot.com"
url_format: "https://luci-milo.appspot.com/swarming/task/{task_id}"
builder_defaults {
swarming_tags: "allow_milo:1"
dimensions: "cpu:x86-64"
dimensions: "pool:Chrome"
recipe {
repository: "https://chromium.googlesource.com/infra/infra"
}
execution_timeout_secs: 1800 # 30 min
}
...
builders {
category: "Infra"
name: "Infra Win Tester"
dimensions: "os:Windows-7-SP1"
recipe {
name: "infra_repo_trybot"
}
}
...
}
}
```
[Here's](https://chromium.googlesource.com/infra/infra/+/master/appengine/cr-buildbucket/proto/project_config.proto#32) the corresponding `*.proto` file.
Let's follow the build parameters. Looks like `parameters_json` is immediately deserialized into `parameters` field of the `Build` object. Then the service looks at the bucket config and `builder_name` parameter in `parameters` and figures out whether it needs to start a Swarming job (by searching for `Swarming.Build` messages in the bucket proto in `cr-buildbucket.cfg`).
And we end up in `create_task_async` that looks scary. It fetches `Swarming` and `Builder` proto from corresponding `cr-buildbucket.cfg` config and calls `create_task_def_async` (that looks even scarier) to convert build `parameters` and all configs into Swarming task definition.
The function merges `builders` proto with `builder_defaults` proto with `build.parameters['swarming']` (that is missing in our example) into something that looks like this:
```python
builder {
swarming_tags: "allow_milo:1"
dimensions: "cpu:x86-64"
dimensions: "pool:Chrome"
dimensions: "os:Windows-7-SP1"
recipe {
name: "infra_repo_trybot"
repository: "https://chromium.googlesource.com/infra/infra"
}
execution_timeout_secs: 1800
}
```
Then it sees `recipe` section and does some strange transformations to come up with something called `properties_json`. In particular, all `build.parameters['properties']` (generated by CQ at the very start) end up in `properties_json` as is. E.g. `properties_json` looks like:
```json
{
<stuff>
"attempt_start_ts": 1.472083514e+15,
"category": "cq",
"event.change.id": "infra%2Finfra~master~I2582d437489ec9688788cbaea026d8eba7e790c3",
"event.change.number": 375219,
"event.change.url": "https:\/\/chromium-review.googlesource.com\/#\/c\/375219",
"event.patchSet.ref": "refs\/changes\/19\/375219\/1",
"gerrit": "https:\/\/chromium-review.googlesource.com",
"master": "luci.infra.try",
"patch_project": "infra\/infra",
"patch_storage": "gerrit",
"reason": "CQ",
"revision": "HEAD"
}
```
Next Buildbucket fetches something called "task template" from global Buildbucket config. Here it is. It then renders this template using the data generated above. Relevant chunk of the template:
```json
"command": [
"kitchen${EXECUTABLE_SUFFIX}",
"cook",
"-python-path", "third_party",
"-timestamps",
"-repository", "$repository",
"-revision", "$revision",
"-recipe", "$recipe",
"-properties", "$properties_json",
"-logdog-host", "luci-logdog.appspot.com",
"-logdog-project", "$project",
"-logdog-tee",
"-logdog-enable-annotee"
],
```
Here
`$repository` comes from `cr-buildbucket.cfg` ("https://chromium.googlesource.com/infra/infra" in our case)
`$revision` comes from `build.properties['swarming']['recipe']['revision']` (empty in our case)
`$recipe` comes from `cr-buildbucket.cfg` ("infra_repo_trybot" in our case).
`$properties_json` is JSON blob I've described above (the one that contains data from CQ).
`$project` is indirectly derived from `cr-buildbucket.cfg` (it's the name of the project that defined the bucket, "infra" in our case).
So we end up with a Swarming task that invokes "kitchen" tool, passing it a JSON blob with properties. Here is the example: https://chromium-swarm.appspot.com/user/task/30d7d5a2288f9710
### Swarming -> Recipe engine (through kitchen)
Kitchen is a tool that bootstraps recipe engine and its environment.
It's source code is [here](https://chromium.googlesource.com/infra/infra/+/master/go/src/infra/tools/kitchen/).
Let's follow the build properties.
Kitchen reads them from the command line and appends one more property "path_config": "swarmbucket".
It then checks out `$repository` at `$revision`. In our case "https://chromium.googlesource.com/infra/infra" at FETCH_HEAD.
Next it prepares a command to invoke recipe engine. Reads "infra/config/recipes.cfg" from the checked out repo (in our case this file), there it finds "recipes_path" field ("recipes" in our case). It uses it to construct a path to recipes.py file. This one.
The final command is
python ...../recipes.py run --properties-file <path to JSON> ... <recipe>
(In our case "<recipe>" is "infra_repo_trybot").
`--properties-file` points to a JSON file with serialized build properties, in our case it looks like this:
```json
{
"attempt_start_ts": 1.472083514e+15,
"blamelist": [
"martiniss@chromium.org"
],
"buildername": "Infra Win Tester",
"category": "cq",
"event.change.id": "infra%2Finfra~master~I2582d437489ec9688788cbaea026d8eba7e790c3",
"event.change.number": 375219,
"event.change.url": "https:\/\/chromium-review.googlesource.com\/#\/c\/375219",
"event.patchSet.ref": "refs\/changes\/19\/375219\/1",
"gerrit": "https:\/\/chromium-review.googlesource.com",
"master": "luci.infra.try",
"patch_project": "infra\/infra",
"patch_storage": "gerrit",
"path_config": "swarmbucket",
"reason": "CQ",
"repository": "https:\/\/chromium.googlesource.com\/infra\/infra",
"revision": "HEAD"
}
```
As a reminder, it came directly from CQ, with few fields like `buildername`, `blamelist` and `category` added by Swarmbucket code.
### Recipe engine -> recipes & recipe modules
[recipes.py](https://chromium.googlesource.com/infra/infra/+/master/recipes/recipes.py) checks out recipe\_engine code and all dependent modules (based on [recipes.cfg](https://chromium.googlesource.com/infra/infra/+/master/infra/config/recipes.cfg)) and then it passes control to the actual recipe.
The properties are available via `api.properties` dictionary. (or `self.m.properties` if used from inside some recipe module). Documentation on properties in recipes is [here](https://github.com/luci/recipes-py/blob/master/doc/user_guide.md#properties-are-the-primary-input-for-your-recipes).
For example `event.patchSet.ref` property (which is `refs/changes/19/375219/1` in our case, as supplied by CQ) is
[transformed](https://cs.chromium.org/chromium/tools/depot_tools/recipe_modules/bot_update/__init__.py?q=event.patchSet.ref&l=22) into `gerrit_ref` parameter of `bot_update` recipe module.
So `bot_update` recipe module ends up "knowing" about CL ref as `self._gerrit_ref` (and it then passes it to actual bot_update.py script).