blob: 4a00d94659edeef745a2837616bc5cc660247bd4 [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 Lin3330d672020-03-03 14:52:36 -080092 # delay_minutes ensures that most of the target builds were inserted to BQ.
93 delay_minutes = datetime.timedelta(minutes=constants.BaseEvent.DELAY_MINUTES)
Xinan Lin71eeeb02020-03-10 17:37:12 -070094 return build_lib.get_cros_builds(build_client,
95 lab_config.get_cros_board_list(),
96 self.since_date - delay_minutes,
97 time_converter.utc_now() - delay_minutes,
98 self.keyword)
Xixuan Wu303a5192017-08-29 11:10:42 -070099
100 def get_launch_control_builds(self, lab_config, android_client):
101 """Get launch control builds to run on for this event.
102
103 Args:
104 lab_config: a config_reader.LabConfig object, to read lab configs.
105 android_client: a rest_client.AndroidBuildRestClient object to call
106 android build API.
107
108 Returns:
109 a dict containing launch control builds for Android boards. See return
110 value of |build_lib.get_launch_control_builds_by_branch_targets|.
111 """
112 return build_lib.get_launch_control_builds_by_branch_targets(
113 android_client, lab_config.get_android_board_list(),
114 self.launch_control_branch_targets)
115
Xinan Lin028f9582019-12-11 10:55:33 -0800116 def get_firmware_builds(self, build_client):
117 """Get the latest firmware builds for all boards.
118
119 Args:
120 build_client: a rest_client.BuildBucketBigqueryClient object, to
121 connect Buildbucket Bigquery.
122
123 Returns:
124 A dict of artifact link for the firmware. The key is
125 ([cros|firmware], board).
126 """
127 firmware_builds = build_client.get_latest_passed_firmware_builds()
128 if not firmware_builds:
129 return None
130 firmware_build_dict = {}
131 ARTIFACT_PATTERN = r'gs://chromeos-image-archive/(?P<firmware_build>.+)'
132 for spec, board, artifact in firmware_builds:
133 match = re.match(ARTIFACT_PATTERN, artifact)
134 if not match:
135 logging.debug('Artifact path of firmware is not valid: %s, %s, %s',
136 spec, board, artifact)
137 continue
138 firmware_build = match.group('firmware_build')
139 logging.debug('latest firmware build of (%s, %s): %s',
140 spec, board, firmware_build)
141 firmware_build_dict[(spec, board)] = firmware_build
142 return firmware_build_dict
143
Craig Bergstrom58263d32018-04-26 14:11:35 -0600144 def process_tasks(self, launch_control_builds, cros_builds_tuple,
Xinan Lin028f9582019-12-11 10:55:33 -0800145 firmware_builds, configs):
Xixuan Wu303a5192017-08-29 11:10:42 -0700146 """Schedule tasks in task_list.
147
148 Args:
149 launch_control_builds: the build dict for Android boards, see
150 return value of |get_launch_control_builds|.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600151 cros_builds_tuple: a two-tuple of build dicts for ChromeOS boards,
152 see return value of |get_cros_builds|.
Xinan Lin028f9582019-12-11 10:55:33 -0800153 firmware_builds: a dict of firmware artifact, see return value of
154 |get_firmware_builds|.
Xixuan Wuf4a4c882019-03-15 14:48:26 -0700155 configs: a config_reader.Configs object, to contain several config
156 readers.
Xixuan Wu303a5192017-08-29 11:10:42 -0700157
158 Returns:
159 A list of finished tasks' names.
160 """
Xixuan Wu5451a662017-10-17 10:57:40 -0700161 scheduled_tasks = []
Xixuan Wu303a5192017-08-29 11:10:42 -0700162 for per_task in self.task_list:
163 try:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600164 if per_task.schedule(launch_control_builds, cros_builds_tuple,
Xinan Lin028f9582019-12-11 10:55:33 -0800165 firmware_builds, configs):
Xixuan Wu5451a662017-10-17 10:57:40 -0700166 scheduled_tasks.append(per_task.name)
167 else:
168 logging.debug('No suites are scheduled in suites queue for '
169 'task %s', per_task.name)
Xixuan Wu303a5192017-08-29 11:10:42 -0700170 except task.SchedulingError:
171 logging.exception('Failed to schedule task: %s', per_task.name)
172 continue
173
Xixuan Wu5451a662017-10-17 10:57:40 -0700174 return scheduled_tasks
Xixuan Wu303a5192017-08-29 11:10:42 -0700175
176 def finish(self):
177 """Execute all actions that once an event is finished."""
178 self.datastore_client.set_last_execute_time(
179 self.keyword, self.target_exec_utc)
180
181 def _update_with_settings(self, event_settings):
182 """Update event with given settings from config file.
183
184 Args:
185 event_settings: a config_reader.EventSettings object, indicating
186 the event settings.
187 """
188 self.always_handle = event_settings.always_handle is not None
189 if self.always_handle:
190 self.always_handle = event_settings.always_handle
191
192 def _set_last_exec(self, last_exec_utc):
193 """Process and set last execute time.
194
195 Set last_exec_utc. If last_exec_utc is too old or None, set it as
196 target_exec_utc.
197
198 Args:
199 last_exec_utc: the utc datetime.datetime timestamp of last execute
200 time. None means no last_exec_utc saved for this event in
201 datastore.
202 """
203 if last_exec_utc is not None:
204 self.last_exec_utc = last_exec_utc
205
206 # If this TimedEvent has expired for a period of time, run its
207 # tasks on next available timepoint.
208 if (self.target_exec_utc - self.last_exec_utc > datetime.timedelta(
209 hours=self.LAST_EXEC_INTERVAL)):
210 self.last_exec_utc = self.target_exec_utc
211 else:
212 self.last_exec_utc = self.target_exec_utc
213
214 def _set_should_handle(self):
215 """Update it's the time for the event to be handled."""
Xixuan Wua5a29442017-10-11 11:03:02 -0700216 if self.always_handle or global_config.GAE_TESTING:
Xixuan Wu303a5192017-08-29 11:10:42 -0700217 self.should_handle = True
218 else:
219 if self.last_exec_utc and self.last_exec_utc == self.target_exec_utc:
220 self.should_handle = False
221 else:
222 self.should_handle = True
223
224 @classmethod
225 def section_name(cls):
226 """Getter for the section name of the event.
227
228 Returns:
229 A string representing section name, refers to a section in config file
230 that contains this event's params.
231 """
232 return cls.KEYWORD + _SECTION_SUFFIX
233
234 @property
235 def datastore_client(self):
236 """Getter for private |self._datastore_client| property."""
237 if self._datastore_client is None:
238 self._datastore_client = (
239 datastore_client.LastExecutionRecordStore())
240
241 return self._datastore_client
242
243 @property
244 def keyword(self):
245 """Getter for private |self.KEYWORD| property."""
246 return self.KEYWORD
247
248 @property
249 def launch_control_branch_targets(self):
250 """Get a dict of branch:targets for launch controls from all tasks."""
251 branches = {}
252 for per_task in self.task_list:
253 for branch in per_task.launch_control_branches:
254 branches.setdefault(branch, []).extend(
255 per_task.launch_control_targets)
256
257 return branches