blob: 425b16ddb47cde6f38a8ce393dc5d7465c132de8 [file] [log] [blame]
Xixuan Wu303a5192017-08-29 11:10:42 -07001# Copyright 2017 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"""Module of a basic event."""
6
7import datetime
8import logging
Xinan Lin028f9582019-12-11 10:55:33 -08009import re
Xixuan Wu303a5192017-08-29 11:10:42 -070010
11import build_lib
12import constants
13import datastore_client
Xixuan Wua5a29442017-10-11 11:03:02 -070014import global_config
Xixuan Wu303a5192017-08-29 11:10:42 -070015import task
Xinan Linea1efcb2019-12-30 23:46:42 -080016import time_converter
Xixuan Wu303a5192017-08-29 11:10:42 -070017
18# The suffix for section
19_SECTION_SUFFIX = '_params'
20
21
22class BaseEvent(object):
23 """Basic class for a suite scheduler event."""
24
25 # The default keyword and priority for base event. Will be overwritten by
26 # its subclass.
27 KEYWORD = 'base'
28 PRIORITY = constants.Priorities.DEFAULT
29
30 # The interval hours between consequent rounds of events. Default is 6.
31 LAST_EXEC_INTERVAL = 6
32
33 # The number of days between each event to trigger. Default is 1.
34 DAYS_INTERVAL = 1
35
36 # The max lifetime of suites kicked off by this event.
37 TIMEOUT = 24 # Hours
38
39 def __init__(self, event_settings, last_exec_utc, target_exec_utc):
40 """Initialize a base event.
41
42 Args:
43 event_settings: a config_reader.EventSettings object, indicating
44 the event settings.
45 last_exec_utc: The utc datetime.datetime timestamp of the last
46 execution of the event.
47 target_exec_utc: The utc datetime.datetime timestamp of the next
48 execution of the event.
49 """
50 self._update_with_settings(event_settings)
51 self._datastore_client = None
52 self.task_list = []
53
54 # Processing executing time.
55 self.target_exec_utc = target_exec_utc
56 self._set_last_exec(last_exec_utc)
57 self._set_should_handle()
58
59 logging.info('%s Event created:\ntarget_exec_time (utc): %s\n'
60 'last_exec_time (utc): %s\nshould_handle: %s',
61 self.keyword, self.target_exec_utc, self.last_exec_utc,
62 self.should_handle)
63
64 def set_task_list(self, task_list):
65 """Update task list with given input.
66
67 Args:
68 task_list: a new task list for this event.
69 """
70 self.task_list = list(task_list)
71
72 def filter_tasks(self):
73 """Filter tasks from original task list.
74
75 This could be overwritten by subclass of BaseEvent, like Nightly event, or
76 be directly used by event types like NewBuild.
77 """
78 self.task_list = list(self.task_list)
79
Xixuan Wuc6819012019-05-23 11:34:59 -070080 def get_cros_builds(self, lab_config, build_client):
Xixuan Wu303a5192017-08-29 11:10:42 -070081 """Get CrOS builds to run on for this event.
82
83 Args:
Xixuan Wu6fb16272017-10-19 13:16:00 -070084 lab_config: a config_reader.LabConfig object, to read lab configs.
Xixuan Wuc6819012019-05-23 11:34:59 -070085 build_client: a rest_client.BuildBucketBigqueryClient object, to
86 connect Buildbucket Bigquery.
Xixuan Wu303a5192017-08-29 11:10:42 -070087
88 Returns:
Craig Bergstrom58263d32018-04-26 14:11:35 -060089 A two-tuples of dicts containing cros builds, see return from
Xinan Linea1efcb2019-12-30 23:46:42 -080090 |build_lib.get_cros_builds|.
Xixuan Wu303a5192017-08-29 11:10:42 -070091 """
Xinan Linea1efcb2019-12-30 23:46:42 -080092 # delay_hours ensures suite scheduler fetch the builds which have inserted
93 # to BigQuery.
94 delay_hours = datetime.timedelta(hours=constants.BaseEvent.DELAY_HOURS)
95 return build_lib.get_cros_builds(
96 build_client,
97 lab_config.get_cros_board_list(),
98 self.since_date - delay_hours,
99 time_converter.utc_now() - delay_hours)
Xixuan Wu303a5192017-08-29 11:10:42 -0700100
101 def get_launch_control_builds(self, lab_config, android_client):
102 """Get launch control builds to run on for this event.
103
104 Args:
105 lab_config: a config_reader.LabConfig object, to read lab configs.
106 android_client: a rest_client.AndroidBuildRestClient object to call
107 android build API.
108
109 Returns:
110 a dict containing launch control builds for Android boards. See return
111 value of |build_lib.get_launch_control_builds_by_branch_targets|.
112 """
113 return build_lib.get_launch_control_builds_by_branch_targets(
114 android_client, lab_config.get_android_board_list(),
115 self.launch_control_branch_targets)
116
Xinan Lin028f9582019-12-11 10:55:33 -0800117 def get_firmware_builds(self, build_client):
118 """Get the latest firmware builds for all boards.
119
120 Args:
121 build_client: a rest_client.BuildBucketBigqueryClient object, to
122 connect Buildbucket Bigquery.
123
124 Returns:
125 A dict of artifact link for the firmware. The key is
126 ([cros|firmware], board).
127 """
128 firmware_builds = build_client.get_latest_passed_firmware_builds()
129 if not firmware_builds:
130 return None
131 firmware_build_dict = {}
132 ARTIFACT_PATTERN = r'gs://chromeos-image-archive/(?P<firmware_build>.+)'
133 for spec, board, artifact in firmware_builds:
134 match = re.match(ARTIFACT_PATTERN, artifact)
135 if not match:
136 logging.debug('Artifact path of firmware is not valid: %s, %s, %s',
137 spec, board, artifact)
138 continue
139 firmware_build = match.group('firmware_build')
140 logging.debug('latest firmware build of (%s, %s): %s',
141 spec, board, firmware_build)
142 firmware_build_dict[(spec, board)] = firmware_build
143 return firmware_build_dict
144
Craig Bergstrom58263d32018-04-26 14:11:35 -0600145 def process_tasks(self, launch_control_builds, cros_builds_tuple,
Xinan Lin028f9582019-12-11 10:55:33 -0800146 firmware_builds, configs):
Xixuan Wu303a5192017-08-29 11:10:42 -0700147 """Schedule tasks in task_list.
148
149 Args:
150 launch_control_builds: the build dict for Android boards, see
151 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600152 cros_builds_tuple: a two-tuple of build dicts for ChromeOS boards,
153 see return value of |get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800154 firmware_builds: a dict of firmware artifact, see return value of
155 |get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700156 configs: a config_reader.Configs object, to contain several config
157 readers.
Xixuan Wu303a5192017-08-29 11:10:42 -0700158
159 Returns:
160 A list of finished tasks' names.
161 """
Xixuan Wu5451a662017-10-17 10:57:40 -0700162 scheduled_tasks = []
Xixuan Wu303a5192017-08-29 11:10:42 -0700163 for per_task in self.task_list:
164 try:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600165 if per_task.schedule(launch_control_builds, cros_builds_tuple,
Xinan Lin028f9582019-12-11 10:55:33 -0800166 firmware_builds, configs):
Xixuan Wu5451a662017-10-17 10:57:40 -0700167 scheduled_tasks.append(per_task.name)
168 else:
169 logging.debug('No suites are scheduled in suites queue for '
170 'task %s', per_task.name)
Xixuan Wu303a5192017-08-29 11:10:42 -0700171 except task.SchedulingError:
172 logging.exception('Failed to schedule task: %s', per_task.name)
173 continue
174
Xixuan Wu5451a662017-10-17 10:57:40 -0700175 return scheduled_tasks
Xixuan Wu303a5192017-08-29 11:10:42 -0700176
177 def finish(self):
178 """Execute all actions that once an event is finished."""
179 self.datastore_client.set_last_execute_time(
180 self.keyword, self.target_exec_utc)
181
182 def _update_with_settings(self, event_settings):
183 """Update event with given settings from config file.
184
185 Args:
186 event_settings: a config_reader.EventSettings object, indicating
187 the event settings.
188 """
189 self.always_handle = event_settings.always_handle is not None
190 if self.always_handle:
191 self.always_handle = event_settings.always_handle
192
193 def _set_last_exec(self, last_exec_utc):
194 """Process and set last execute time.
195
196 Set last_exec_utc. If last_exec_utc is too old or None, set it as
197 target_exec_utc.
198
199 Args:
200 last_exec_utc: the utc datetime.datetime timestamp of last execute
201 time. None means no last_exec_utc saved for this event in
202 datastore.
203 """
204 if last_exec_utc is not None:
205 self.last_exec_utc = last_exec_utc
206
207 # If this TimedEvent has expired for a period of time, run its
208 # tasks on next available timepoint.
209 if (self.target_exec_utc - self.last_exec_utc > datetime.timedelta(
210 hours=self.LAST_EXEC_INTERVAL)):
211 self.last_exec_utc = self.target_exec_utc
212 else:
213 self.last_exec_utc = self.target_exec_utc
214
215 def _set_should_handle(self):
216 """Update it's the time for the event to be handled."""
Xixuan Wua5a29442017-10-11 11:03:02 -0700217 if self.always_handle or global_config.GAE_TESTING:
Xixuan Wu303a5192017-08-29 11:10:42 -0700218 self.should_handle = True
219 else:
220 if self.last_exec_utc and self.last_exec_utc == self.target_exec_utc:
221 self.should_handle = False
222 else:
223 self.should_handle = True
224
225 @classmethod
226 def section_name(cls):
227 """Getter for the section name of the event.
228
229 Returns:
230 A string representing section name, refers to a section in config file
231 that contains this event's params.
232 """
233 return cls.KEYWORD + _SECTION_SUFFIX
234
235 @property
236 def datastore_client(self):
237 """Getter for private |self._datastore_client| property."""
238 if self._datastore_client is None:
239 self._datastore_client = (
240 datastore_client.LastExecutionRecordStore())
241
242 return self._datastore_client
243
244 @property
245 def keyword(self):
246 """Getter for private |self.KEYWORD| property."""
247 return self.KEYWORD
248
249 @property
250 def launch_control_branch_targets(self):
251 """Get a dict of branch:targets for launch controls from all tasks."""
252 branches = {}
253 for per_task in self.task_list:
254 for branch in per_task.launch_control_branches:
255 branches.setdefault(branch, []).extend(
256 per_task.launch_control_targets)
257
258 return branches