12d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
22d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone# Use of this source code is governed by a BSD-style license that can be
32d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone# found in the LICENSE file.
42d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
53197b39f82eb92afff33c7d44b805afe120c7627Fang Dengimport logging
6c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00eAlex Miller
7c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00eAlex Millerimport common
8c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00eAlex Millerfrom autotest_lib.client.common_lib import priorities
9c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00eAlex Miller
102d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
1193f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone"""Module containing base class and methods for working with scheduler events.
1293f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone
1393f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone@var _SECTION_SUFFIX: suffix of config file sections that apply to derived
1493f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone                      classes of TimedEvent.
1593f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone"""
1693f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone
1793f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone
1893f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone_SECTION_SUFFIX = '_params'
1993f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone
202d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
2193f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masonedef SectionName(keyword):
2293f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone    """Generate a section name for a *Event config stanza."""
2393f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone    return keyword + _SECTION_SUFFIX
2493f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone
2593f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone
2693f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masonedef HonoredSection(section):
2793f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone    """Returns True if section is something _ParseConfig() might consume."""
2893f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone    return section.endswith(_SECTION_SUFFIX)
2993f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone
3093f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone
3167f06d6003bb11f03f925810272f595d79dce44aChris Masonedef BuildName(board, type, milestone, manifest):
3267f06d6003bb11f03f925810272f595d79dce44aChris Masone    """Format a build name, given board, type, milestone, and manifest number.
3367f06d6003bb11f03f925810272f595d79dce44aChris Masone
3467f06d6003bb11f03f925810272f595d79dce44aChris Masone    @param board: board the manifest is for, e.g. x86-alex.
3567f06d6003bb11f03f925810272f595d79dce44aChris Masone    @param type: one of 'release', 'factory', or 'firmware'
3667f06d6003bb11f03f925810272f595d79dce44aChris Masone    @param milestone: (numeric) milestone the manifest was associated with.
3767f06d6003bb11f03f925810272f595d79dce44aChris Masone    @param manifest: manifest number, e.g. '2015.0.0'
3867f06d6003bb11f03f925810272f595d79dce44aChris Masone    @return a build name, e.g. 'x86-alex-release/R20-2015.0.0'
3967f06d6003bb11f03f925810272f595d79dce44aChris Masone    """
4067f06d6003bb11f03f925810272f595d79dce44aChris Masone    return '%s-%s/R%s-%s' % (board, type, milestone, manifest)
4167f06d6003bb11f03f925810272f595d79dce44aChris Masone
4267f06d6003bb11f03f925810272f595d79dce44aChris Masone
432d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masoneclass BaseEvent(object):
442d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone    """Represents a supported scheduler event.
452d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
46c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00eAlex Miller    @var PRIORITY: The priority of suites kicked off by this event.
47c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00eAlex Miller    @var TIMEOUT: The max lifetime of suites kicked off by this event.
48c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00eAlex Miller
492d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone    @var _keyword: the keyword/name of this event, e.g. new_build, nightly.
5093f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone    @var _mv: ManifestVersions instance used to query for new builds, etc.
51855d86f838faccb4ad112a222412e29c047b7d3eChris Masone    @var _always_handle: whether to make ShouldHandle always return True.
522d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone    @var _tasks: set of Task instances that run on this event.
532d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone                 Use a set so that instances that encode logically equivalent
542d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone                 Tasks get de-duped before we even try to schedule them.
552d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone    """
562d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
572d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
58c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00eAlex Miller    PRIORITY = priorities.Priority.DEFAULT
59c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00eAlex Miller    TIMEOUT = 24  # Hours
60c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00eAlex Miller
61c7bcf8bf70d6e40aa4d37528f75b5a98b0f7a00eAlex Miller
6296f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone    @classmethod
6393f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone    def CreateFromConfig(cls, config, manifest_versions):
6496f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone        """Instantiate a cls object, options from |config|."""
6593f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone        return cls(manifest_versions, **cls._ParseConfig(config))
6696f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone
6796f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone
6896f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone    @classmethod
6996f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone    def _ParseConfig(cls, config):
70fe5a509f533e7de2531a0d0838146859b543569cChris Masone        """Parse config and return a dict of parameters for this event.
71fe5a509f533e7de2531a0d0838146859b543569cChris Masone
7293f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone        Uses cls.KEYWORD to determine which section to look at, and parses
7393f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone        the following options:
7493f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone          always_handle: If True, ShouldHandle() must always return True.
7593f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone
7693f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone        @param config: a ForgivingConfigParser instance.
77fe5a509f533e7de2531a0d0838146859b543569cChris Masone        """
7893f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone        section = SectionName(cls.KEYWORD)
7993f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone        return {'always_handle': config.getboolean(section, 'always_handle')}
8096f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone
8196f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone
8293f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone    def __init__(self, keyword, manifest_versions, always_handle):
832d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        """Constructor.
842d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
852d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        @param keyword: the keyword/name of this event, e.g. nightly.
8693f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone        @param manifest_versions: ManifestVersions instance to use for querying.
8793f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone        @param always_handle: If True, make ShouldHandle() always return True.
882d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        """
892d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        self._keyword = keyword
9093f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone        self._mv = manifest_versions
9196f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone        self._tasks = set()
92855d86f838faccb4ad112a222412e29c047b7d3eChris Masone        self._always_handle = always_handle
932d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
942d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
952d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone    @property
962d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone    def keyword(self):
972d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        """Getter for private |self._keyword| property."""
982d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        return self._keyword
992d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
1002d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
10196f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone    @property
10296f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone    def tasks(self):
10396f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone        return self._tasks
10496f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone
10596f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone
10696f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone    @tasks.setter
10796f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone    def tasks(self, iterable_of_tasks):
10896f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone        """Set the tasks property with an iterable.
10996f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone
11096f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone        @param iterable_of_tasks: list of Task instances that can fire on this.
11196f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone        """
11296f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone        self._tasks = set(iterable_of_tasks)
11396f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone
11496f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone
115855d86f838faccb4ad112a222412e29c047b7d3eChris Masone    def Merge(self, to_merge):
116855d86f838faccb4ad112a222412e29c047b7d3eChris Masone        """Merge this event with to_merge, changing all mutable properties.
117855d86f838faccb4ad112a222412e29c047b7d3eChris Masone
118855d86f838faccb4ad112a222412e29c047b7d3eChris Masone        keyword remains unchanged; the following take on values from to_merge:
119855d86f838faccb4ad112a222412e29c047b7d3eChris Masone          _tasks
120855d86f838faccb4ad112a222412e29c047b7d3eChris Masone          _mv
121855d86f838faccb4ad112a222412e29c047b7d3eChris Masone          _always_handle
122855d86f838faccb4ad112a222412e29c047b7d3eChris Masone
123855d86f838faccb4ad112a222412e29c047b7d3eChris Masone        @param to_merge: A BaseEvent instance to merge into this instance.
124855d86f838faccb4ad112a222412e29c047b7d3eChris Masone        """
125855d86f838faccb4ad112a222412e29c047b7d3eChris Masone        self.tasks = to_merge.tasks
126855d86f838faccb4ad112a222412e29c047b7d3eChris Masone        self._mv = to_merge._mv
127855d86f838faccb4ad112a222412e29c047b7d3eChris Masone        self._always_handle = to_merge._always_handle
128855d86f838faccb4ad112a222412e29c047b7d3eChris Masone
129855d86f838faccb4ad112a222412e29c047b7d3eChris Masone
13073a7838db228a1b4a6141b5ba9efa4d1d1cf6251Chris Masone    def Prepare(self):
13173a7838db228a1b4a6141b5ba9efa4d1d1cf6251Chris Masone        """Perform any one-time setup that must occur before [Should]Handle().
13273a7838db228a1b4a6141b5ba9efa4d1d1cf6251Chris Masone
13373a7838db228a1b4a6141b5ba9efa4d1d1cf6251Chris Masone        Must be implemented by subclasses.
13473a7838db228a1b4a6141b5ba9efa4d1d1cf6251Chris Masone        """
13573a7838db228a1b4a6141b5ba9efa4d1d1cf6251Chris Masone        raise NotImplementedError()
13673a7838db228a1b4a6141b5ba9efa4d1d1cf6251Chris Masone
13773a7838db228a1b4a6141b5ba9efa4d1d1cf6251Chris Masone
138d17d185d7abd68584f3756a89a7475a5b6109f34Chris Masone    def GetBranchBuildsForBoard(self, board):
139fe5a509f533e7de2531a0d0838146859b543569cChris Masone        """Get per-branch, per-board builds since last run of this event.
1405bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone
1415bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone        @param board: the board whose builds we want.
14205b1944ebcb8a62ee0cc38a7f90b4131ededab8fChris Masone        @return {branch: [build-name]}
143fe5a509f533e7de2531a0d0838146859b543569cChris Masone
144fe5a509f533e7de2531a0d0838146859b543569cChris Masone        Must be implemented by subclasses.
1455bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone        """
1465bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone        raise NotImplementedError()
1475bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone
1485bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone
1492d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone    def ShouldHandle(self):
150e8bebaf10711ca802d40c0b7f412e3910999159eChris Masone        """Returns True if this BaseEvent should be Handle()'d, False if not.
151e8bebaf10711ca802d40c0b7f412e3910999159eChris Masone
152855d86f838faccb4ad112a222412e29c047b7d3eChris Masone        Must be extended by subclasses.
153e8bebaf10711ca802d40c0b7f412e3910999159eChris Masone        """
154855d86f838faccb4ad112a222412e29c047b7d3eChris Masone        return self._always_handle
155e8bebaf10711ca802d40c0b7f412e3910999159eChris Masone
156e8bebaf10711ca802d40c0b7f412e3910999159eChris Masone
157e8bebaf10711ca802d40c0b7f412e3910999159eChris Masone    def UpdateCriteria(self):
158e8bebaf10711ca802d40c0b7f412e3910999159eChris Masone        """Updates internal state used to decide if this event ShouldHandle()
1592d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
1602d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        Must be implemented by subclasses.
1612d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        """
1622d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        raise NotImplementedError()
1632d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
1642d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
1659f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi    def FilterTasks(self):
1669f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi        """Filter the tasks to only return tasks should run now.
1679f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi
1689f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi        One use case is that Nightly task can run at each hour. The override of
1699f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi        this function in Nightly class will only return the tasks set to run in
1709f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi        current hour.
1719f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi
1729f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi        @return: A list of tasks can run now.
1739f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi        """
1749f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi        return list(self.tasks)
1759f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi
1769f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi
17796f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone    def Handle(self, scheduler, branch_builds, board, force=False):
178511a9e3a1cfc2c6dd1263c8f6cb667ab12cd7a6fAlex Miller        """Runs all tasks in self._tasks that if scheduled, can be
179511a9e3a1cfc2c6dd1263c8f6cb667ab12cd7a6fAlex Miller        successfully run.
1802d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone
1812d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        @param scheduler: an instance of DedupingScheduler, as defined in
1822d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone                          deduping_scheduler.py
18396f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone        @param branch_builds: a dict mapping branch name to the build to
18496f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone                              install for that branch, e.g.
18505b1944ebcb8a62ee0cc38a7f90b4131ededab8fChris Masone                              {'R18': ['x86-alex-release/R18-1655.0.0'],
18605b1944ebcb8a62ee0cc38a7f90b4131ededab8fChris Masone                               'R19': ['x86-alex-release/R19-2077.0.0']
18705b1944ebcb8a62ee0cc38a7f90b4131ededab8fChris Masone                               'factory': ['x86-alex-factory/R19-2077.0.5']}
18896f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone        @param board: the board against which to Run() all of self._tasks.
18996f1663ecc7d8821e804b0256fc68be0756ba66eChris Masone        @param force: Tell every Task to always Run().
1902d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        """
191a3a38172021c03d5c048681467efd132eece72d1Chris Masone        logging.info('Handling %s for %s', self.keyword, board)
1922d61ca263d3e571b19604ea4c9d7b55d83c28405Chris Masone        # we need to iterate over an immutable copy of self._tasks
1939f256d91d378cdc0bbffa607ef3572f006d0b0dcDan Shi        for task in self.FilterTasks():
1947ce1b36592894e77c9ef2e05f26657891b47484eAlex Miller            if task.AvailableHosts(scheduler, board):
19515d42311147778148e0a80b703e789abc6bbd12dDan Shi                if not task.Run(scheduler, branch_builds, board, force,
19615d42311147778148e0a80b703e789abc6bbd12dDan Shi                                self._mv):
197511a9e3a1cfc2c6dd1263c8f6cb667ab12cd7a6fAlex Miller                    self._tasks.remove(task)
198d621cf2a52f9c13a0880b431fea3898adc8209a3Alex Miller            elif task.ShouldHaveAvailableHosts():
199cd214e0d2fe515b3cc7faacc44e4e539142670eaChris Masone                logging.warning('Skipping %s on %s, due to lack of hosts.',
200cd214e0d2fe515b3cc7faacc44e4e539142670eaChris Masone                                task, board)
201