-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ci: create decision task to run os-integration tasks
- Loading branch information
Showing
10 changed files
with
1,014 additions
and
529 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,4 +10,5 @@ pip>=21.2 | |
pytest | ||
pytest-cov | ||
pytest-mock | ||
taskcluster-taskgraph | ||
yamllint |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from importlib import import_module | ||
|
||
|
||
def register(graph_config): | ||
"""Setup for task generation.""" | ||
|
||
# Import sibling modules, triggering decorators in the process | ||
_import_modules( | ||
[ | ||
"target_tasks", | ||
] | ||
) | ||
|
||
|
||
def _import_modules(modules): | ||
for module in modules: | ||
import_module(f".{module}", package=__name__) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from taskgraph.target_tasks import register_target_task | ||
|
||
|
||
@register_target_task("os-integration") | ||
def os_integration(full_task_graph, parameters, graph_config): | ||
return ["schedule-os-integration"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# This Source Code Form is subject to the terms of the Mozilla Public | ||
# License, v. 2.0. If a copy of the MPL was not distributed with this | ||
# file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
--- | ||
loader: taskgraph.loader.transform:loader | ||
|
||
kind-dependencies: | ||
- tc-admin | ||
|
||
transforms: | ||
- taskgraph.transforms.run:transforms | ||
- taskgraph.transforms.task:transforms | ||
|
||
task-defaults: | ||
run-on-tasks-for: [] # these tasks should only be triggered manually | ||
run: | ||
checkout: true | ||
cwd: '{checkout}' | ||
using: run-task | ||
worker-type: t-linux | ||
worker: | ||
docker-image: {in-tree: python3.11} | ||
max-run-time: 600 | ||
|
||
tasks: | ||
os-integration: | ||
description: "Run Gecko os-integration tests" | ||
dependencies: | ||
apply: tc-admin-apply-staging | ||
scopes: | ||
- secrets:get:project/releng/fxci-config/taskcluster-stage-client | ||
run: | ||
command: >- | ||
pip install --user -r requirements/test.txt && | ||
python taskcluster/scripts/schedule-tasks.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import os | ||
import sys | ||
from functools import cache | ||
from typing import Any | ||
|
||
import slugid | ||
import taskcluster | ||
from taskgraph.util.parameterization import resolve_timestamps | ||
from taskgraph.util.time import current_json_time | ||
|
||
DEFAULT_INDEX_PATHS = [ | ||
"gecko.v2.mozilla-central.latest.taskgraph.decision-os-integration" | ||
] | ||
FIREFOXCI_ROOT_URL = "https://firefox-ci-tc.services.mozilla.com" | ||
STAGING_ROOT_URL = "https://stage.taskcluster.nonprod.cloudops.mozgcp.net" | ||
|
||
|
||
@cache | ||
def get_taskcluster_client(service: str, staging=False): | ||
if staging: | ||
secrets = get_taskcluster_client("secrets") | ||
result = secrets.get("project/releng/fxci-config/taskcluster-stage-client") | ||
assert result | ||
|
||
stage_auth = result["secret"] | ||
assert isinstance(stage_auth, dict) | ||
options = {"rootUrl": STAGING_ROOT_URL, "credentials": stage_auth} | ||
elif "TASKCLUSTER_PROXY_URL" in os.environ: | ||
options = {"rootUrl": os.environ["TASKCLUSTER_PROXY_URL"]} | ||
else: | ||
options = taskcluster.optionsFromEnvironment() | ||
|
||
return getattr(taskcluster, service.capitalize())(options) | ||
|
||
|
||
def find_tasks(index_paths: None | list[str] = None) -> list[dict[str, Any]]: | ||
queue = get_taskcluster_client("queue") | ||
index = get_taskcluster_client("index") | ||
|
||
index_paths = index_paths or DEFAULT_INDEX_PATHS | ||
|
||
tasks = [] | ||
for path in index_paths: | ||
data = index.findTask(path) | ||
assert data | ||
task_id = data["taskId"] | ||
|
||
task_graph = queue.getLatestArtifact(task_id, "public/task-graph.json") | ||
assert task_graph | ||
|
||
for task in task_graph.values(): | ||
assert isinstance(task, dict) | ||
if task.get("attributes", {}).get("unittest_variant") != "os-integration": | ||
continue | ||
|
||
tasks.append(task["task"]) | ||
|
||
return tasks | ||
|
||
|
||
def rewrite_mounts(task_def: dict[str, Any]) -> None: | ||
index = get_taskcluster_client("index") | ||
|
||
for mount in task_def["payload"].get("mounts", []): | ||
content = mount["content"] | ||
if "artifact" not in content: | ||
continue | ||
|
||
if "namespace" in content: | ||
task_id = index.findTask(content["namespace"]) | ||
else: | ||
assert "taskId" in content | ||
task_id = content["taskId"] | ||
|
||
content["url"] = ( | ||
f"{FIREFOXCI_ROOT_URL}/api/queue/v1/task/{task_id}/artifacts/{content['artifact']}" | ||
) | ||
|
||
del content["artifact"] | ||
if "namespace" in content: | ||
del content["namespace"] | ||
if "taskId" in content: | ||
del content["taskId"] | ||
|
||
|
||
def schedule_task(task_def: dict[str, Any], task_id: str | None = None): | ||
queue = get_taskcluster_client("queue") | ||
queue_staging = get_taskcluster_client("queue", staging=True) | ||
env = task_def["payload"].setdefault("env", {}) | ||
|
||
# We set firefoxci as the root url to trick the `fetch-content` script into | ||
# downloading artifacts from the firefoxci instance. This only works for | ||
# public artifacts. | ||
if "MOZ_FETCHES" in env: | ||
task_def["payload"]["command"].insert( | ||
0, ["export", f"TASKCLUSTER_ROOT_URL={FIREFOXCI_ROOT_URL}"] | ||
) | ||
|
||
# Drop down to level 1 resources | ||
for key in ("provisionerId", "taskQueueId", "schedulerId"): | ||
if key in task_def: | ||
task_def[key] = task_def[key].replace("3", "1") | ||
|
||
task_def["taskGroupId"] = os.environ["TASK_ID"] | ||
task_def["priority"] = "low" | ||
rewrite_mounts(task_def) | ||
|
||
del task_def["dependencies"] | ||
del task_def["routes"] | ||
|
||
# It's not possible to trick docker-worker into using image tasks from | ||
# another Taskcluster instance, so we need to schedule those on the staging | ||
# instance as well. | ||
image = task_def["payload"].get("image") | ||
if image and "taskId" in image: | ||
try: | ||
state = queue_staging.status(image["taskId"])["status"]["state"] | ||
task_def["dependencies"] = [image["taskId"]] | ||
except Exception: | ||
state = "not found" | ||
|
||
if state not in ("unscheduled", "pending", "running", "completed"): | ||
image_task_def = queue.task(image["taskId"]) | ||
image_task_def["created"] = {"relative-datestamp": "0 seconds"} | ||
image_task_def["deadline"] = {"relative-datestamp": "1 day"} | ||
image_task_def["expires"] = {"relative-datestamp": "1 year"} | ||
|
||
# We re-use the same taskId as the image task from the firefoxci | ||
# instance. This gives us a hacky implementation of "cached-tasks". | ||
schedule_task(image_task_def, task_id=image["taskId"]) | ||
|
||
now = current_json_time(datetime_format=True) | ||
task_def = resolve_timestamps(now, task_def) | ||
|
||
task_id = task_id or slugid.nice() | ||
print( | ||
f"Creating {task_def['metadata']['name']}: {STAGING_ROOT_URL}/tasks/{task_id}" | ||
) | ||
return queue_staging.createTask(task_id, task_def) | ||
|
||
|
||
def schedule_tasks(): | ||
for task_def in find_tasks(): | ||
schedule_task(task_def) | ||
|
||
|
||
if __name__ == "__main__": | ||
sys.exit(schedule_tasks()) |