blob: 28234ebfba605aa453793d0f042bc88d9e415f4b [file] [log] [blame]
xixuanbea010f2017-03-27 10:10:19 -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 for ChromeOS & Android build related logic in suite scheduler."""
Xixuan Wu5d6063e2017-09-05 16:15:07 -07006# pylint: disable=g-bad-import-order
xixuanbea010f2017-03-27 10:10:19 -07007
Xixuan Wu5d6063e2017-09-05 16:15:07 -07008from distutils import version
xixuanbea010f2017-03-27 10:10:19 -07009import collections
10import logging
11import re
12
Xixuan Wu5d6063e2017-09-05 16:15:07 -070013import apiclient
14
xixuanbea010f2017-03-27 10:10:19 -070015# Bare branches
16BARE_BRANCHES = ['factory', 'firmware']
17
18# Definition of os types.
19OS_TYPE_CROS = 'cros'
20OS_TYPE_BRILLO = 'brillo'
21OS_TYPE_ANDROID = 'android'
22OS_TYPES = [OS_TYPE_CROS, OS_TYPE_BRILLO, OS_TYPE_ANDROID]
23OS_TYPES_LAUNCH_CONTROL = [OS_TYPE_BRILLO, OS_TYPE_ANDROID]
24
Xixuan Wu5d6063e2017-09-05 16:15:07 -070025# Launch control build's target's information
xixuanbea010f2017-03-27 10:10:19 -070026LaunchControlBuildTargetInfo = collections.namedtuple(
27 'LaunchControlBuildTargetInfo',
28 [
Xixuan Wu5d6063e2017-09-05 16:15:07 -070029 'target',
30 'type',
xixuanbea010f2017-03-27 10:10:19 -070031 ])
32
Xixuan Wu5d6063e2017-09-05 16:15:07 -070033# ChromeOS build config's information
xixuanbea010f2017-03-27 10:10:19 -070034CrOSBuildConfigInfo = collections.namedtuple(
35 'CrOSBuildConfigInfo',
36 [
Xixuan Wu5d6063e2017-09-05 16:15:07 -070037 'board',
38 'type',
xixuanbea010f2017-03-27 10:10:19 -070039 ])
40
xixuanbea010f2017-03-27 10:10:19 -070041# The default build type for fetching latest build.
42_DEFAULT_BUILD_SUFFIX = '-paladin'
43
44# The default setting of board for fetching latest build.
45_DEFAULT_MASTER = 'master'
46
47# The path for storing the latest build.
48_GS_LATEST_MASTER_PATTERN = '%(board)s%(suffix)s/%(name)s'
49
50# The gs bucket to fetch the latest build.
51_GS_BUCKET = 'chromeos-image-archive'
52
53# The file in Google Storage to fetch the latest build.
54_LATEST_MASTER = 'LATEST-master'
55
56# Special android target to board map.
57_ANDROID_TARGET_TO_BOARD_MAP = {
58 'seed_l8150': 'gm4g_sprout',
59 'bat_land': 'bat'
60}
61
62# CrOS build name patter
63_CROS_BUILD_PATTERN = '%(board)s-%(build_type)s/R%(milestone)s-%(manifest)s'
64
65# Android build name pattern
66_ANDROID_BUILD_PATTERN = '%(branch)s/%(target)s/%(build_id)s'
67
68# The pattern for Launch Control target
69_LAUNCH_CONTROL_TARGET_PATTERN = r'(?P<build_target>.+)-(?P<build_type>[^-]+)'
70
71# The pattern for CrOS build config
72_CROS_BUILD_CONFIG_PATTERN = r'-([^-]+)(?:-group)?'
Xixuan Wu5d6063e2017-09-05 16:15:07 -070073
74
75class NoBuildError(Exception):
76 """Raised when failing to get the required build from Google Storage."""
77
78
79class BuildType(object):
80 """Representing the type of test source build.
81
82 This is used to identify the test source build for testing.
83 """
84 FIRMWARE_RW = 'firmware_rw'
85 FIRMWARE_RO = 'firmware_ro'
86 CROS = 'cros'
87
88
89class BuildVersionKey(object):
90 """Keys referring to the builds to install in run_suites."""
91
92 CROS_VERSION = 'cros_version'
93 ANDROID_BUILD_VERSION = 'android_version'
94 TESTBED_BUILD_VERSION = 'testbed_version'
95 FW_RW_VERSION = 'fwrw_version'
96 FW_RO_VERSION = 'fwro_version'
97
98
99class AndroidBuild(collections.namedtuple(
100 '_AndroidBuildBase', ['branch', 'target', 'build_id']), object):
101 """Class for constructing android build string."""
102
103 def __str__(self):
104 return _ANDROID_BUILD_PATTERN % {'branch': self.branch,
105 'target': self.target,
106 'build_id': self.build_id}
107
108
109class CrOSBuild(collections.namedtuple(
110 '_CrOSBuildBase',
111 ['board', 'build_type', 'milestone', 'manifest']), object):
112 """Class for constructing ChromeOS build string."""
113
114 def __str__(self):
115 return _CROS_BUILD_PATTERN % {'board': self.board,
116 'build_type': self.build_type,
117 'milestone': self.milestone,
118 'manifest': self.manifest}
119
120
121def get_latest_cros_build_from_gs(storage_client, board=None, suffix=None):
122 """Get latest build for given board from Google Storage.
123
124 Args:
125 storage_client: a rest_client.StorageRestClient object.
126 board: the board to fetch latest build. Default is 'master'.
127 suffix: suffix represents build channel, like '-release'.
128 Default is '-paladin'.
129
130 Returns:
131 a ChromeOS version string, e.g. '59.0.000.0'.
132
133 Raises:
134 HttpError if error happens in interacting with Google Storage.
135 """
136 board = board if board is not None else _DEFAULT_MASTER
137 suffix = suffix if suffix is not None else _DEFAULT_BUILD_SUFFIX
138 file_to_check = _GS_LATEST_MASTER_PATTERN % {
139 'board': board,
140 'suffix': suffix,
141 'name': _LATEST_MASTER}
142
143 try:
144 return storage_client.read_object(_GS_BUCKET, file_to_check)
145 except apiclient.errors.HttpError as e:
146 raise NoBuildError(
147 'Cannot find latest build for board %s, suffix %s: %s' %
148 (board, suffix, str(e)))
149
150
Craig Bergstrom58263d32018-04-26 14:11:35 -0600151def buildinfo_list_to_branch_build_dict(cros_board_list, buildinfo_list):
152 """Validate and convert a list of BuildInfo to branch build dict.
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700153
154 Args:
Xixuan Wu6fb16272017-10-19 13:16:00 -0700155 cros_board_list: The board list including all CrOS boards.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600156 buildinfo_list: A list of BuildInfo objects obtained from CIDB.
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700157
158 Returns:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600159 A branch build dict:
160 key: a tuple of (board, build_type, milestone), like:
161 ('wolf', 'release', '58')
162 value: the latest manifest for the given tuple, like:
163 '9242.0.0'.
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700164 """
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700165 branch_build_dict = {}
Craig Bergstrom58263d32018-04-26 14:11:35 -0600166 for build in buildinfo_list:
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700167 try:
168 build_config_info = parse_cros_build_config(build.board,
169 build.build_config)
170 except ValueError as e:
171 logging.warning('Failed to parse build config: %s: %s',
172 build.build_config, e)
173 continue
174
175 if build.board != build_config_info.board:
176 logging.warning('Non-matched build_config and board: %s, %s',
177 build.board, build.board)
178 continue
179
Xixuan Wu6fb16272017-10-19 13:16:00 -0700180 if build.board not in cros_board_list:
181 logging.warning('%s is not a valid CrOS board.', build.board)
182 continue
183
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700184 build_key = (build.board, build_config_info.type, build.milestone)
185 cur_manifest = branch_build_dict.get(build_key)
186 if cur_manifest is not None:
187 branch_build_dict[build_key] = max(
188 [cur_manifest, build.platform], key=version.LooseVersion)
189 else:
190 branch_build_dict[build_key] = build.platform
191
192 return branch_build_dict
193
194
Craig Bergstrom58263d32018-04-26 14:11:35 -0600195def get_cros_builds_since_date_from_db(db_client, cros_board_list, since_date):
196 """Get branch builds for ChromeOS boards.
197
198 Args:
199 db_client: a cloud_sql_client.CIDBClient object, to read cidb
200 build infos.
201 cros_board_list: The board list including all CrOS boards.
202 since_date: a datetime.datetime object in UTC to indicate since when CrOS
203 builds will be fetched.
204
205 Returns:
206 a two-tuple of branch build dicts. Each branch build dict has
207 keys and value described below. The first build_dict describes
208 successful builds and the second describes builds that failed
209 but met the relaxed success requirement (a successful 'HWTest [sanity]'
210 stage).
211 key: a tuple of (board, build_type, milestone), like:
212 ('wolf', 'release', '58')
213 value: the latest manifest for the given tuple, like:
214 '9242.0.0'.
215 """
216 # CIDB use UTC timezone
217 all_branch_builds = db_client.get_passed_builds_since_date(since_date)
218 relaxed_builds = db_client.get_relaxed_pased_builds_since_date(since_date)
219
220 branch_builds_dict = buildinfo_list_to_branch_build_dict(
221 cros_board_list, all_branch_builds)
222 relaxed_builds_dict = buildinfo_list_to_branch_build_dict(
223 cros_board_list, relaxed_builds)
224
225 return branch_builds_dict, relaxed_builds_dict
226
227
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700228def get_latest_launch_control_build(android_client, branch, target):
229 """Get the latest launch control build from Android Build API.
230
231 Args:
232 android_client: a rest_client.AndroidBuildRestClient object.
233 branch: the launch control branch.
234 target: the launch control target.
235
236 Returns:
237 a string latest launch control build id.
238
239 Raises:
240 NoBuildError if no latest launch control build is found.
241 """
242 try:
243 latest_build_id = android_client.get_latest_build_id(branch, target)
244 if latest_build_id is None:
245 raise NoBuildError('No latest builds is found.')
246
247 return latest_build_id
248 except apiclient.errors.HttpError as e:
249 raise NoBuildError('HttpError happened in getting the latest launch '
250 'control build for '
251 '%s,%s: %s' % (branch, target, str(e)))
252
253
254def get_launch_control_builds_by_branch_targets(
255 android_client, android_board_list, launch_control_branch_targets):
256 """Get latest launch_control_builds for android boards.
257
258 For every tasks in this event, if it has settings of launch control
259 branch & target, get the latest launch control build for it.
260
261 Args:
262 android_client: a rest_client.AndroidBuildRestClient object to
263 interact with android build API.
264 android_board_list: a list of Android boards.
265 launch_control_branch_targets: a dict of branch:targets, see property
266 launch_control_branch_targets in base_event.py.
267
268 Returns:
269 a launch control build dict:
270 key: an android board, like 'shamu'.
271 value: a list involves the latest builds for each pair
272 (branch, target) of this board, like:
273 [u'git_nyc-mr2-release/shamu-userdebug/3844975',
274 u'git_nyc-mr1-release/shamu-userdebug/3783920']
275 """
276 launch_control_dict = {}
277 board_to_builds_dict = {}
278 for branch, targets in launch_control_branch_targets.iteritems():
279 for t in targets:
280 try:
281 board = parse_launch_control_target(t).target
282 except ValueError:
283 logging.warning(
284 'Failed to parse launch control target: %s', t)
285 continue
286
287 if board not in android_board_list:
288 continue
289
290 # Use dict here to reduce the times to call AndroidBuild API
291 if launch_control_dict.get((branch, t)) is None:
292 try:
293 build_id = get_latest_launch_control_build(
294 android_client, branch, t)
295 except NoBuildError as e:
296 logging.warning(e)
297 continue
298
299 build = str(AndroidBuild(branch, t, build_id))
300 launch_control_dict[(branch, t)] = build
301 board_to_builds_dict.setdefault(board, []).append(
302 build)
303
304 for board, in board_to_builds_dict.iteritems():
305 mapped_board = get_board_by_android_target(board)
306 if mapped_board != board:
307 logging.debug('Map board %s to %s', board, mapped_board)
308 if board_to_builds_dict.get(mapped_board) is None:
309 del board_to_builds_dict[board]
310 else:
311 board_to_builds_dict[board] = board_to_builds_dict[
312 mapped_board]
313
314 return board_to_builds_dict
315
316
317def parse_launch_control_target(target):
318 """Parse the build target and type from a Launch Control target.
319
320 The Launch Control target has the format of build_target-build_type, e.g.,
321 shamu-eng or dragonboard-userdebug. This method extracts the build target
322 and type from the target name.
323
324 Args:
325 target: Name of a Launch Control target, e.g., shamu-userdebug.
326
327 Returns:
328 a LaunchControlBuildTargetInfo object whose value is like
329 (target='shamu',
330 type='userdebug')
331
332 Raises:
333 ValueError: if target is not valid.
334 """
335 match = re.match(_LAUNCH_CONTROL_TARGET_PATTERN, target)
336 if not match:
337 raise ValueError('target format is not valid')
338
339 return LaunchControlBuildTargetInfo(match.group('build_target'),
340 match.group('build_type'))
341
342
343def parse_cros_build_config(board, build_config):
344 """Parse build_type from a given builder for a given board.
345
346 Args:
347 board: the prefix of a ChromeOS build_config, representing board.
348 build_config: a ChromeOS build_config name, like 'kevin-release'.
349
350 Returns:
351 a CrOSBuildConfigInfo object whose value is like
352 (board='kevin',
353 type='release')
354
355 Raises:
356 ValueError: if build_config is in invalid form.
357 """
358 if build_config[0:len(board)] != board:
359 raise ValueError('build_config cannot be parsed: %s' % build_config)
360
361 match = re.match(_CROS_BUILD_CONFIG_PATTERN, build_config[len(board):])
362 if not match:
363 raise ValueError('build_config %s is not matched %s' % (
364 build_config, _CROS_BUILD_CONFIG_PATTERN))
365
366 return CrOSBuildConfigInfo(board, match.groups()[0])
367
368
369def get_board_by_android_target(target):
370 """Map a android target to a android board.
371
372 # Mapping between an android board name and a build target. This is for
373 # special case handling for certain Android board that the board name and
374 # build target name does not match.
375 # This comes from server/site_utils.py in autotest module.
376
377 Args:
378 target: an android target.
379
380 Returns:
381 a string android board mapped by ANDROID_TARGET_TO_BOARD_MAP.
382 """
383 return _ANDROID_TARGET_TO_BOARD_MAP.get(target, target)