blob: df49fd4a3fccec48ca23953d101f09a48637bfe8 [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
Xixuan Wu244e0ec2018-05-23 14:49:55 -070010import json
xixuanbea010f2017-03-27 10:10:19 -070011import logging
12import re
13
Xixuan Wu5d6063e2017-09-05 16:15:07 -070014import apiclient
15
xixuanbea010f2017-03-27 10:10:19 -070016# Bare branches
17BARE_BRANCHES = ['factory', 'firmware']
18
19# Definition of os types.
20OS_TYPE_CROS = 'cros'
21OS_TYPE_BRILLO = 'brillo'
22OS_TYPE_ANDROID = 'android'
23OS_TYPES = [OS_TYPE_CROS, OS_TYPE_BRILLO, OS_TYPE_ANDROID]
24OS_TYPES_LAUNCH_CONTROL = [OS_TYPE_BRILLO, OS_TYPE_ANDROID]
25
Xixuan Wu5d6063e2017-09-05 16:15:07 -070026# Launch control build's target's information
xixuanbea010f2017-03-27 10:10:19 -070027LaunchControlBuildTargetInfo = collections.namedtuple(
28 'LaunchControlBuildTargetInfo',
29 [
Xixuan Wu5d6063e2017-09-05 16:15:07 -070030 'target',
31 'type',
xixuanbea010f2017-03-27 10:10:19 -070032 ])
33
Xixuan Wu5d6063e2017-09-05 16:15:07 -070034# ChromeOS build config's information
xixuanbea010f2017-03-27 10:10:19 -070035CrOSBuildConfigInfo = collections.namedtuple(
36 'CrOSBuildConfigInfo',
37 [
Xixuan Wu5d6063e2017-09-05 16:15:07 -070038 'board',
39 'type',
xixuanbea010f2017-03-27 10:10:19 -070040 ])
41
xixuanbea010f2017-03-27 10:10:19 -070042# The default build type for fetching latest build.
Xixuan Wu7899db52018-05-29 14:19:06 -070043_DEFAULT_BUILD_SUFFIX = '-release'
xixuanbea010f2017-03-27 10:10:19 -070044
45# The default setting of board for fetching latest build.
46_DEFAULT_MASTER = 'master'
47
48# The path for storing the latest build.
49_GS_LATEST_MASTER_PATTERN = '%(board)s%(suffix)s/%(name)s'
50
51# The gs bucket to fetch the latest build.
52_GS_BUCKET = 'chromeos-image-archive'
53
54# The file in Google Storage to fetch the latest build.
55_LATEST_MASTER = 'LATEST-master'
56
Xixuan Wu244e0ec2018-05-23 14:49:55 -070057# The bucket for ChromeOS build release boards. Maintained by GoldenEye.
58_GE_RELEASE_BUILD_BUCKET = 'chromeos-build-release-console'
59
xixuanbea010f2017-03-27 10:10:19 -070060# Special android target to board map.
61_ANDROID_TARGET_TO_BOARD_MAP = {
62 'seed_l8150': 'gm4g_sprout',
63 'bat_land': 'bat'
64}
65
66# CrOS build name patter
67_CROS_BUILD_PATTERN = '%(board)s-%(build_type)s/R%(milestone)s-%(manifest)s'
68
69# Android build name pattern
70_ANDROID_BUILD_PATTERN = '%(branch)s/%(target)s/%(build_id)s'
71
72# The pattern for Launch Control target
73_LAUNCH_CONTROL_TARGET_PATTERN = r'(?P<build_target>.+)-(?P<build_type>[^-]+)'
74
75# The pattern for CrOS build config
76_CROS_BUILD_CONFIG_PATTERN = r'-([^-]+)(?:-group)?'
Xixuan Wu5d6063e2017-09-05 16:15:07 -070077
78
79class NoBuildError(Exception):
80 """Raised when failing to get the required build from Google Storage."""
81
82
83class BuildType(object):
84 """Representing the type of test source build.
85
86 This is used to identify the test source build for testing.
87 """
88 FIRMWARE_RW = 'firmware_rw'
89 FIRMWARE_RO = 'firmware_ro'
90 CROS = 'cros'
91
92
93class BuildVersionKey(object):
94 """Keys referring to the builds to install in run_suites."""
95
96 CROS_VERSION = 'cros_version'
97 ANDROID_BUILD_VERSION = 'android_version'
98 TESTBED_BUILD_VERSION = 'testbed_version'
99 FW_RW_VERSION = 'fwrw_version'
100 FW_RO_VERSION = 'fwro_version'
101
102
103class AndroidBuild(collections.namedtuple(
104 '_AndroidBuildBase', ['branch', 'target', 'build_id']), object):
105 """Class for constructing android build string."""
106
107 def __str__(self):
108 return _ANDROID_BUILD_PATTERN % {'branch': self.branch,
109 'target': self.target,
110 'build_id': self.build_id}
111
112
113class CrOSBuild(collections.namedtuple(
114 '_CrOSBuildBase',
115 ['board', 'build_type', 'milestone', 'manifest']), object):
116 """Class for constructing ChromeOS build string."""
117
118 def __str__(self):
119 return _CROS_BUILD_PATTERN % {'board': self.board,
120 'build_type': self.build_type,
121 'milestone': self.milestone,
122 'manifest': self.manifest}
123
124
125def get_latest_cros_build_from_gs(storage_client, board=None, suffix=None):
126 """Get latest build for given board from Google Storage.
127
128 Args:
129 storage_client: a rest_client.StorageRestClient object.
130 board: the board to fetch latest build. Default is 'master'.
131 suffix: suffix represents build channel, like '-release'.
132 Default is '-paladin'.
133
134 Returns:
135 a ChromeOS version string, e.g. '59.0.000.0'.
136
137 Raises:
138 HttpError if error happens in interacting with Google Storage.
139 """
140 board = board if board is not None else _DEFAULT_MASTER
141 suffix = suffix if suffix is not None else _DEFAULT_BUILD_SUFFIX
142 file_to_check = _GS_LATEST_MASTER_PATTERN % {
143 'board': board,
144 'suffix': suffix,
145 'name': _LATEST_MASTER}
146
147 try:
148 return storage_client.read_object(_GS_BUCKET, file_to_check)
149 except apiclient.errors.HttpError as e:
150 raise NoBuildError(
151 'Cannot find latest build for board %s, suffix %s: %s' %
152 (board, suffix, str(e)))
153
154
Xixuan Wu244e0ec2018-05-23 14:49:55 -0700155def get_board_family_mapping_from_gs(storage_client):
156 """Get board_family to boards mapping from Google Storage.
157
158 Args:
159 storage_client: a rest_client.StorageRestClient object.
160
161 Returns:
162 a dictionary of mapping between board family name to boards, e.g.
163 {'nyan': ['nyan', 'nyan_big', 'nyan_blaze', ..]}
164
165 Raises:
166 HttpError if error happens in interacting with Google Storage.
167 """
168 try:
169 boards = storage_client.read_object(_GE_RELEASE_BUILD_BUCKET, 'boards.json')
170 json_object = json.loads(boards)
171 board_family = {}
172 for board in json_object['boards']:
173 group = board['reference_group']
174 if not group:
175 continue
176
177 # This is to change boards like nyan-blaze to nyan_blaze, which is
178 # actually used in lab.
179 board_name = board['public_codename'].replace('-', '_')
180 if group not in board_family:
181 board_family[group] = []
182
183 board_family[group].append(board_name)
184
185 logging.info('Successfully get following board families from GS: %r',
186 board_family.keys())
187 return board_family
188 except apiclient.errors.HttpError as e:
189 logging.error('Cannot load boards.json in bucket %s: %s',
190 _GE_RELEASE_BUILD_BUCKET, str(e))
191 raise
192
193
Craig Bergstrom58263d32018-04-26 14:11:35 -0600194def buildinfo_list_to_branch_build_dict(cros_board_list, buildinfo_list):
195 """Validate and convert a list of BuildInfo to branch build dict.
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700196
197 Args:
Xixuan Wu6fb16272017-10-19 13:16:00 -0700198 cros_board_list: The board list including all CrOS boards.
Craig Bergstrom58263d32018-04-26 14:11:35 -0600199 buildinfo_list: A list of BuildInfo objects obtained from CIDB.
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700200
201 Returns:
Craig Bergstrom58263d32018-04-26 14:11:35 -0600202 A branch build dict:
203 key: a tuple of (board, build_type, milestone), like:
204 ('wolf', 'release', '58')
205 value: the latest manifest for the given tuple, like:
206 '9242.0.0'.
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700207 """
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700208 branch_build_dict = {}
Craig Bergstrom58263d32018-04-26 14:11:35 -0600209 for build in buildinfo_list:
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700210 try:
211 build_config_info = parse_cros_build_config(build.board,
212 build.build_config)
213 except ValueError as e:
214 logging.warning('Failed to parse build config: %s: %s',
215 build.build_config, e)
216 continue
217
218 if build.board != build_config_info.board:
219 logging.warning('Non-matched build_config and board: %s, %s',
220 build.board, build.board)
221 continue
222
Xixuan Wu6fb16272017-10-19 13:16:00 -0700223 if build.board not in cros_board_list:
224 logging.warning('%s is not a valid CrOS board.', build.board)
225 continue
226
Xixuan Wu8d2f2862018-08-28 16:48:04 -0700227 build_key = (build.board, build.model, build_config_info.type,
228 build.milestone)
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700229 cur_manifest = branch_build_dict.get(build_key)
230 if cur_manifest is not None:
231 branch_build_dict[build_key] = max(
232 [cur_manifest, build.platform], key=version.LooseVersion)
233 else:
234 branch_build_dict[build_key] = build.platform
235
236 return branch_build_dict
237
238
Craig Bergstrom58263d32018-04-26 14:11:35 -0600239def get_cros_builds_since_date_from_db(db_client, cros_board_list, since_date):
240 """Get branch builds for ChromeOS boards.
241
242 Args:
243 db_client: a cloud_sql_client.CIDBClient object, to read cidb
244 build infos.
245 cros_board_list: The board list including all CrOS boards.
246 since_date: a datetime.datetime object in UTC to indicate since when CrOS
247 builds will be fetched.
248
249 Returns:
250 a two-tuple of branch build dicts. Each branch build dict has
251 keys and value described below. The first build_dict describes
252 successful builds and the second describes builds that failed
253 but met the relaxed success requirement (a successful 'HWTest [sanity]'
254 stage).
255 key: a tuple of (board, build_type, milestone), like:
256 ('wolf', 'release', '58')
257 value: the latest manifest for the given tuple, like:
258 '9242.0.0'.
259 """
260 # CIDB use UTC timezone
261 all_branch_builds = db_client.get_passed_builds_since_date(since_date)
262 relaxed_builds = db_client.get_relaxed_pased_builds_since_date(since_date)
263
264 branch_builds_dict = buildinfo_list_to_branch_build_dict(
265 cros_board_list, all_branch_builds)
266 relaxed_builds_dict = buildinfo_list_to_branch_build_dict(
Xixuan Wu384f9132018-08-28 15:48:26 -0700267 cros_board_list, relaxed_builds + all_branch_builds)
Craig Bergstrom58263d32018-04-26 14:11:35 -0600268
269 return branch_builds_dict, relaxed_builds_dict
270
271
Xixuan Wu5d6063e2017-09-05 16:15:07 -0700272def get_latest_launch_control_build(android_client, branch, target):
273 """Get the latest launch control build from Android Build API.
274
275 Args:
276 android_client: a rest_client.AndroidBuildRestClient object.
277 branch: the launch control branch.
278 target: the launch control target.
279
280 Returns:
281 a string latest launch control build id.
282
283 Raises:
284 NoBuildError if no latest launch control build is found.
285 """
286 try:
287 latest_build_id = android_client.get_latest_build_id(branch, target)
288 if latest_build_id is None:
289 raise NoBuildError('No latest builds is found.')
290
291 return latest_build_id
292 except apiclient.errors.HttpError as e:
293 raise NoBuildError('HttpError happened in getting the latest launch '
294 'control build for '
295 '%s,%s: %s' % (branch, target, str(e)))
296
297
298def get_launch_control_builds_by_branch_targets(
299 android_client, android_board_list, launch_control_branch_targets):
300 """Get latest launch_control_builds for android boards.
301
302 For every tasks in this event, if it has settings of launch control
303 branch & target, get the latest launch control build for it.
304
305 Args:
306 android_client: a rest_client.AndroidBuildRestClient object to
307 interact with android build API.
308 android_board_list: a list of Android boards.
309 launch_control_branch_targets: a dict of branch:targets, see property
310 launch_control_branch_targets in base_event.py.
311
312 Returns:
313 a launch control build dict:
314 key: an android board, like 'shamu'.
315 value: a list involves the latest builds for each pair
316 (branch, target) of this board, like:
317 [u'git_nyc-mr2-release/shamu-userdebug/3844975',
318 u'git_nyc-mr1-release/shamu-userdebug/3783920']
319 """
320 launch_control_dict = {}
321 board_to_builds_dict = {}
322 for branch, targets in launch_control_branch_targets.iteritems():
323 for t in targets:
324 try:
325 board = parse_launch_control_target(t).target
326 except ValueError:
327 logging.warning(
328 'Failed to parse launch control target: %s', t)
329 continue
330
331 if board not in android_board_list:
332 continue
333
334 # Use dict here to reduce the times to call AndroidBuild API
335 if launch_control_dict.get((branch, t)) is None:
336 try:
337 build_id = get_latest_launch_control_build(
338 android_client, branch, t)
339 except NoBuildError as e:
340 logging.warning(e)
341 continue
342
343 build = str(AndroidBuild(branch, t, build_id))
344 launch_control_dict[(branch, t)] = build
345 board_to_builds_dict.setdefault(board, []).append(
346 build)
347
348 for board, in board_to_builds_dict.iteritems():
349 mapped_board = get_board_by_android_target(board)
350 if mapped_board != board:
351 logging.debug('Map board %s to %s', board, mapped_board)
352 if board_to_builds_dict.get(mapped_board) is None:
353 del board_to_builds_dict[board]
354 else:
355 board_to_builds_dict[board] = board_to_builds_dict[
356 mapped_board]
357
358 return board_to_builds_dict
359
360
361def parse_launch_control_target(target):
362 """Parse the build target and type from a Launch Control target.
363
364 The Launch Control target has the format of build_target-build_type, e.g.,
365 shamu-eng or dragonboard-userdebug. This method extracts the build target
366 and type from the target name.
367
368 Args:
369 target: Name of a Launch Control target, e.g., shamu-userdebug.
370
371 Returns:
372 a LaunchControlBuildTargetInfo object whose value is like
373 (target='shamu',
374 type='userdebug')
375
376 Raises:
377 ValueError: if target is not valid.
378 """
379 match = re.match(_LAUNCH_CONTROL_TARGET_PATTERN, target)
380 if not match:
381 raise ValueError('target format is not valid')
382
383 return LaunchControlBuildTargetInfo(match.group('build_target'),
384 match.group('build_type'))
385
386
387def parse_cros_build_config(board, build_config):
388 """Parse build_type from a given builder for a given board.
389
390 Args:
391 board: the prefix of a ChromeOS build_config, representing board.
392 build_config: a ChromeOS build_config name, like 'kevin-release'.
393
394 Returns:
395 a CrOSBuildConfigInfo object whose value is like
396 (board='kevin',
397 type='release')
398
399 Raises:
400 ValueError: if build_config is in invalid form.
401 """
402 if build_config[0:len(board)] != board:
403 raise ValueError('build_config cannot be parsed: %s' % build_config)
404
405 match = re.match(_CROS_BUILD_CONFIG_PATTERN, build_config[len(board):])
406 if not match:
407 raise ValueError('build_config %s is not matched %s' % (
408 build_config, _CROS_BUILD_CONFIG_PATTERN))
409
410 return CrOSBuildConfigInfo(board, match.groups()[0])
411
412
413def get_board_by_android_target(target):
414 """Map a android target to a android board.
415
416 # Mapping between an android board name and a build target. This is for
417 # special case handling for certain Android board that the board name and
418 # build target name does not match.
419 # This comes from server/site_utils.py in autotest module.
420
421 Args:
422 target: an android target.
423
424 Returns:
425 a string android board mapped by ANDROID_TARGET_TO_BOARD_MAP.
426 """
427 return _ANDROID_TARGET_TO_BOARD_MAP.get(target, target)