summaryrefslogtreecommitdiff
path: root/buildscripts/patch_builds/task_generation.py
blob: d8d9ff267d4a7bef63f357ca6c32280e88d22a73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""Utilities to help generate evergreen tasks."""
from __future__ import annotations

from typing import Any, Dict, Optional, List

from shrub.v2 import FunctionCall, ShrubProject
from shrub.v2.command import timeout_update, ShrubCommand
from structlog import get_logger

from buildscripts.task_generation.constants import CONFIGURE_EVG_CREDENTIALS

LOGGER = get_logger(__name__)
MAX_SHRUB_TASKS_FOR_SINGLE_TASK = 1000

MAX_GEN_TASKS_ERR = """
********************************************************************************
It appears we are trying to generate more tasks than are supported by burn_in.
This likely means that a large number of tests have been changed in this patch
build. 

burn_in supports of a max of {max_tasks} and your patch requires {patch_tasks}.

If you would like to validate your changes with burn_in, you will need to break
your patch up into patches that each touch a fewer number of tests.
********************************************************************************
"""


def resmoke_commands(run_tests_fn_name: str, run_tests_vars: Dict[str, Any],
                     timeout_info: TimeoutInfo,
                     require_multiversion_setup: Optional[bool] = False) -> List[ShrubCommand]:
    """
    Create a list of commands to run a resmoke task.

    Used by burn_in* only. Other tasks use a standalone multiversion decorator.

    :param run_tests_fn_name: Name of function to run resmoke tests.
    :param run_tests_vars: Dictionary of variables to pass to run_tests function.
    :param timeout_info: Timeout info for task.
    :param require_multiversion_setup: Requires downloading Multiversion binaries.
    :return: List of commands to run a resmoke task.
    """

    commands = [
        timeout_info.cmd,
        FunctionCall("git get project no modules") if require_multiversion_setup else None,
        FunctionCall("add git tag") if require_multiversion_setup else None,
        FunctionCall("do setup"),
        FunctionCall(CONFIGURE_EVG_CREDENTIALS),
        FunctionCall("do multiversion setup") if require_multiversion_setup else None,
        FunctionCall(run_tests_fn_name, run_tests_vars),
    ]

    return [cmd for cmd in commands if cmd]


class TimeoutInfo(object):
    """Timeout information for a generated task."""

    def __init__(self, use_defaults, exec_timeout=None, timeout=None):
        """
        Create timeout information.

        :param use_defaults: Don't overwrite any timeouts.
        :param exec_timeout: Exec timeout value to overwrite.
        :param timeout: Timeout value to overwrite.
        """
        self.use_defaults = use_defaults
        self.exec_timeout = exec_timeout
        self.timeout = timeout

    @classmethod
    def default_timeout(cls):
        """Create an instance of TimeoutInfo that uses default timeouts."""
        return cls(True)

    @classmethod
    def overridden(cls, exec_timeout=None, timeout=None):
        """
        Create an instance of TimeoutInfo that overwrites timeouts.

        :param exec_timeout: Exec timeout value to overwrite.
        :param timeout: Timeout value to overwrite.
        :return: TimeoutInfo that overwrites given timeouts.
        """
        if not exec_timeout and not timeout:
            raise ValueError("Must override either 'exec_timeout' or 'timeout'")
        return cls(False, exec_timeout=exec_timeout, timeout=timeout)

    @property
    def cmd(self):
        """Create a command that sets timeouts as specified."""
        if not self.use_defaults:
            return timeout_update(exec_timeout_secs=self.exec_timeout, timeout_secs=self.timeout)

        return None

    def __repr__(self):
        """Create a string representation for debugging."""
        if self.use_defaults:
            return "<No Timeout Override>"
        return f"<exec_timeout={self.exec_timeout}, timeout={self.timeout}>"


def validate_task_generation_limit(shrub_project: ShrubProject) -> bool:
    """
    Determine if this shrub configuration generates less than the limit.

    :param shrub_project: Shrub configuration to validate.
    :return: True if the configuration is under the limit.
    """
    tasks_to_create = len(shrub_project.all_tasks())
    if tasks_to_create > MAX_SHRUB_TASKS_FOR_SINGLE_TASK:
        print(
            MAX_GEN_TASKS_ERR.format(max_tasks=MAX_SHRUB_TASKS_FOR_SINGLE_TASK,
                                     patch_tasks=tasks_to_create))
        LOGGER.warning("Attempting to create more tasks than max, aborting", tasks=tasks_to_create,
                       max=MAX_SHRUB_TASKS_FOR_SINGLE_TASK)
        return False
    return True