Home | History | Annotate | Download | only in suite_scheduler
      1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import datetime
      6 import logging
      7 
      8 import common
      9 from autotest_lib.client.common_lib import priorities
     10 
     11 import base_event
     12 import task
     13 
     14 
     15 class TimedEvent(base_event.BaseEvent):
     16     """Base class for events that trigger based on time/day.
     17 
     18     @var _deadline: If this time has passed, ShouldHandle() returns True.
     19     """
     20 
     21     # Number of days between each timed event to trigger. Default to 1.
     22     DAYS_INTERVAL = 1
     23 
     24 
     25     def __init__(self, keyword, manifest_versions, always_handle, deadline):
     26         """Constructor.
     27 
     28         @param keyword: the keyword/name of this event, e.g. nightly.
     29         @param manifest_versions: ManifestVersions instance to use for querying.
     30         @param always_handle: If True, make ShouldHandle() always return True.
     31         @param deadline: This instance's initial |_deadline|.
     32         """
     33         super(TimedEvent, self).__init__(keyword, manifest_versions,
     34                                          always_handle)
     35         self._deadline = deadline
     36 
     37 
     38     def __ne__(self, other):
     39         return self._deadline != other._deadline or self.tasks != other.tasks
     40 
     41 
     42     def __eq__(self, other):
     43         return self._deadline == other._deadline and self.tasks == other.tasks
     44 
     45 
     46     @staticmethod
     47     def _now():
     48         return datetime.datetime.now()
     49 
     50 
     51     def Prepare(self):
     52         pass
     53 
     54 
     55     def ShouldHandle(self):
     56         """Return True if self._deadline has passed; False if not."""
     57         if super(TimedEvent, self).ShouldHandle():
     58             return True
     59         else:
     60             logging.info('Checking deadline %s for event %s',
     61                          self._deadline, self.keyword)
     62             return self._now() >= self._deadline
     63 
     64 
     65     def _LatestPerBranchBuildsSince(self, board, days_ago):
     66         """Get latest per-branch, per-board builds from last |days_ago| days.
     67 
     68         @param board: the board whose builds we want.
     69         @param days_ago: how many days back to look for manifests.
     70         @return {branch: [build-name]}
     71         """
     72         since_date = self._deadline - datetime.timedelta(days=days_ago)
     73         since_date = max(since_date, datetime.datetime(2017, 1, 31, 23, 0, 0))
     74         all_branch_manifests = self._mv.ManifestsSinceDate(since_date, board)
     75         latest_branch_builds = {}
     76         for (type, milestone), manifests in all_branch_manifests.iteritems():
     77             build = base_event.BuildName(board, type, milestone, manifests[-1])
     78             latest_branch_builds[task.PickBranchName(type, milestone)] = [build]
     79         logging.info('%s event found candidate builds: %r',
     80                      self.keyword, latest_branch_builds)
     81         return latest_branch_builds
     82 
     83 
     84     def _LatestLaunchControlBuildsSince(self, board, days_ago):
     85         """Get per-branch, per-board builds since last run of this event.
     86 
     87         @param board: the board whose builds we want, e.g., android-shamu.
     88         @param days_ago: how many days back to look for manifests.
     89 
     90         @return: A list of Launch Control builds for the given board, e.g.,
     91                 ['git_mnc_release/shamu-eng/123',
     92                  'git_mnc_release/shamu-eng/124'].
     93         """
     94         # TODO(crbug.com/589936): Get the latest builds for Launch Control for a
     95         # given period, not just the last build on each branch.
     96         return self._LatestLaunchControlBuilds(board)
     97 
     98 
     99     def GetBranchBuildsForBoard(self, board):
    100         """Get per-branch, per-board builds since last run of this event.
    101         """
    102         return self._LatestPerBranchBuildsSince(board, self.DAYS_INTERVAL)
    103 
    104 
    105     def GetLaunchControlBuildsForBoard(self, board):
    106         """Get per-branch, per-board builds since last run of this event.
    107 
    108         @param board: the board whose builds we want.
    109 
    110         @return: A list of Launch Control builds for the given board, e.g.,
    111                 ['git_mnc_release/shamu-eng/123',
    112                  'git_mnc_release/shamu-eng/124'].
    113         """
    114         return self._LatestLaunchControlBuildsSince(board, self.DAYS_INTERVAL)
    115 
    116 
    117 class Nightly(TimedEvent):
    118     """A TimedEvent that allows a task to be triggered at every night. Each task
    119     can set the hour when it should be triggered, through `hour` setting.
    120 
    121     @var KEYWORD: the keyword to use in a run_on option to associate a task
    122                   with the Nightly event.
    123     @var _DEFAULT_HOUR: the default hour to trigger the nightly event.
    124     """
    125 
    126     # Nightly event is triggered once a day.
    127     DAYS_INTERVAL = 1
    128 
    129     KEYWORD = 'nightly'
    130     # Each task may have different setting of `hour`. Therefore, nightly tasks
    131     # can run at each hour. The default is set to 9PM.
    132     _DEFAULT_HOUR = 21
    133     PRIORITY = priorities.Priority.DAILY
    134     TIMEOUT = 24  # Kicked off once a day, so they get the full day to run
    135 
    136     def __init__(self, manifest_versions, always_handle):
    137         """Constructor.
    138 
    139         @param manifest_versions: ManifestVersions instance to use for querying.
    140         @param always_handle: If True, make ShouldHandle() always return True.
    141         """
    142         # Set the deadline to the next even hour.
    143         now = self._now()
    144         now_hour = datetime.datetime(now.year, now.month, now.day, now.hour)
    145         extra_hour = 0 if now == now_hour else 1
    146         deadline = now_hour + datetime.timedelta(hours=extra_hour)
    147         super(Nightly, self).__init__(self.KEYWORD, manifest_versions,
    148                                       always_handle, deadline)
    149 
    150 
    151     def UpdateCriteria(self):
    152         self._deadline = self._deadline + datetime.timedelta(hours=1)
    153 
    154 
    155     def FilterTasks(self):
    156         """Filter the tasks to only return tasks that should run now.
    157 
    158         Nightly task can run at each hour. This function only return the tasks
    159         set to run in current hour.
    160 
    161         @return: A list of tasks that can run now.
    162         """
    163         current_hour = self._now().hour
    164         return [task for task in self.tasks
    165                 if ((task.hour is not None and task.hour == current_hour) or
    166                     (task.hour is None and
    167                      current_hour == self._DEFAULT_HOUR))]
    168 
    169 
    170 class Weekly(TimedEvent):
    171     """A TimedEvent that allows a task to be triggered at every week. Each task
    172     can set the day when it should be triggered, through `day` setting.
    173 
    174     @var KEYWORD: the keyword to use in a run_on option to associate a task
    175                   with the Weekly event.
    176     @var _DEFAULT_DAY: The default day to run a weekly task.
    177     @var _DEFAULT_HOUR: can be overridden in the "weekly_params" config section.
    178     """
    179 
    180     # Weekly event is triggered once a week.
    181     DAYS_INTERVAL = 7
    182 
    183     KEYWORD = 'weekly'
    184     _DEFAULT_DAY = 5  # Saturday
    185     _DEFAULT_HOUR = 23
    186     PRIORITY = priorities.Priority.WEEKLY
    187     TIMEOUT = 7 * 24  # 7 days
    188 
    189 
    190     @classmethod
    191     def _ParseConfig(cls, config):
    192         """Create args to pass to __init__ by parsing |config|.
    193 
    194         Calls super class' _ParseConfig() method, then parses these additonal
    195         options:
    196           hour: Integer hour, on a 24 hour clock.
    197         """
    198         from_base = super(Weekly, cls)._ParseConfig(config)
    199 
    200         section = base_event.SectionName(cls.KEYWORD)
    201         event_time = config.getint(section, 'hour') or cls._DEFAULT_HOUR
    202 
    203         from_base.update({'event_time': event_time})
    204         return from_base
    205 
    206 
    207     def __init__(self, manifest_versions, always_handle, event_time):
    208         """Constructor.
    209 
    210         @param manifest_versions: ManifestVersions instance to use for querying.
    211         @param always_handle: If True, make ShouldHandle() always return True.
    212         @param event_time: The hour of the day to set |self._deadline| at.
    213         """
    214         # determine if we're past this week's event and set the
    215         # next deadline for this suite appropriately.
    216         now = self._now()
    217         this_week_deadline = datetime.datetime.combine(
    218                 now, datetime.time(event_time))
    219         if this_week_deadline >= now:
    220             deadline = this_week_deadline
    221         else:
    222             deadline = this_week_deadline + datetime.timedelta(days=1)
    223         super(Weekly, self).__init__(self.KEYWORD, manifest_versions,
    224                                      always_handle, deadline)
    225 
    226 
    227     def Merge(self, to_merge):
    228         """Merge this event with to_merge, changing some mutable properties.
    229 
    230         keyword remains unchanged; the following take on values from to_merge:
    231           _deadline iff the time of day in to_merge._deadline is different.
    232 
    233         @param to_merge: A TimedEvent instance to merge into this instance.
    234         """
    235         super(Weekly, self).Merge(to_merge)
    236         if self._deadline.time() != to_merge._deadline.time():
    237             self._deadline = to_merge._deadline
    238 
    239 
    240     def UpdateCriteria(self):
    241         self._deadline = self._deadline + datetime.timedelta(days=1)
    242 
    243 
    244     def FilterTasks(self):
    245         """Filter the tasks to only return tasks that should run now.
    246 
    247         Weekly task can be scheduled at any day of the week. This function only
    248         return the tasks set to run in current day.
    249 
    250         @return: A list of tasks that can run now.
    251         """
    252         current_day = self._now().weekday()
    253         return [task for task in self.tasks
    254                 if ((task.day is not None and task.day == current_day) or
    255                     (task.day is None and current_day == self._DEFAULT_DAY))]
    256