blob: 0bb5c990b5b981d6f97bad41930ff291138baa25 [file] [log] [blame]
Luis Hector Chavezdca9dd72018-06-12 19:56:301# -*- coding: utf-8 -*-
joychen3cb228e2013-06-12 19:13:132# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Chris Sosa0eecf96e2014-02-03 22:14:396"""Main module for parsing and interpreting XBuddy paths for the devserver."""
7
Gilad Arnold5f46d8e2015-02-19 20:17:558from __future__ import print_function
9
Yiming Chenaab488e2014-11-17 22:49:3110import cherrypy
joychen562699a2013-08-13 22:22:1411import ConfigParser
joychen3cb228e2013-06-12 19:13:1312import datetime
xixuan44b55452016-09-06 22:35:5613import distutils.version
joychen3cb228e2013-06-12 19:13:1314import operator
15import os
joychenf8f07e22013-07-13 00:45:5116import re
joychen3cb228e2013-06-12 19:13:1317import shutil
joychenf8f07e22013-07-13 00:45:5118import time
joychen3cb228e2013-06-12 19:13:1319import threading
20
21import artifact_info
Gabe Black3b567202015-09-23 21:07:5922import build_artifact
23import build_util
joychen3cb228e2013-06-12 19:13:1324import common_util
25import devserver_constants
26import downloader
27import log_util
28
xixuan44b55452016-09-06 22:35:5629# Make sure that chromite is available to import.
30import setup_chromite # pylint: disable=unused-import
31
32try:
33 from chromite.lib import gs
Gwendal Grignouad4cb982017-03-31 18:36:1934except ImportError:
xixuan44b55452016-09-06 22:35:5635 gs = None
36
joychen3cb228e2013-06-12 19:13:1337# Module-local log function.
38def _Log(message, *args):
39 return log_util.LogWithTag('XBUDDY', message, *args)
40
joychen562699a2013-08-13 22:22:1441# xBuddy config constants
42CONFIG_FILE = 'xbuddy_config.ini'
43SHADOW_CONFIG_FILE = 'shadow_xbuddy_config.ini'
44PATH_REWRITES = 'PATH_REWRITES'
45GENERAL = 'GENERAL'
Gilad Arnold896c6d82015-03-13 23:20:2946LOCATION_SUFFIXES = 'LOCATION_SUFFIXES'
joychen921e1fb2013-06-28 18:12:2047
Chris Sosac2abc722013-08-27 00:11:2248# Path for shadow config in chroot.
49CHROOT_SHADOW_DIR = '/mnt/host/source/src/platform/dev'
50
joychen25d25972013-07-30 21:54:1651# XBuddy aliases
52TEST = 'test'
53BASE = 'base'
54DEV = 'dev'
55FULL = 'full_payload'
Luis Hector Chavezdca9dd72018-06-12 19:56:3056SIGNED = 'signed'
joychen25d25972013-07-30 21:54:1657RECOVERY = 'recovery'
58STATEFUL = 'stateful'
59AUTOTEST = 'autotest'
Mike Frysingera0e6a282016-09-01 21:29:0860FACTORY_SHIM = 'factory_shim'
joychen25d25972013-07-30 21:54:1661
joychen921e1fb2013-06-28 18:12:2062# Local build constants
joychenc3944cb2013-08-19 17:42:0763ANY = "ANY"
joychen7df67f72013-07-18 21:21:1264LATEST = "latest"
65LOCAL = "local"
66REMOTE = "remote"
Chris Sosa75490802013-10-01 00:21:4567
68# TODO(sosa): Fix a lot of assumptions about these aliases. There is too much
69# implicit logic here that's unnecessary. What should be done:
70# 1) Collapse Alias logic to one set of aliases for xbuddy (not local/remote).
71# 2) Do not use zip when creating these dicts. Better to not rely on ordering.
72# 3) Move alias/artifact mapping to a central module rather than having it here.
73# 4) Be explicit when things are missing i.e. no dev images in image.zip.
74
joychen921e1fb2013-06-28 18:12:2075LOCAL_ALIASES = [
Gilad Arnold5f46d8e2015-02-19 20:17:5576 TEST,
77 DEV,
78 BASE,
79 RECOVERY,
Mike Frysingera0e6a282016-09-01 21:29:0880 FACTORY_SHIM,
Gilad Arnold5f46d8e2015-02-19 20:17:5581 FULL,
82 STATEFUL,
83 ANY,
joychen921e1fb2013-06-28 18:12:2084]
85
86LOCAL_FILE_NAMES = [
Gilad Arnold5f46d8e2015-02-19 20:17:5587 devserver_constants.TEST_IMAGE_FILE,
88 devserver_constants.IMAGE_FILE,
89 devserver_constants.BASE_IMAGE_FILE,
90 devserver_constants.RECOVERY_IMAGE_FILE,
Mike Frysingera0e6a282016-09-01 21:29:0891 devserver_constants.FACTORY_SHIM_IMAGE_FILE,
Gilad Arnold5f46d8e2015-02-19 20:17:5592 devserver_constants.UPDATE_FILE,
93 devserver_constants.STATEFUL_FILE,
94 None, # For ANY.
joychen921e1fb2013-06-28 18:12:2095]
96
97LOCAL_ALIAS_TO_FILENAME = dict(zip(LOCAL_ALIASES, LOCAL_FILE_NAMES))
98
99# Google Storage constants
100GS_ALIASES = [
Gilad Arnold5f46d8e2015-02-19 20:17:55101 TEST,
102 BASE,
103 RECOVERY,
Luis Hector Chavezdca9dd72018-06-12 19:56:30104 SIGNED,
Mike Frysingera0e6a282016-09-01 21:29:08105 FACTORY_SHIM,
Gilad Arnold5f46d8e2015-02-19 20:17:55106 FULL,
107 STATEFUL,
108 AUTOTEST,
joychen3cb228e2013-06-12 19:13:13109]
110
joychen921e1fb2013-06-28 18:12:20111GS_FILE_NAMES = [
Gilad Arnold5f46d8e2015-02-19 20:17:55112 devserver_constants.TEST_IMAGE_FILE,
113 devserver_constants.BASE_IMAGE_FILE,
114 devserver_constants.RECOVERY_IMAGE_FILE,
Luis Hector Chavezdca9dd72018-06-12 19:56:30115 devserver_constants.SIGNED_IMAGE_FILE,
Mike Frysingera0e6a282016-09-01 21:29:08116 devserver_constants.FACTORY_SHIM_IMAGE_FILE,
Gilad Arnold5f46d8e2015-02-19 20:17:55117 devserver_constants.UPDATE_FILE,
118 devserver_constants.STATEFUL_FILE,
119 devserver_constants.AUTOTEST_DIR,
joychen3cb228e2013-06-12 19:13:13120]
121
122ARTIFACTS = [
Gilad Arnold5f46d8e2015-02-19 20:17:55123 artifact_info.TEST_IMAGE,
124 artifact_info.BASE_IMAGE,
125 artifact_info.RECOVERY_IMAGE,
Luis Hector Chavezdca9dd72018-06-12 19:56:30126 artifact_info.SIGNED_IMAGE,
Mike Frysingera0e6a282016-09-01 21:29:08127 artifact_info.FACTORY_SHIM_IMAGE,
Gilad Arnold5f46d8e2015-02-19 20:17:55128 artifact_info.FULL_PAYLOAD,
129 artifact_info.STATEFUL_PAYLOAD,
130 artifact_info.AUTOTEST,
joychen3cb228e2013-06-12 19:13:13131]
132
joychen921e1fb2013-06-28 18:12:20133GS_ALIAS_TO_FILENAME = dict(zip(GS_ALIASES, GS_FILE_NAMES))
134GS_ALIAS_TO_ARTIFACT = dict(zip(GS_ALIASES, ARTIFACTS))
joychen3cb228e2013-06-12 19:13:13135
joychen921e1fb2013-06-28 18:12:20136LATEST_OFFICIAL = "latest-official"
joychen3cb228e2013-06-12 19:13:13137
Chris Sosaea734d92013-10-11 18:28:58138RELEASE = "-release"
joychen3cb228e2013-06-12 19:13:13139
joychen3cb228e2013-06-12 19:13:13140
141class XBuddyException(Exception):
142 """Exception classes used by this module."""
143 pass
144
145
146# no __init__ method
147#pylint: disable=W0232
Gilad Arnold5f46d8e2015-02-19 20:17:55148class Timestamp(object):
joychen3cb228e2013-06-12 19:13:13149 """Class to translate build path strings and timestamp filenames."""
150
151 _TIMESTAMP_DELIMITER = 'SLASH'
152 XBUDDY_TIMESTAMP_DIR = 'xbuddy_UpdateTimestamps'
153
154 @staticmethod
155 def TimestampToBuild(timestamp_filename):
156 return timestamp_filename.replace(Timestamp._TIMESTAMP_DELIMITER, '/')
157
158 @staticmethod
159 def BuildToTimestamp(build_path):
160 return build_path.replace('/', Timestamp._TIMESTAMP_DELIMITER)
joychen921e1fb2013-06-28 18:12:20161
162 @staticmethod
163 def UpdateTimestamp(timestamp_dir, build_id):
164 """Update timestamp file of build with build_id."""
165 common_util.MkDirP(timestamp_dir)
joychen562699a2013-08-13 22:22:14166 _Log("Updating timestamp for %s", build_id)
joychen921e1fb2013-06-28 18:12:20167 time_file = os.path.join(timestamp_dir,
168 Timestamp.BuildToTimestamp(build_id))
169 with file(time_file, 'a'):
170 os.utime(time_file, None)
joychen3cb228e2013-06-12 19:13:13171#pylint: enable=W0232
172
173
joychen921e1fb2013-06-28 18:12:20174class XBuddy(build_util.BuildObject):
joychen3cb228e2013-06-12 19:13:13175 """Class that manages image retrieval and caching by the devserver.
176
177 Image retrieval by xBuddy path:
178 XBuddy accesses images and artifacts that it stores using an xBuddy
179 path of the form: board/version/alias
180 The primary xbuddy.Get call retrieves the correct artifact or url to where
181 the artifacts can be found.
182
183 Image caching:
184 Images and other artifacts are stored identically to how they would have
185 been if devserver's stage rpc was called and the xBuddy cache replaces
186 build versions on a LRU basis. Timestamps are maintained by last accessed
187 times of representative files in the a directory in the static serve
188 directory (XBUDDY_TIMESTAMP_DIR).
189
190 Private class members:
joychen121fc9b2013-08-02 21:30:30191 _true_values: used for interpreting boolean values
192 _staging_thread_count: track download requests
193 _timestamp_folder: directory with empty files standing in as timestamps
joychen921e1fb2013-06-28 18:12:20194 for each image currently cached by xBuddy
joychen3cb228e2013-06-12 19:13:13195 """
196 _true_values = ['true', 't', 'yes', 'y']
197
198 # Number of threads that are staging images.
199 _staging_thread_count = 0
200 # Lock used to lock increasing/decreasing count.
201 _staging_thread_count_lock = threading.Lock()
202
Gilad Arnoldd04fcab2015-02-19 20:00:45203 def __init__(self, manage_builds=False, board=None, version=None,
204 images_dir=None, log_screen=True, **kwargs):
joychen921e1fb2013-06-28 18:12:20205 super(XBuddy, self).__init__(**kwargs)
joychenb0dfe552013-07-30 17:02:06206
Yiming Chenaab488e2014-11-17 22:49:31207 if not log_screen:
208 cherrypy.config.update({'log.screen': False})
209
joychen562699a2013-08-13 22:22:14210 self.config = self._ReadConfig()
211 self._manage_builds = manage_builds or self._ManageBuilds()
Chris Sosa75490802013-10-01 00:21:45212 self._board = board
Gilad Arnoldd04fcab2015-02-19 20:00:45213 self._version = version
joychen921e1fb2013-06-28 18:12:20214 self._timestamp_folder = os.path.join(self.static_dir,
joychen3cb228e2013-06-12 19:13:13215 Timestamp.XBUDDY_TIMESTAMP_DIR)
Chris Sosa7cd23202013-10-16 00:22:57216 if images_dir:
217 self.images_dir = images_dir
218 else:
219 self.images_dir = os.path.join(self.GetSourceRoot(), 'src/build/images')
220
xixuan178263c2017-03-22 16:10:25221 if common_util.IsRunningOnMoblab():
222 self._ctx = gs.GSContext(cache_user='chronos') if gs else None
223 else:
224 self._ctx = gs.GSContext() if gs else None
xixuan44b55452016-09-06 22:35:56225
joychen7df67f72013-07-18 21:21:12226 common_util.MkDirP(self._timestamp_folder)
joychen3cb228e2013-06-12 19:13:13227
228 @classmethod
229 def ParseBoolean(cls, boolean_string):
230 """Evaluate a string to a boolean value"""
231 if boolean_string:
232 return boolean_string.lower() in cls._true_values
233 else:
234 return False
235
joychen562699a2013-08-13 22:22:14236 def _ReadConfig(self):
237 """Read xbuddy config from ini files.
238
239 Reads the base config from xbuddy_config.ini, and then merges in the
240 shadow config from shadow_xbuddy_config.ini
241
242 Returns:
243 The merged configuration.
Yu-Ju Hongc54658c2014-01-22 17:18:07244
joychen562699a2013-08-13 22:22:14245 Raises:
246 XBuddyException if the config file is missing.
247 """
248 xbuddy_config = ConfigParser.ConfigParser()
249 config_file = os.path.join(self.devserver_dir, CONFIG_FILE)
250 if os.path.exists(config_file):
251 xbuddy_config.read(config_file)
252 else:
Yiming Chend9202142014-11-07 22:56:52253 # Get the directory of xbuddy.py file.
254 file_dir = os.path.dirname(os.path.realpath(__file__))
255 # Read the default xbuddy_config.ini from the directory.
256 xbuddy_config.read(os.path.join(file_dir, CONFIG_FILE))
joychen562699a2013-08-13 22:22:14257
258 # Read the shadow file if there is one.
Chris Sosac2abc722013-08-27 00:11:22259 if os.path.isdir(CHROOT_SHADOW_DIR):
260 shadow_config_file = os.path.join(CHROOT_SHADOW_DIR, SHADOW_CONFIG_FILE)
261 else:
262 shadow_config_file = os.path.join(self.devserver_dir, SHADOW_CONFIG_FILE)
263
264 _Log('Using shadow config file stored at %s', shadow_config_file)
joychen562699a2013-08-13 22:22:14265 if os.path.exists(shadow_config_file):
266 shadow_xbuddy_config = ConfigParser.ConfigParser()
267 shadow_xbuddy_config.read(shadow_config_file)
268
269 # Merge shadow config in.
270 sections = shadow_xbuddy_config.sections()
271 for s in sections:
272 if not xbuddy_config.has_section(s):
273 xbuddy_config.add_section(s)
274 options = shadow_xbuddy_config.options(s)
275 for o in options:
276 val = shadow_xbuddy_config.get(s, o)
277 xbuddy_config.set(s, o, val)
278
279 return xbuddy_config
280
281 def _ManageBuilds(self):
282 """Checks if xBuddy is managing local builds using the current config."""
283 try:
284 return self.ParseBoolean(self.config.get(GENERAL, 'manage_builds'))
285 except ConfigParser.Error:
286 return False
287
288 def _Capacity(self):
289 """Gets the xbuddy capacity from the current config."""
290 try:
291 return int(self.config.get(GENERAL, 'capacity'))
292 except ConfigParser.Error:
293 return 5
294
Gilad Arnold38e828c2015-04-24 20:52:07295 def LookupAlias(self, alias, board=None, version=None):
joychen562699a2013-08-13 22:22:14296 """Given the full xbuddy config, look up an alias for path rewrite.
297
298 Args:
299 alias: The xbuddy path that could be one of the aliases in the
300 rewrite table.
301 board: The board to fill in with when paths are rewritten. Can be from
Gilad Arnold38e828c2015-04-24 20:52:07302 the update request xml or the default board from devserver. If None,
303 defers to the value given during XBuddy initialization.
Gilad Arnoldd04fcab2015-02-19 20:00:45304 version: The version to fill in when rewriting paths. Could be a specific
Gilad Arnold38e828c2015-04-24 20:52:07305 version number or a version alias like LATEST. If None, defers to the
306 value given during XBuddy initialization, or LATEST.
Yu-Ju Hongc54658c2014-01-22 17:18:07307
joychen562699a2013-08-13 22:22:14308 Returns:
Gilad Arnold896c6d82015-03-13 23:20:29309 A pair (val, suffix) where val is the rewritten path, or the original
310 string if no rewrite was found; and suffix is the assigned location
311 suffix, or the default suffix if none was found.
joychen562699a2013-08-13 22:22:14312 """
joychen562699a2013-08-13 22:22:14313 try:
Gilad Arnold896c6d82015-03-13 23:20:29314 suffix = self.config.get(LOCATION_SUFFIXES, alias)
315 except ConfigParser.Error:
316 suffix = RELEASE
317
318 try:
joychen562699a2013-08-13 22:22:14319 val = self.config.get(PATH_REWRITES, alias)
320 except ConfigParser.Error:
321 # No alias lookup found. Return original path.
Gilad Arnold896c6d82015-03-13 23:20:29322 val = None
joychen562699a2013-08-13 22:22:14323
Gilad Arnold896c6d82015-03-13 23:20:29324 if not (val and val.strip()):
325 val = alias
joychen562699a2013-08-13 22:22:14326 else:
Gilad Arnold896c6d82015-03-13 23:20:29327 # The found value is not an empty string.
Gilad Arnoldd04fcab2015-02-19 20:00:45328 # Fill in the board and version.
Gilad Arnold896c6d82015-03-13 23:20:29329 val = val.replace("BOARD", "%(board)s")
330 val = val.replace("VERSION", "%(version)s")
Gilad Arnold38e828c2015-04-24 20:52:07331 val = val % {'board': board or self._board,
332 'version': version or self._version or LATEST}
Gilad Arnold896c6d82015-03-13 23:20:29333
334 _Log("Path is %s, location suffix is %s", val, suffix)
335 return val, suffix
joychen562699a2013-08-13 22:22:14336
Simran Basi99e63c02014-05-20 17:39:52337 @staticmethod
338 def _ResolveImageDir(image_dir):
339 """Clean up and return the image dir to use.
340
341 Args:
342 image_dir: directory in Google Storage to use.
343
344 Returns:
345 |image_dir| if |image_dir| is not None. Otherwise, returns
346 devserver_constants.GS_IMAGE_DIR
347 """
348 image_dir = image_dir or devserver_constants.GS_IMAGE_DIR
349 # Remove trailing slashes.
350 return image_dir.rstrip('/')
351
Gilad Arnold896c6d82015-03-13 23:20:29352 def _LookupOfficial(self, board, suffix, image_dir=None):
joychenf8f07e22013-07-13 00:45:51353 """Check LATEST-master for the version number of interest."""
354 _Log("Checking gs for latest %s-%s image", board, suffix)
Simran Basi99e63c02014-05-20 17:39:52355 image_dir = XBuddy._ResolveImageDir(image_dir)
356 latest_addr = (devserver_constants.GS_LATEST_MASTER %
357 {'image_dir': image_dir,
358 'board': board,
359 'suffix': suffix})
joychen121fc9b2013-08-02 21:30:30360 # Full release + version is in the LATEST file.
xixuan44b55452016-09-06 22:35:56361 version = self._ctx.Cat(latest_addr)
joychen3cb228e2013-06-12 19:13:13362
joychenf8f07e22013-07-13 00:45:51363 return devserver_constants.IMAGE_DIR % {'board':board,
364 'suffix':suffix,
365 'version':version}
Gilad Arnold869e8ab2015-02-20 07:34:49366
xixuan44b55452016-09-06 22:35:56367 def _LS(self, path, list_subdirectory=False):
368 """Does a directory listing of the given gs path.
369
370 Args:
371 path: directory location on google storage to check.
372 list_subdirectory: whether to only list subdirectory for |path|.
373
374 Returns:
375 A list of paths that matched |path|.
376 """
377 if list_subdirectory:
378 return self._ctx.DoCommand(
Gwendal Grignou76d08792017-03-31 18:32:16379 ['ls', '-d', '--', path], redirect_stdout=True).output.splitlines()
xixuan44b55452016-09-06 22:35:56380 else:
381 return self._ctx.LS(path)
382
383 def _GetLatestVersionFromGsDir(self, path, list_subdirectory=False,
384 with_release=True):
385 """Returns most recent version number found in a google storage directory.
386
387 This lists out the contents of the given GS bucket or regex to GS buckets,
388 and tries to grab the newest version found in the directory names.
389
390 Args:
391 path: directory location on google storage to check.
392 list_subdirectory: whether to only list subdirectory for |path|.
393 with_release: whether versions include a release milestone (e.g. R12).
394
395 Returns:
396 The most recent version number found.
397 """
398 list_result = self._LS(path, list_subdirectory=list_subdirectory)
399 dir_names = [os.path.basename(p.rstrip('/')) for p in list_result]
400 try:
401 filter_re = re.compile(devserver_constants.VERSION_RE if with_release
402 else devserver_constants.VERSION)
403 versions = filter(filter_re.match, dir_names)
404 latest_version = max(versions, key=distutils.version.LooseVersion)
405 except ValueError:
406 raise gs.GSContextException(
407 'Failed to find most recent builds at %s' % path)
408
409 return latest_version
410
Gilad Arnold896c6d82015-03-13 23:20:29411 def _LookupChannel(self, board, suffix, channel='stable',
412 image_dir=None):
joychenf8f07e22013-07-13 00:45:51413 """Check the channel folder for the version number of interest."""
joychen121fc9b2013-08-02 21:30:30414 # Get all names in channel dir. Get 10 highest directories by version.
joychen7df67f72013-07-18 21:21:12415 _Log("Checking channel '%s' for latest '%s' image", channel, board)
Yu-Ju Hongc54658c2014-01-22 17:18:07416 # Due to historical reasons, gs://chromeos-releases uses
417 # daisy-spring as opposed to the board name daisy_spring. Convert
xixuan44b55452016-09-06 22:35:56418 # he board name for the lookup.
Yu-Ju Hongc54658c2014-01-22 17:18:07419 channel_dir = devserver_constants.GS_CHANNEL_DIR % {
420 'channel':channel,
421 'board':re.sub('_', '-', board)}
xixuan44b55452016-09-06 22:35:56422 latest_version = self._GetLatestVersionFromGsDir(channel_dir,
423 with_release=False)
joychenf8f07e22013-07-13 00:45:51424
joychen121fc9b2013-08-02 21:30:30425 # Figure out release number from the version number.
joychenc3944cb2013-08-19 17:42:07426 image_url = devserver_constants.IMAGE_DIR % {
Gilad Arnold896c6d82015-03-13 23:20:29427 'board': board,
428 'suffix': suffix,
429 'version': 'R*' + latest_version}
Simran Basi99e63c02014-05-20 17:39:52430 image_dir = XBuddy._ResolveImageDir(image_dir)
431 gs_url = os.path.join(image_dir, image_url)
joychenf8f07e22013-07-13 00:45:51432
433 # There should only be one match on cros-image-archive.
xixuan44b55452016-09-06 22:35:56434 full_version = self._GetLatestVersionFromGsDir(gs_url,
435 list_subdirectory=True)
joychenf8f07e22013-07-13 00:45:51436
Gilad Arnold896c6d82015-03-13 23:20:29437 return devserver_constants.IMAGE_DIR % {'board': board,
438 'suffix': suffix,
439 'version': full_version}
joychenf8f07e22013-07-13 00:45:51440
Gilad Arnold896c6d82015-03-13 23:20:29441 def _LookupVersion(self, board, suffix, version):
joychenf8f07e22013-07-13 00:45:51442 """Search GS image releases for the highest match to a version prefix."""
joychen121fc9b2013-08-02 21:30:30443 # Build the pattern for GS to match.
joychen7df67f72013-07-18 21:21:12444 _Log("Checking gs for latest '%s' image with prefix '%s'", board, version)
Gilad Arnold896c6d82015-03-13 23:20:29445 image_url = devserver_constants.IMAGE_DIR % {'board': board,
446 'suffix': suffix,
447 'version': version + '*'}
joychenf8f07e22013-07-13 00:45:51448 image_dir = os.path.join(devserver_constants.GS_IMAGE_DIR, image_url)
449
joychen121fc9b2013-08-02 21:30:30450 # Grab the newest version of the ones matched.
xixuan44b55452016-09-06 22:35:56451 full_version = self._GetLatestVersionFromGsDir(image_dir,
452 list_subdirectory=True)
Gilad Arnold896c6d82015-03-13 23:20:29453 return devserver_constants.IMAGE_DIR % {'board': board,
454 'suffix': suffix,
455 'version': full_version}
joychenf8f07e22013-07-13 00:45:51456
Gilad Arnold896c6d82015-03-13 23:20:29457 def _RemoteBuildId(self, board, suffix, version):
Chris Sosaea734d92013-10-11 18:28:58458 """Returns the remote build_id for the given board and version.
459
460 Raises:
461 XBuddyException: If we failed to resolve the version to a valid build_id.
462 """
Gilad Arnold896c6d82015-03-13 23:20:29463 build_id_as_is = devserver_constants.IMAGE_DIR % {'board': board,
464 'suffix': '',
465 'version': version}
466 build_id_suffix = devserver_constants.IMAGE_DIR % {'board': board,
467 'suffix': suffix,
468 'version': version}
Chris Sosaea734d92013-10-11 18:28:58469 # Return the first path that exists. We assume that what the user typed
470 # is better than with a default suffix added i.e. x86-generic/blah is
471 # more valuable than x86-generic-release/blah.
Gilad Arnold896c6d82015-03-13 23:20:29472 for build_id in build_id_as_is, build_id_suffix:
Chris Sosaea734d92013-10-11 18:28:58473 try:
xixuan44b55452016-09-06 22:35:56474 version = self._ctx.LS(
475 '%s/%s' % (devserver_constants.GS_IMAGE_DIR, build_id))
Chris Sosaea734d92013-10-11 18:28:58476 return build_id
xixuan44b55452016-09-06 22:35:56477 except (gs.GSCommandError, gs.GSContextException, gs.GSNoSuchKey):
Chris Sosaea734d92013-10-11 18:28:58478 continue
Gilad Arnold5f46d8e2015-02-19 20:17:55479
480 raise XBuddyException('Could not find remote build_id for %s %s' % (
481 board, version))
Chris Sosaea734d92013-10-11 18:28:58482
Gilad Arnold896c6d82015-03-13 23:20:29483 def _ResolveBuildVersion(self, board, suffix, base_version):
Gilad Arnold869e8ab2015-02-20 07:34:49484 """Check LATEST-<base_version> and returns a full build version."""
485 _Log('Checking gs for full version for %s of %s', base_version, board)
486 # TODO(garnold) We might want to accommodate version prefixes and pick the
487 # most recent found, as done in _LookupVersion().
488 latest_addr = (devserver_constants.GS_LATEST_BASE_VERSION %
489 {'image_dir': devserver_constants.GS_IMAGE_DIR,
490 'board': board,
Gilad Arnold896c6d82015-03-13 23:20:29491 'suffix': suffix,
Gilad Arnold869e8ab2015-02-20 07:34:49492 'base_version': base_version})
Gilad Arnold869e8ab2015-02-20 07:34:49493 # Full release + version is in the LATEST file.
xixuan44b55452016-09-06 22:35:56494 return self._ctx.Cat(latest_addr)
Gilad Arnold869e8ab2015-02-20 07:34:49495
Luis Hector Chavezdca9dd72018-06-12 19:56:30496 def _ResolveVersionToBuildIdAndChannel(self, board, suffix, version,
497 image_dir=None):
joychen121fc9b2013-08-02 21:30:30498 """Handle version aliases for remote payloads in GS.
joychen3cb228e2013-06-12 19:13:13499
500 Args:
501 board: as specified in the original call. (i.e. x86-generic, parrot)
Gilad Arnold896c6d82015-03-13 23:20:29502 suffix: The location suffix, to be added to board name.
joychen3cb228e2013-06-12 19:13:13503 version: as entered in the original call. can be
504 {TBD, 0. some custom alias as defined in a config file}
Ningning Xiab2a1af52016-04-22 18:14:42505 1. fully qualified build version.
Gilad Arnold869e8ab2015-02-20 07:34:49506 2. latest
507 3. latest-{channel}
508 4. latest-official-{board suffix}
509 5. version prefix (i.e. RX-Y.X, RX-Y, RX)
Simran Basi99e63c02014-05-20 17:39:52510 image_dir: image directory to check in Google Storage. If none,
511 the default bucket is used.
joychen3cb228e2013-06-12 19:13:13512
513 Returns:
Luis Hector Chavezdca9dd72018-06-12 19:56:30514 Tuple of (Location where the image dir is actually found on GS (build_id),
515 best guess for the channel).
joychen3cb228e2013-06-12 19:13:13516
Chris Sosaea734d92013-10-11 18:28:58517 Raises:
518 XBuddyException: If we failed to resolve the version to a valid url.
joychen3cb228e2013-06-12 19:13:13519 """
joychenf8f07e22013-07-13 00:45:51520 # Only the last segment of the alias is variable relative to the rest.
521 version_tuple = version.rsplit('-', 1)
joychen3cb228e2013-06-12 19:13:13522
joychenf8f07e22013-07-13 00:45:51523 if re.match(devserver_constants.VERSION_RE, version):
Luis Hector Chavezdca9dd72018-06-12 19:56:30524 return self._RemoteBuildId(board, suffix, version), None
Gilad Arnold869e8ab2015-02-20 07:34:49525 elif re.match(devserver_constants.VERSION, version):
Ningning Xiab2a1af52016-04-22 18:14:42526 raise XBuddyException('\'%s\' is not valid. Should provide the fully '
527 'qualified version with a version prefix \'RX-\' '
528 'due to crbug.com/585914' % version)
joychenf8f07e22013-07-13 00:45:51529 elif version == LATEST_OFFICIAL:
530 # latest-official --> LATEST build in board-release
Luis Hector Chavezdca9dd72018-06-12 19:56:30531 return self._LookupOfficial(board, suffix, image_dir=image_dir), None
joychenf8f07e22013-07-13 00:45:51532 elif version_tuple[0] == LATEST_OFFICIAL:
533 # latest-official-{suffix} --> LATEST build in board-{suffix}
Gilad Arnold896c6d82015-03-13 23:20:29534 return self._LookupOfficial(board, version_tuple[1],
Luis Hector Chavezdca9dd72018-06-12 19:56:30535 image_dir=image_dir), None
joychenf8f07e22013-07-13 00:45:51536 elif version == LATEST:
537 # latest --> latest build on stable channel
Luis Hector Chavezdca9dd72018-06-12 19:56:30538 return self._LookupChannel(board, suffix, image_dir=image_dir), 'stable'
joychenf8f07e22013-07-13 00:45:51539 elif version_tuple[0] == LATEST:
540 if re.match(devserver_constants.VERSION_RE, version_tuple[1]):
541 # latest-R* --> most recent qualifying build
Luis Hector Chavezdca9dd72018-06-12 19:56:30542 return self._LookupVersion(board, suffix, version_tuple[1]), None
joychenf8f07e22013-07-13 00:45:51543 else:
544 # latest-{channel} --> latest build within that channel
Gilad Arnold896c6d82015-03-13 23:20:29545 return self._LookupChannel(board, suffix, channel=version_tuple[1],
Luis Hector Chavezdca9dd72018-06-12 19:56:30546 image_dir=image_dir), version_tuple[1]
joychen3cb228e2013-06-12 19:13:13547 else:
548 # The given version doesn't match any known patterns.
joychen921e1fb2013-06-28 18:12:20549 raise XBuddyException("Version %s unknown. Can't find on GS." % version)
joychen3cb228e2013-06-12 19:13:13550
joychen5260b9a2013-07-16 21:48:01551 @staticmethod
552 def _Symlink(link, target):
553 """Symlinks link to target, and removes whatever link was there before."""
554 _Log("Linking to %s from %s", link, target)
555 if os.path.lexists(link):
556 os.unlink(link)
557 os.symlink(target, link)
558
joychen121fc9b2013-08-02 21:30:30559 def _GetLatestLocalVersion(self, board):
joychen921e1fb2013-06-28 18:12:20560 """Get the version of the latest image built for board by build_image
561
562 Updates the symlink reference within the xBuddy static dir to point to
563 the real image dir in the local /build/images directory.
564
565 Args:
joychenc3944cb2013-08-19 17:42:07566 board: board that image was built for.
joychen921e1fb2013-06-28 18:12:20567
568 Returns:
joychen121fc9b2013-08-02 21:30:30569 The discovered version of the image.
joychenc3944cb2013-08-19 17:42:07570
571 Raises:
572 XBuddyException if neither test nor dev image was found in latest built
573 directory.
joychen3cb228e2013-06-12 19:13:13574 """
joychen921e1fb2013-06-28 18:12:20575 latest_local_dir = self.GetLatestImageDir(board)
joychenb0dfe552013-07-30 17:02:06576 if not latest_local_dir or not os.path.exists(latest_local_dir):
joychen921e1fb2013-06-28 18:12:20577 raise XBuddyException('No builds found for %s. Did you run build_image?' %
578 board)
579
joychen121fc9b2013-08-02 21:30:30580 # Assume that the version number is the name of the directory.
joychenc3944cb2013-08-19 17:42:07581 return os.path.basename(latest_local_dir.rstrip('/'))
joychen921e1fb2013-06-28 18:12:20582
joychenc3944cb2013-08-19 17:42:07583 @staticmethod
584 def _FindAny(local_dir):
585 """Returns the image_type for ANY given the local_dir."""
joychenc3944cb2013-08-19 17:42:07586 test_image = os.path.join(local_dir, devserver_constants.TEST_IMAGE_FILE)
Yu-Ju Hongc23c79b2014-03-17 19:40:33587 dev_image = os.path.join(local_dir, devserver_constants.IMAGE_FILE)
588 # Prioritize test images over dev images.
joychenc3944cb2013-08-19 17:42:07589 if os.path.exists(test_image):
590 return 'test'
591
Yu-Ju Hongc23c79b2014-03-17 19:40:33592 if os.path.exists(dev_image):
593 return 'dev'
594
joychenc3944cb2013-08-19 17:42:07595 raise XBuddyException('No images found in %s' % local_dir)
596
597 @staticmethod
Gilad Arnoldd04fcab2015-02-19 20:00:45598 def _InterpretPath(path, default_board=None, default_version=None):
joychen121fc9b2013-08-02 21:30:30599 """Split and return the pieces of an xBuddy path name
joychen921e1fb2013-06-28 18:12:20600
joychen121fc9b2013-08-02 21:30:30601 Args:
602 path: the path xBuddy Get was called with.
Chris Sosa0eecf96e2014-02-03 22:14:39603 default_board: board to use in case board isn't in path.
Gilad Arnoldd04fcab2015-02-19 20:00:45604 default_version: Version to use in case version isn't in path.
joychen3cb228e2013-06-12 19:13:13605
Yu-Ju Hongc54658c2014-01-22 17:18:07606 Returns:
Chris Sosa75490802013-10-01 00:21:45607 tuple of (image_type, board, version, whether the path is local)
joychen3cb228e2013-06-12 19:13:13608
609 Raises:
610 XBuddyException: if the path can't be resolved into valid components
611 """
joychen121fc9b2013-08-02 21:30:30612 path_list = filter(None, path.split('/'))
joychen7df67f72013-07-18 21:21:12613
Chris Sosa0eecf96e2014-02-03 22:14:39614 # Do the stuff that is well known first. We know that if paths have a
615 # image_type, it must be one of the GS/LOCAL aliases and it must be at the
616 # end. Similarly, local/remote are well-known and must start the path list.
617 is_local = True
618 if path_list and path_list[0] in (REMOTE, LOCAL):
619 is_local = (path_list.pop(0) == LOCAL)
joychen7df67f72013-07-18 21:21:12620
Chris Sosa0eecf96e2014-02-03 22:14:39621 # Default image type is determined by remote vs. local.
622 if is_local:
623 image_type = ANY
624 else:
625 image_type = TEST
joychen7df67f72013-07-18 21:21:12626
Chris Sosa0eecf96e2014-02-03 22:14:39627 if path_list and path_list[-1] in GS_ALIASES + LOCAL_ALIASES:
628 image_type = path_list.pop(-1)
joychen3cb228e2013-06-12 19:13:13629
Chris Sosa0eecf96e2014-02-03 22:14:39630 # Now for the tricky part. We don't actually know at this point if the rest
631 # of the path is just a board | version (like R33-2341.0.0) or just a board
632 # or just a version. So we do our best to do the right thing.
633 board = default_board
Gilad Arnoldd04fcab2015-02-19 20:00:45634 version = default_version or LATEST
Chris Sosa0eecf96e2014-02-03 22:14:39635 if len(path_list) == 1:
636 path = path_list.pop(0)
Gilad Arnoldd04fcab2015-02-19 20:00:45637 # Treat this as a version if it's one we know (contains default or
638 # latest), or we were given an actual default board.
639 if default_version in path or LATEST in path or default_board is not None:
Chris Sosa0eecf96e2014-02-03 22:14:39640 version = path
joychen7df67f72013-07-18 21:21:12641 else:
Chris Sosa0eecf96e2014-02-03 22:14:39642 board = path
joychen7df67f72013-07-18 21:21:12643
Chris Sosa0eecf96e2014-02-03 22:14:39644 elif len(path_list) == 2:
645 # Assumes board/version.
646 board = path_list.pop(0)
647 version = path_list.pop(0)
648
649 if path_list:
650 raise XBuddyException("Path isn't valid. Could not figure out how to "
651 "parse remaining components: %s." % path_list)
652
653 _Log("Get artifact '%s' with board %s and version %s'. Locally? %s",
joychen7df67f72013-07-18 21:21:12654 image_type, board, version, is_local)
655
656 return image_type, board, version, is_local
joychen3cb228e2013-06-12 19:13:13657
joychen921e1fb2013-06-28 18:12:20658 def _SyncRegistryWithBuildImages(self):
Gilad Arnold5f46d8e2015-02-19 20:17:55659 """Crawl images_dir for build_ids of images generated from build_image.
joychen5260b9a2013-07-16 21:48:01660
661 This will find images and symlink them in xBuddy's static dir so that
662 xBuddy's cache can serve them.
663 If xBuddy's _manage_builds option is on, then a timestamp will also be
664 generated, and xBuddy will clear them from the directory they are in, as
665 necessary.
666 """
Yu-Ju Hong235d1b52014-04-16 18:01:47667 if not os.path.isdir(self.images_dir):
668 # Skip syncing if images_dir does not exist.
669 _Log('Cannot find %s; skip syncing image registry.', self.images_dir)
670 return
671
joychen921e1fb2013-06-28 18:12:20672 build_ids = []
673 for b in os.listdir(self.images_dir):
Mike Frysingera2c24252017-11-29 00:14:45674 # Ignore random files in the build dir.
675 board_dir = os.path.join(self.images_dir, b)
676 if not os.path.isdir(board_dir):
677 continue
678
joychen5260b9a2013-07-16 21:48:01679 # Ensure we have directories to track all boards in build/images
680 common_util.MkDirP(os.path.join(self.static_dir, b))
joychen921e1fb2013-06-28 18:12:20681 build_ids.extend(['/'.join([b, v]) for v
joychenc3944cb2013-08-19 17:42:07682 in os.listdir(board_dir) if not v == LATEST])
joychen921e1fb2013-06-28 18:12:20683
joychen121fc9b2013-08-02 21:30:30684 # Symlink undiscovered images, and update timestamps if manage_builds is on.
joychen5260b9a2013-07-16 21:48:01685 for build_id in build_ids:
686 link = os.path.join(self.static_dir, build_id)
687 target = os.path.join(self.images_dir, build_id)
688 XBuddy._Symlink(link, target)
689 if self._manage_builds:
690 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen921e1fb2013-06-28 18:12:20691
692 def _ListBuildTimes(self):
Gilad Arnold5f46d8e2015-02-19 20:17:55693 """Returns the currently cached builds and their last access timestamp.
joychen3cb228e2013-06-12 19:13:13694
695 Returns:
696 list of tuples that matches xBuddy build/version to timestamps in long
697 """
joychen121fc9b2013-08-02 21:30:30698 # Update currently cached builds.
joychen3cb228e2013-06-12 19:13:13699 build_dict = {}
700
joychen7df67f72013-07-18 21:21:12701 for f in os.listdir(self._timestamp_folder):
joychen3cb228e2013-06-12 19:13:13702 last_accessed = os.path.getmtime(os.path.join(self._timestamp_folder, f))
703 build_id = Timestamp.TimestampToBuild(f)
joychenc3944cb2013-08-19 17:42:07704 stale_time = datetime.timedelta(seconds=(time.time() - last_accessed))
joychen921e1fb2013-06-28 18:12:20705 build_dict[build_id] = stale_time
joychen3cb228e2013-06-12 19:13:13706 return_tup = sorted(build_dict.iteritems(), key=operator.itemgetter(1))
707 return return_tup
708
Luis Hector Chavezdca9dd72018-06-12 19:56:30709 def _Download(self, gs_url, artifacts, build_id):
Chris Sosa75490802013-10-01 00:21:45710 """Download the artifacts from the given gs_url.
711
712 Raises:
713 build_artifact.ArtifactDownloadError: If we failed to download the
714 artifact.
715 """
joychen3cb228e2013-06-12 19:13:13716 with XBuddy._staging_thread_count_lock:
717 XBuddy._staging_thread_count += 1
718 try:
Chris Sosa75490802013-10-01 00:21:45719 _Log("Downloading %s from %s", artifacts, gs_url)
Luis Hector Chavezdca9dd72018-06-12 19:56:30720 dl = downloader.GoogleStorageDownloader(self.static_dir, gs_url, build_id)
Gabe Black3b567202015-09-23 21:07:59721 factory = build_artifact.ChromeOSArtifactFactory(
722 dl.GetBuildDir(), artifacts, [], dl.GetBuild())
723 dl.Download(factory)
joychen3cb228e2013-06-12 19:13:13724 finally:
725 with XBuddy._staging_thread_count_lock:
726 XBuddy._staging_thread_count -= 1
727
Chris Sosa75490802013-10-01 00:21:45728 def CleanCache(self):
joychen562699a2013-08-13 22:22:14729 """Delete all builds besides the newest N builds"""
joychen121fc9b2013-08-02 21:30:30730 if not self._manage_builds:
731 return
joychen921e1fb2013-06-28 18:12:20732 cached_builds = [e[0] for e in self._ListBuildTimes()]
joychen3cb228e2013-06-12 19:13:13733 _Log('In cache now: %s', cached_builds)
734
joychen562699a2013-08-13 22:22:14735 for b in range(self._Capacity(), len(cached_builds)):
joychen3cb228e2013-06-12 19:13:13736 b_path = cached_builds[b]
joychen7df67f72013-07-18 21:21:12737 _Log("Clearing '%s' from cache", b_path)
joychen3cb228e2013-06-12 19:13:13738
739 time_file = os.path.join(self._timestamp_folder,
740 Timestamp.BuildToTimestamp(b_path))
joychen921e1fb2013-06-28 18:12:20741 os.unlink(time_file)
742 clear_dir = os.path.join(self.static_dir, b_path)
joychen3cb228e2013-06-12 19:13:13743 try:
joychen121fc9b2013-08-02 21:30:30744 # Handle symlinks, in the case of links to local builds if enabled.
745 if os.path.islink(clear_dir):
joychen5260b9a2013-07-16 21:48:01746 target = os.readlink(clear_dir)
747 _Log('Deleting locally built image at %s', target)
joychen921e1fb2013-06-28 18:12:20748
749 os.unlink(clear_dir)
joychen5260b9a2013-07-16 21:48:01750 if os.path.exists(target):
joychen921e1fb2013-06-28 18:12:20751 shutil.rmtree(target)
752 elif os.path.exists(clear_dir):
joychen5260b9a2013-07-16 21:48:01753 _Log('Deleting downloaded image at %s', clear_dir)
joychen3cb228e2013-06-12 19:13:13754 shutil.rmtree(clear_dir)
joychen921e1fb2013-06-28 18:12:20755
joychen121fc9b2013-08-02 21:30:30756 except Exception as err:
757 raise XBuddyException('Failed to clear %s: %s' % (clear_dir, err))
joychen3cb228e2013-06-12 19:13:13758
Luis Hector Chavezdca9dd72018-06-12 19:56:30759 def _TranslateSignedGSUrl(self, build_id, channel=None):
760 """Translate the GS URL to be able to find signed images.
761
762 Args:
763 build_id: Path to the image or update directory on the devserver or
764 in Google Storage. e.g. 'x86-generic/R26-4000.0.0'
765 channel: The channel for the image. If none, it tries to guess it in
766 order of stability.
767
768 Returns:
769 The GS URL for the directory where the signed image can be found.
770
771 Raises:
772 build_artifact.ArtifactDownloadError: If we failed to download the
773 artifact.
774 """
775 match = re.match(r'^([^/]+?)(?:-release)?/R\d+-(.*)$', build_id)
776
777 channels = []
778 if channel:
779 channels.append(channel)
780 else:
781 # Attempt to enumerate all channels, in order of stability.
782 channels.extend(devserver_constants.CHANNELS[::-1])
783
784 for channel in channels:
785 image_dir = devserver_constants.GS_CHANNEL_DIR % {
786 'channel': channel,
787 'board': match.group(1),
788 }
789 gs_url = os.path.join(image_dir, match.group(2))
790 try:
791 self._LS(gs_url)
792 return gs_url
793 except gs.GSNoSuchKey:
794 continue
795 raise build_artifact.ArtifactDownloadError(
796 'Could not find signed image URL for %s in Google Storage' %
797 build_id)
798
799 def _GetFromGS(self, build_id, image_type, image_dir=None, channel=None):
Chris Sosa75490802013-10-01 00:21:45800 """Check if the artifact is available locally. Download from GS if not.
801
Simran Basi99e63c02014-05-20 17:39:52802 Args:
803 build_id: Path to the image or update directory on the devserver or
804 in Google Storage. e.g. 'x86-generic/R26-4000.0.0'
805 image_type: Image type to download. Look at aliases at top of file for
806 options.
807 image_dir: Google Storage image archive to search in if requesting a
808 remote artifact. If none uses the default bucket.
Luis Hector Chavezdca9dd72018-06-12 19:56:30809 channel: The channel for the image. If none, it tries to guess it in
810 order of stability.
Simran Basi99e63c02014-05-20 17:39:52811
Chris Sosa75490802013-10-01 00:21:45812 Raises:
813 build_artifact.ArtifactDownloadError: If we failed to download the
814 artifact.
815 """
joychen121fc9b2013-08-02 21:30:30816 # Stage image if not found in cache.
joychen921e1fb2013-06-28 18:12:20817 file_name = GS_ALIAS_TO_FILENAME[image_type]
joychen346531c2013-07-24 23:55:56818 file_loc = os.path.join(self.static_dir, build_id, file_name)
819 cached = os.path.exists(file_loc)
820
joychen921e1fb2013-06-28 18:12:20821 if not cached:
Chris Sosa75490802013-10-01 00:21:45822 artifact = GS_ALIAS_TO_ARTIFACT[image_type]
Luis Hector Chavezdca9dd72018-06-12 19:56:30823 if image_type == SIGNED:
824 gs_url = self._TranslateSignedGSUrl(build_id, channel=channel)
825 else:
826 image_dir = XBuddy._ResolveImageDir(image_dir)
827 gs_url = os.path.join(image_dir, build_id)
828 self._Download(gs_url, [artifact], build_id)
joychen921e1fb2013-06-28 18:12:20829 else:
830 _Log('Image already cached.')
831
Gilad Arnoldd04fcab2015-02-19 20:00:45832 def _GetArtifact(self, path_list, board=None, version=None,
833 lookup_only=False, image_dir=None):
joychen346531c2013-07-24 23:55:56834 """Interpret an xBuddy path and return directory/file_name to resource.
835
Chris Sosa75490802013-10-01 00:21:45836 Note board can be passed that in but by default if self._board is set,
837 that is used rather than board.
838
Simran Basi99e63c02014-05-20 17:39:52839 Args:
840 path_list: [board, version, alias] as split from the xbuddy call url.
Gilad Arnoldd04fcab2015-02-19 20:00:45841 board: Board whos artifacts we are looking for. Only used if no board was
842 given during XBuddy initialization.
843 version: Version whose artifacts we are looking for. Used if no version
844 was given during XBuddy initialization. If None, defers to LATEST.
Simran Basi99e63c02014-05-20 17:39:52845 lookup_only: If true just look up the artifact, if False stage it on
846 the devserver as well.
847 image_dir: Google Storage image archive to search in if requesting a
848 remote artifact. If none uses the default bucket.
849
joychen346531c2013-07-24 23:55:56850 Returns:
Simran Basi99e63c02014-05-20 17:39:52851 build_id: Path to the image or update directory on the devserver or
852 in Google Storage. e.g. 'x86-generic/R26-4000.0.0'
853 file_name: of the artifact in the build_id directory.
joychen346531c2013-07-24 23:55:56854
855 Raises:
joychen121fc9b2013-08-02 21:30:30856 XBuddyException: if the path could not be translated
Chris Sosa75490802013-10-01 00:21:45857 build_artifact.ArtifactDownloadError: if we failed to download the
858 artifact.
joychen346531c2013-07-24 23:55:56859 """
joychen121fc9b2013-08-02 21:30:30860 path = '/'.join(path_list)
Chris Sosa0eecf96e2014-02-03 22:14:39861 default_board = self._board if self._board else board
Gilad Arnoldd04fcab2015-02-19 20:00:45862 default_version = self._version or version or LATEST
joychenb0dfe552013-07-30 17:02:06863 # Rewrite the path if there is an appropriate default.
Gilad Arnold38e828c2015-04-24 20:52:07864 path, suffix = self.LookupAlias(path, board=default_board,
865 version=default_version)
joychen121fc9b2013-08-02 21:30:30866 # Parse the path.
Chris Sosa0eecf96e2014-02-03 22:14:39867 image_type, board, version, is_local = self._InterpretPath(
Gilad Arnoldd04fcab2015-02-19 20:00:45868 path, default_board, default_version)
joychen7df67f72013-07-18 21:21:12869 if is_local:
joychen121fc9b2013-08-02 21:30:30870 # Get a local image.
joychen7df67f72013-07-18 21:21:12871 if version == LATEST:
joychen121fc9b2013-08-02 21:30:30872 # Get the latest local image for the given board.
873 version = self._GetLatestLocalVersion(board)
joychen7df67f72013-07-18 21:21:12874
joychenc3944cb2013-08-19 17:42:07875 build_id = os.path.join(board, version)
876 artifact_dir = os.path.join(self.static_dir, build_id)
877 if image_type == ANY:
878 image_type = self._FindAny(artifact_dir)
joychen121fc9b2013-08-02 21:30:30879
joychenc3944cb2013-08-19 17:42:07880 file_name = LOCAL_ALIAS_TO_FILENAME[image_type]
881 artifact_path = os.path.join(artifact_dir, file_name)
882 if not os.path.exists(artifact_path):
883 raise XBuddyException('Local %s artifact not in static_dir at %s' %
884 (image_type, artifact_path))
joychen121fc9b2013-08-02 21:30:30885
joychen921e1fb2013-06-28 18:12:20886 else:
joychen121fc9b2013-08-02 21:30:30887 # Get a remote image.
joychen921e1fb2013-06-28 18:12:20888 if image_type not in GS_ALIASES:
joychen7df67f72013-07-18 21:21:12889 raise XBuddyException('Bad remote image type: %s. Use one of: %s' %
joychen921e1fb2013-06-28 18:12:20890 (image_type, GS_ALIASES))
Luis Hector Chavezdca9dd72018-06-12 19:56:30891 build_id, channel = self._ResolveVersionToBuildIdAndChannel(
892 board, suffix, version, image_dir=image_dir)
Chris Sosa75490802013-10-01 00:21:45893 _Log('Resolved version %s to %s.', version, build_id)
894 file_name = GS_ALIAS_TO_FILENAME[image_type]
895 if not lookup_only:
Luis Hector Chavezdca9dd72018-06-12 19:56:30896 self._GetFromGS(build_id, image_type, image_dir=image_dir,
897 channel=channel)
joychenf8f07e22013-07-13 00:45:51898
joychenc3944cb2013-08-19 17:42:07899 return build_id, file_name
joychen3cb228e2013-06-12 19:13:13900
901 ############################ BEGIN PUBLIC METHODS
902
903 def List(self):
904 """Lists the currently available images & time since last access."""
joychen921e1fb2013-06-28 18:12:20905 self._SyncRegistryWithBuildImages()
906 builds = self._ListBuildTimes()
907 return_string = ''
908 for build, timestamp in builds:
909 return_string += '<b>' + build + '</b> '
910 return_string += '(time since last access: ' + str(timestamp) + ')<br>'
911 return return_string
joychen3cb228e2013-06-12 19:13:13912
913 def Capacity(self):
914 """Returns the number of images cached by xBuddy."""
joychen562699a2013-08-13 22:22:14915 return str(self._Capacity())
joychen3cb228e2013-06-12 19:13:13916
Gilad Arnoldd04fcab2015-02-19 20:00:45917 def Translate(self, path_list, board=None, version=None, image_dir=None):
joychen346531c2013-07-24 23:55:56918 """Translates an xBuddy path to a real path to artifact if it exists.
919
joychen121fc9b2013-08-02 21:30:30920 Equivalent to the Get call, minus downloading and updating timestamps,
joychen346531c2013-07-24 23:55:56921
Simran Basi99e63c02014-05-20 17:39:52922 Args:
923 path_list: [board, version, alias] as split from the xbuddy call url.
924 board: Board whos artifacts we are looking for. If None, use the board
925 XBuddy was initialized to use.
Gilad Arnoldd04fcab2015-02-19 20:00:45926 version: Version whose artifacts we are looking for. If None, use the
927 version XBuddy was initialized with, or LATEST.
Simran Basi99e63c02014-05-20 17:39:52928 image_dir: image directory to check in Google Storage. If none,
929 the default bucket is used.
930
joychen7c2054a2013-07-25 18:14:07931 Returns:
joychen121fc9b2013-08-02 21:30:30932 build_id: Path to the image or update directory on the devserver.
933 e.g. 'x86-generic/R26-4000.0.0'
934 The returned path is always the path to the directory within
935 static_dir, so it is always the build_id of the image.
936 file_name: The file name of the artifact. Can take any of the file
937 values in devserver_constants.
938 e.g. 'chromiumos_test_image.bin' or 'update.gz' if the path list
939 specified 'test' or 'full_payload' artifacts, respectively.
joychen7c2054a2013-07-25 18:14:07940
joychen121fc9b2013-08-02 21:30:30941 Raises:
942 XBuddyException: if the path couldn't be translated
joychen346531c2013-07-24 23:55:56943 """
944 self._SyncRegistryWithBuildImages()
Chris Sosa75490802013-10-01 00:21:45945 build_id, file_name = self._GetArtifact(path_list, board=board,
Gilad Arnoldd04fcab2015-02-19 20:00:45946 version=version,
Simran Basi99e63c02014-05-20 17:39:52947 lookup_only=True,
948 image_dir=image_dir)
joychen346531c2013-07-24 23:55:56949
joychen121fc9b2013-08-02 21:30:30950 _Log('Returning path to payload: %s/%s', build_id, file_name)
951 return build_id, file_name
joychen346531c2013-07-24 23:55:56952
Yu-Ju Hong1bdb7a92014-04-10 23:02:11953 def StageTestArtifactsForUpdate(self, path_list):
Chris Sosa75490802013-10-01 00:21:45954 """Stages test artifacts for update and returns build_id.
955
956 Raises:
957 XBuddyException: if the path could not be translated
958 build_artifact.ArtifactDownloadError: if we failed to download the test
959 artifacts.
960 """
961 build_id, file_name = self.Translate(path_list)
962 if file_name == devserver_constants.TEST_IMAGE_FILE:
963 gs_url = os.path.join(devserver_constants.GS_IMAGE_DIR,
964 build_id)
965 artifacts = [FULL, STATEFUL]
Luis Hector Chavezdca9dd72018-06-12 19:56:30966 self._Download(gs_url, artifacts, build_id)
Chris Sosa75490802013-10-01 00:21:45967 return build_id
968
Simran Basi99e63c02014-05-20 17:39:52969 def Get(self, path_list, image_dir=None):
joychen921e1fb2013-06-28 18:12:20970 """The full xBuddy call, returns resource specified by path_list.
joychen3cb228e2013-06-12 19:13:13971
972 Please see devserver.py:xbuddy for full documentation.
joychen121fc9b2013-08-02 21:30:30973
joychen3cb228e2013-06-12 19:13:13974 Args:
Simran Basi99e63c02014-05-20 17:39:52975 path_list: [board, version, alias] as split from the xbuddy call url.
976 image_dir: image directory to check in Google Storage. If none,
977 the default bucket is used.
joychen3cb228e2013-06-12 19:13:13978
979 Returns:
joychen121fc9b2013-08-02 21:30:30980 build_id: Path to the image or update directory on the devserver.
Simran Basi99e63c02014-05-20 17:39:52981 e.g. 'x86-generic/R26-4000.0.0'
982 The returned path is always the path to the directory within
983 static_dir, so it is always the build_id of the image.
joychen121fc9b2013-08-02 21:30:30984 file_name: The file name of the artifact. Can take any of the file
Simran Basi99e63c02014-05-20 17:39:52985 values in devserver_constants.
986 e.g. 'chromiumos_test_image.bin' or 'update.gz' if the path list
987 specified 'test' or 'full_payload' artifacts, respectively.
joychen3cb228e2013-06-12 19:13:13988
989 Raises:
Chris Sosa75490802013-10-01 00:21:45990 XBuddyException: if the path could not be translated
991 build_artifact.ArtifactDownloadError: if we failed to download the
992 artifact.
joychen3cb228e2013-06-12 19:13:13993 """
joychen7df67f72013-07-18 21:21:12994 self._SyncRegistryWithBuildImages()
Simran Basi99e63c02014-05-20 17:39:52995 build_id, file_name = self._GetArtifact(path_list, image_dir=image_dir)
joychen921e1fb2013-06-28 18:12:20996 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen3cb228e2013-06-12 19:13:13997 #TODO (joyc): run in sep thread
Chris Sosa75490802013-10-01 00:21:45998 self.CleanCache()
joychen3cb228e2013-06-12 19:13:13999
joychen121fc9b2013-08-02 21:30:301000 _Log('Returning path to payload: %s/%s', build_id, file_name)
1001 return build_id, file_name