tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

retrigger.py (9779B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 
      6 import itertools
      7 import logging
      8 import sys
      9 import textwrap
     10 
     11 from taskgraph.util.taskcluster import get_task_definition, rerun_task
     12 
     13 from gecko_taskgraph.util.taskcluster import state_task
     14 
     15 from .registry import register_callback_action
     16 from .util import (
     17    create_task_from_def,
     18    create_tasks,
     19    fetch_graph_and_labels,
     20    get_tasks_with_downstream,
     21    relativize_datestamps,
     22 )
     23 
     24 logger = logging.getLogger(__name__)
     25 
     26 RERUN_STATES = ("exception", "failed")
     27 
     28 
     29 def _should_retrigger(task_graph, label):
     30    """
     31    Return whether a given task in the taskgraph should be retriggered.
     32 
     33    This handles the case where the task isn't there by assuming it should not be.
     34    """
     35    if label not in task_graph:
     36        logger.info(
     37            f"Task {label} not in full taskgraph, assuming task should not be retriggered."
     38        )
     39        return False
     40    return task_graph[label].attributes.get("retrigger", False)
     41 
     42 
     43 @register_callback_action(
     44    title="Retrigger",
     45    name="retrigger",
     46    symbol="rt",
     47    cb_name="retrigger-decision",
     48    description=textwrap.dedent(
     49        """\
     50        Create a clone of the task (retriggering decision, action, and cron tasks requires
     51        special scopes)."""
     52    ),
     53    order=11,
     54    context=[
     55        {"kind": "decision-task"},
     56        {"kind": "action-callback"},
     57        {"kind": "cron-task"},
     58        {"action": "backfill-task"},
     59    ],
     60 )
     61 def retrigger_decision_action(parameters, graph_config, input, task_group_id, task_id):
     62    """For a single task, we try to just run exactly the same task once more.
     63    It's quite possible that we don't have the scopes to do so (especially for
     64    an action), but this is best-effort."""
     65 
     66    # make all of the timestamps relative; they will then be turned back into
     67    # absolute timestamps relative to the current time.
     68    task = get_task_definition(task_id)
     69    task = relativize_datestamps(task)
     70    create_task_from_def(
     71        task, parameters["level"], action_tag="retrigger-decision-task"
     72    )
     73 
     74 
     75 @register_callback_action(
     76    title="Retrigger",
     77    name="retrigger",
     78    symbol="rt",
     79    description=("Create a clone of the task."),
     80    order=19,  # must be greater than other orders in this file, as this is the fallback version
     81    context=[{"retrigger": "true"}],
     82    schema={
     83        "type": "object",
     84        "properties": {
     85            "downstream": {
     86                "type": "boolean",
     87                "description": (
     88                    "If true, downstream tasks from this one will be cloned as well. "
     89                    "The dependencies will be updated to work with the new task at the root."
     90                ),
     91                "default": False,
     92            },
     93            "times": {
     94                "type": "integer",
     95                "default": 1,
     96                "minimum": 1,
     97                "maximum": 100,
     98                "title": "Times",
     99                "description": "How many times to run each task.",
    100            },
    101        },
    102    },
    103 )
    104 @register_callback_action(
    105    title="Retrigger (disabled)",
    106    name="retrigger",
    107    cb_name="retrigger-disabled",
    108    symbol="rt",
    109    description=(
    110        "Create a clone of the task.\n\n"
    111        "This type of task should typically be re-run instead of re-triggered."
    112    ),
    113    order=20,  # must be greater than other orders in this file, as this is the fallback version
    114    context=[{}],
    115    schema={
    116        "type": "object",
    117        "properties": {
    118            "downstream": {
    119                "type": "boolean",
    120                "description": (
    121                    "If true, downstream tasks from this one will be cloned as well. "
    122                    "The dependencies will be updated to work with the new task at the root."
    123                ),
    124                "default": False,
    125            },
    126            "times": {
    127                "type": "integer",
    128                "default": 1,
    129                "minimum": 1,
    130                "maximum": 100,
    131                "title": "Times",
    132                "description": "How many times to run each task.",
    133            },
    134            "force": {
    135                "type": "boolean",
    136                "default": False,
    137                "description": (
    138                    "This task should not be re-triggered. "
    139                    "This can be overridden by passing `true` here."
    140                ),
    141            },
    142        },
    143    },
    144 )
    145 def retrigger_action(parameters, graph_config, input, task_group_id, task_id):
    146    decision_task_id, full_task_graph, label_to_taskid, _ = fetch_graph_and_labels(
    147        parameters, graph_config
    148    )
    149 
    150    task = get_task_definition(task_id)
    151    label = task["metadata"]["name"]
    152 
    153    with_downstream = " "
    154    to_run = [label]
    155 
    156    if not input.get("force", None) and not _should_retrigger(full_task_graph, label):
    157        logger.error(
    158            f"fatal error: Not retriggering task {label}, task should not be retriggered "
    159            "and force not specified."
    160        )
    161        sys.exit(1)
    162 
    163    if input.get("downstream"):
    164        to_run = get_tasks_with_downstream(to_run, full_task_graph, label_to_taskid)
    165        with_downstream = " (with downstream) "
    166 
    167    times = input.get("times", 1)
    168 
    169    def modifier(task):
    170        task.attributes["task_duplicates"] = times
    171        return task
    172 
    173    create_tasks(
    174        graph_config,
    175        to_run,
    176        full_task_graph,
    177        label_to_taskid,
    178        parameters,
    179        decision_task_id,
    180        action_tag="retrigger-task",
    181        modifier=modifier,
    182    )
    183    logger.info(f"Scheduled {label}{with_downstream}({times} times)")
    184 
    185 
    186 @register_callback_action(
    187    title="Rerun",
    188    name="rerun",
    189    symbol="rr",
    190    description=(
    191        "Rerun a task.\n\n"
    192        "This only works on failed or exception tasks in the original taskgraph,"
    193        " and is CoT friendly."
    194    ),
    195    order=300,
    196    context=[{}],
    197    schema={"type": "object", "properties": {}},
    198 )
    199 def rerun_action(parameters, graph_config, input, task_group_id, task_id):
    200    task = get_task_definition(task_id)
    201    parameters = dict(parameters)
    202    (
    203        decision_task_id,
    204        full_task_graph,
    205        label_to_taskid,
    206        label_to_taskids,
    207    ) = fetch_graph_and_labels(parameters, graph_config)
    208    label = task["metadata"]["name"]
    209    if task_id not in itertools.chain(*label_to_taskid.values()):
    210        # XXX the error message is wrong, we're also looking at label_to_taskid
    211        #     from action and cron tasks on that revision
    212        logger.error(
    213            f"Refusing to rerun {label}: taskId {task_id} not in decision task {decision_task_id} label_to_taskid!"
    214        )
    215 
    216    _rerun_task(task_id, label)
    217 
    218 
    219 def _rerun_task(task_id, label):
    220    state = state_task(task_id)
    221    if state not in RERUN_STATES:
    222        logger.warning(
    223            f"No need to rerun {label}: state '{state}' not in {RERUN_STATES}!"
    224        )
    225        return
    226    rerun_task(task_id)
    227    logger.info(f"Reran {label}")
    228 
    229 
    230 @register_callback_action(
    231    title="Retrigger",
    232    name="retrigger-multiple",
    233    symbol="rt",
    234    description=("Create a clone of the task."),
    235    context=[],
    236    schema={
    237        "type": "object",
    238        "properties": {
    239            "requests": {
    240                "type": "array",
    241                "items": {
    242                    "type": "object",
    243                    "properties": {
    244                        "tasks": {
    245                            "type": "array",
    246                            "description": "An array of task labels",
    247                            "items": {"type": "string"},
    248                        },
    249                        "times": {
    250                            "type": "integer",
    251                            "minimum": 1,
    252                            "maximum": 100,
    253                            "title": "Times",
    254                            "description": "How many times to run each task.",
    255                        },
    256                    },
    257                    "additionalProperties": False,
    258                },
    259            },
    260            "additionalProperties": False,
    261        },
    262    },
    263 )
    264 def retrigger_multiple(parameters, graph_config, input, task_group_id, task_id):
    265    (
    266        decision_task_id,
    267        full_task_graph,
    268        label_to_taskid,
    269        label_to_taskids,
    270    ) = fetch_graph_and_labels(parameters, graph_config)
    271 
    272    def modifier(task):
    273        for request in input.get("requests", []):
    274            if task.attributes.get("retrigger", False) and task.label in request.get(
    275                "tasks"
    276            ):
    277                task.attributes["task_duplicates"] = request.get("times", 1)
    278        return task
    279 
    280    retrigger_tasks = []
    281 
    282    for request in input.get("requests", []):
    283        rerun_tasks = [
    284            label
    285            for label in request.get("tasks")
    286            if not _should_retrigger(full_task_graph, label)
    287        ]
    288        for label in rerun_tasks:
    289            # XXX we should not re-run tasks pulled in from other pushes
    290            # In practice, this shouldn't matter, as only completed tasks
    291            # are pulled in from other pushes and treeherder won't pass
    292            # those labels.
    293            for rerun_taskid in label_to_taskids[label]:
    294                _rerun_task(rerun_taskid, label)
    295 
    296        retrigger_tasks.extend([
    297            label
    298            for label in request.get("tasks")
    299            if _should_retrigger(full_task_graph, label)
    300        ])
    301 
    302    create_tasks(
    303        graph_config,
    304        retrigger_tasks,
    305        full_task_graph,
    306        label_to_taskid,
    307        parameters,
    308        decision_task_id,
    309        modifier=modifier,
    310        action_tag="retrigger-multiple-task",
    311    )