blob: 7651d81d06e8d510b46aec41b2b32d7dc5d90278 [file] [log] [blame]
Chris Masone2d61ca22012-04-02 16:52:46 -07001# 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
Dan Shi8d7f3562016-01-11 10:55:46 -08005import contextlib
Dan Shi15d42312015-12-15 15:37:28 -08006import logging
7import time
8from multiprocessing import pool
Chris Masone2d61ca22012-04-02 16:52:46 -07009
Dan Shi8446fac2016-03-02 22:07:39 -080010import base_event, board_enumerator, build_event, deduping_scheduler
Aviv Keshet4e7722b2013-02-14 15:07:46 -080011import task, timed_event
Chris Masone2d61ca22012-04-02 16:52:46 -070012
Aviv Keshet4e7722b2013-02-14 15:07:46 -080013import common
Dan Shi5e2efb72017-02-07 11:40:23 -080014from autotest_lib.client.common_lib import utils
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080015from autotest_lib.server import utils
Chris Masone2d61ca22012-04-02 16:52:46 -070016
Dan Shi5e2efb72017-02-07 11:40:23 -080017try:
18 from chromite.lib import metrics
19except ImportError:
20 metrics = utils.metrics_mock
21
Dan Shi098d1e22015-09-02 10:00:24 -070022
Dan Shic3fc5992016-12-08 11:12:30 -080023POOL_SIZE = 32
Dan Shi098d1e22015-09-02 10:00:24 -070024
Chris Masone2d61ca22012-04-02 16:52:46 -070025class Driver(object):
26 """Implements the main loop of the suite_scheduler.
27
Chris Masonebf8775a2012-09-10 10:44:18 -070028 @var EVENT_CLASSES: list of the event classes Driver supports.
Chris Masonefe5a5092012-04-11 18:29:07 -070029 @var _LOOP_INTERVAL_SECONDS: seconds to wait between loop iterations.
Chris Masone2d61ca22012-04-02 16:52:46 -070030
31 @var _scheduler: a DedupingScheduler, used to schedule jobs with the AFE.
Chris Masone3fba86f2012-04-03 10:06:56 -070032 @var _enumerator: a BoardEnumerator, used to list plaforms known to
Chris Masone2d61ca22012-04-02 16:52:46 -070033 the AFE
Chris Masone855d86f2012-05-07 13:48:07 -070034 @var _events: dict of BaseEvents to be handled each time through main loop.
Chris Masone2d61ca22012-04-02 16:52:46 -070035 """
36
Chris Masonebf8775a2012-09-10 10:44:18 -070037 EVENT_CLASSES = [timed_event.Nightly, timed_event.Weekly,
38 build_event.NewBuild]
Chris Masonefe5a5092012-04-11 18:29:07 -070039 _LOOP_INTERVAL_SECONDS = 5 * 60
Chris Masone2d61ca22012-04-02 16:52:46 -070040
Dan Shifa705d22016-07-28 16:16:15 -070041 # Cache for known ChromeOS boards. The cache helps to avoid unnecessary
42 # repeated calls to Launch Control API.
43 _cros_boards = set()
Chris Masone2d61ca22012-04-02 16:52:46 -070044
Dan Shi23245142015-01-22 13:22:28 -080045 def __init__(self, scheduler, enumerator, is_sanity=False):
Chris Masone2d61ca22012-04-02 16:52:46 -070046 """Constructor
47
Chris Masone67f06d62012-04-12 15:16:56 -070048 @param scheduler: an instance of deduping_scheduler.DedupingScheduler.
49 @param enumerator: an instance of board_enumerator.BoardEnumerator.
Dan Shi23245142015-01-22 13:22:28 -080050 @param is_sanity: Set to True if the driver is created for sanity check.
51 Default is set to False.
Chris Masone2d61ca22012-04-02 16:52:46 -070052 """
Chris Masone67f06d62012-04-12 15:16:56 -070053 self._scheduler = scheduler
54 self._enumerator = enumerator
Dan Shi23245142015-01-22 13:22:28 -080055 task.TotMilestoneManager.is_sanity = is_sanity
Chris Masone2d61ca22012-04-02 16:52:46 -070056
Chris Masone2d61ca22012-04-02 16:52:46 -070057
Chris Masone855d86f2012-05-07 13:48:07 -070058 def RereadAndReprocessConfig(self, config, mv):
59 """Re-read config, re-populate self._events and recreate task lists.
60
61 @param config: an instance of ForgivingConfigParser.
62 @param mv: an instance of ManifestVersions.
63 """
64 config.reread()
65 new_events = self._CreateEventsWithTasks(config, mv)
66 for keyword, event in self._events.iteritems():
67 event.Merge(new_events[keyword])
68
69
Chris Masone93f51d42012-04-18 08:46:52 -070070 def SetUpEventsAndTasks(self, config, mv):
Chris Masone67f06d62012-04-12 15:16:56 -070071 """Populate self._events and create task lists from config.
72
Chris Masone96f16632012-04-04 18:36:03 -070073 @param config: an instance of ForgivingConfigParser.
Chris Masone93f51d42012-04-18 08:46:52 -070074 @param mv: an instance of ManifestVersions.
Chris Masone96f16632012-04-04 18:36:03 -070075 """
Chris Masone855d86f2012-05-07 13:48:07 -070076 self._events = self._CreateEventsWithTasks(config, mv)
77
78
79 def _CreateEventsWithTasks(self, config, mv):
80 """Create task lists from config, and assign to newly-minted events.
81
82 Calling multiple times should start afresh each time.
83
84 @param config: an instance of ForgivingConfigParser.
85 @param mv: an instance of ManifestVersions.
86 """
Chris Masone855d86f2012-05-07 13:48:07 -070087 events = {}
Chris Masonebf8775a2012-09-10 10:44:18 -070088 for klass in self.EVENT_CLASSES:
Chris Masone855d86f2012-05-07 13:48:07 -070089 events[klass.KEYWORD] = klass.CreateFromConfig(config, mv)
Chris Masone96f16632012-04-04 18:36:03 -070090
91 tasks = self.TasksFromConfig(config)
Chris Masone645c7e42012-05-17 17:28:40 -070092 for keyword, task_list in tasks.iteritems():
93 if keyword in events:
94 events[keyword].tasks = task_list
95 else:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -070096 logging.warning('%s, is an unknown keyword.', keyword)
Chris Masone855d86f2012-05-07 13:48:07 -070097 return events
Chris Masone96f16632012-04-04 18:36:03 -070098
99
100 def TasksFromConfig(self, config):
101 """Generate a dict of {event_keyword: [tasks]} mappings from |config|.
102
103 For each section in |config| that encodes a Task, instantiate a Task
104 object. Determine the event that Task is supposed to run_on and
105 append the object to a list associated with the appropriate event
106 keyword. Return a dictionary of these keyword: list of task mappings.
107
108 @param config: a ForgivingConfigParser containing tasks to be parsed.
109 @return dict of {event_keyword: [tasks]} mappings.
110 @raise MalformedConfigEntry on a task parsing error.
111 """
112 tasks = {}
113 for section in config.sections():
Chris Masone93f51d42012-04-18 08:46:52 -0700114 if not base_event.HonoredSection(section):
Chris Masone96f16632012-04-04 18:36:03 -0700115 try:
116 keyword, new_task = task.Task.CreateFromConfigSection(
Dan Shi84585362016-10-03 14:11:43 -0700117 config, section)
Chris Masone96f16632012-04-04 18:36:03 -0700118 except task.MalformedConfigEntry as e:
Dan Shi84585362016-10-03 14:11:43 -0700119 logging.warning('%s is malformed: %s', section, str(e))
Chris Masone96f16632012-04-04 18:36:03 -0700120 continue
121 tasks.setdefault(keyword, []).append(new_task)
122 return tasks
Chris Masone2d61ca22012-04-02 16:52:46 -0700123
124
Chris Masone855d86f2012-05-07 13:48:07 -0700125 def RunForever(self, config, mv):
Chris Masone67f06d62012-04-12 15:16:56 -0700126 """Main loop of the scheduler. Runs til the process is killed.
127
Chris Masone855d86f2012-05-07 13:48:07 -0700128 @param config: an instance of ForgivingConfigParser.
Chris Masone67f06d62012-04-12 15:16:56 -0700129 @param mv: an instance of manifest_versions.ManifestVersions.
130 """
Chris Masone855d86f2012-05-07 13:48:07 -0700131 for event in self._events.itervalues():
Chris Masone73a78382012-04-20 13:25:51 -0700132 event.Prepare()
Chris Masone2d61ca22012-04-02 16:52:46 -0700133 while True:
Chris Masone645c7e42012-05-17 17:28:40 -0700134 try:
135 self.HandleEventsOnce(mv)
Scott Zawalskic15c6b42012-07-09 13:16:05 -0400136 except board_enumerator.EnumeratorException as e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700137 logging.warning('Failed to enumerate boards: %r', e)
Dan Shic3fc5992016-12-08 11:12:30 -0800138 mv.Update()
139 task.TotMilestoneManager().refresh()
Chris Masonefe5a5092012-04-11 18:29:07 -0700140 time.sleep(self._LOOP_INTERVAL_SECONDS)
Chris Masone855d86f2012-05-07 13:48:07 -0700141 self.RereadAndReprocessConfig(config, mv)
Chris Masone2d61ca22012-04-02 16:52:46 -0700142
143
Dan Shi15d42312015-12-15 15:37:28 -0800144 @staticmethod
145 def HandleBoard(inputs):
146 """Handle event based on given inputs.
147
148 @param inputs: A dictionary of the arguments needed to handle an event.
149 Keys include:
150 scheduler: a DedupingScheduler, used to schedule jobs with the AFE.
151 event: An event object to be handled.
152 board: Name of the board.
153 """
154 scheduler = inputs['scheduler']
155 event = inputs['event']
156 board = inputs['board']
157
Dan Shifa705d22016-07-28 16:16:15 -0700158 # Try to get builds from LaunchControl first. If failed, the board could
159 # be ChromeOS. Use the cache Driver._cros_boards to avoid unnecessary
160 # repeated call to LaunchControl API.
161 launch_control_builds = None
162 if board not in Driver._cros_boards:
Dan Shi2121a332016-02-25 14:22:22 -0800163 launch_control_builds = event.GetLaunchControlBuildsForBoard(board)
Dan Shifa705d22016-07-28 16:16:15 -0700164 if launch_control_builds:
Dan Shi2121a332016-02-25 14:22:22 -0800165 event.Handle(scheduler, branch_builds=None, board=board,
166 launch_control_builds=launch_control_builds)
167 else:
168 branch_builds = event.GetBranchBuildsForBoard(board)
Dan Shifa705d22016-07-28 16:16:15 -0700169 if branch_builds:
170 Driver._cros_boards.add(board)
171 logging.info('Found ChromeOS build for board %s. This should '
172 'be a ChromeOS board.', board)
Dan Shi2121a332016-02-25 14:22:22 -0800173 event.Handle(scheduler, branch_builds, board)
Dan Shi15d42312015-12-15 15:37:28 -0800174 logging.info('Finished handling %s event for board %s', event.keyword,
175 board)
176
Dan Shic3fc5992016-12-08 11:12:30 -0800177 @metrics.SecondsTimerDecorator('chromeos/autotest/suite_scheduler/'
178 'handle_events_once_duration')
Chris Masone67f06d62012-04-12 15:16:56 -0700179 def HandleEventsOnce(self, mv):
180 """One turn through the loop. Separated out for unit testing.
181
182 @param mv: an instance of manifest_versions.ManifestVersions.
Chris Masone645c7e42012-05-17 17:28:40 -0700183 @raise EnumeratorException if we can't enumerate any supported boards.
Chris Masone67f06d62012-04-12 15:16:56 -0700184 """
Chris Masone92874d32012-04-03 10:13:04 -0700185 boards = self._enumerator.Enumerate()
Dan Shi15d42312015-12-15 15:37:28 -0800186 logging.info('%d boards currently in the lab: %r', len(boards), boards)
187 thread_pool = pool.ThreadPool(POOL_SIZE)
Dan Shi8d7f3562016-01-11 10:55:46 -0800188 with contextlib.closing(thread_pool):
189 for e in self._events.itervalues():
190 if not e.ShouldHandle():
191 continue
Dan Shi8446fac2016-03-02 22:07:39 -0800192 # Reset the value of delay_minutes, as this is the beginning of
193 # handling an event for all boards.
194 self._scheduler.delay_minutes = 0
195 self._scheduler.delay_minutes_interval = (
196 deduping_scheduler.DELAY_MINUTES_INTERVAL)
Dan Shi15d42312015-12-15 15:37:28 -0800197 logging.info('Handling %s event for %d boards', e.keyword,
198 len(boards))
199 args = []
Chris Masone96f16632012-04-04 18:36:03 -0700200 for board in boards:
Dan Shi15d42312015-12-15 15:37:28 -0800201 args.append({'scheduler': self._scheduler,
202 'event': e,
203 'board': board})
204 thread_pool.map(self.HandleBoard, args)
205 logging.info('Finished handling %s event for %d boards',
206 e.keyword, len(boards))
Chris Masonebbde3862012-05-07 14:29:51 -0700207 e.UpdateCriteria()
Chris Masone67f06d62012-04-12 15:16:56 -0700208
209
Dan Shi2121a332016-02-25 14:22:22 -0800210 def ForceEventsOnceForBuild(self, keywords, build_name,
211 os_type=task.OS_TYPE_CROS):
Chris Masone67f06d62012-04-12 15:16:56 -0700212 """Force events with provided keywords to happen, with given build.
213
214 @param keywords: iterable of event keywords to force
215 @param build_name: instead of looking up builds to test, test this one.
Dan Shi2121a332016-02-25 14:22:22 -0800216 @param os_type: Type of the OS to test, default to cros.
Chris Masone67f06d62012-04-12 15:16:56 -0700217 """
Dan Shi2121a332016-02-25 14:22:22 -0800218 branch_builds = None
219 launch_control_builds = None
220 if os_type == task.OS_TYPE_CROS:
221 board, type, milestone, manifest = utils.ParseBuildName(build_name)
222 branch_builds = {task.PickBranchName(type, milestone): [build_name]}
223 logging.info('Testing build R%s-%s on %s', milestone, manifest,
224 board)
225 else:
226 logging.info('Build is not a ChromeOS build, try to parse as a '
227 'Launch Control build.')
Dan Shi6450e142016-03-11 11:52:20 -0800228 _,target,_ = utils.parse_launch_control_build(build_name)
Dan Shi8db7e612016-07-21 12:55:16 -0700229 board = utils.parse_launch_control_target(target)[0]
Dan Shi43274402016-11-04 15:13:43 -0700230 # Translate board name in build target to the actual board name.
231 board = utils.ANDROID_TARGET_TO_BOARD_MAP.get(board, board)
Dan Shi2121a332016-02-25 14:22:22 -0800232 launch_control_builds = [build_name]
233 logging.info('Testing Launch Control build %s on %s', build_name,
234 board)
Chris Masone67f06d62012-04-12 15:16:56 -0700235
Chris Masone855d86f2012-05-07 13:48:07 -0700236 for e in self._events.itervalues():
Chris Masone67f06d62012-04-12 15:16:56 -0700237 if e.keyword in keywords:
Dan Shi2121a332016-02-25 14:22:22 -0800238 e.Handle(self._scheduler, branch_builds, board, force=True,
239 launch_control_builds=launch_control_builds)