blob: b0fa5f5ca4d4d18e9350bcfb4ed0e83218259301 [file] [log] [blame]
joychen3cb228e2013-06-12 12:13:13 -07001# Copyright (c) 2013 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
Chris Sosa0eecf962014-02-03 14:14:39 -08005"""Main module for parsing and interpreting XBuddy paths for the devserver."""
6
Gilad Arnold5f46d8e2015-02-19 12:17:55 -08007from __future__ import print_function
8
Yiming Chenaab488e2014-11-17 14:49:31 -08009import cherrypy
joychen562699a2013-08-13 15:22:14 -070010import ConfigParser
joychen3cb228e2013-06-12 12:13:13 -070011import datetime
xixuan44b55452016-09-06 15:35:56 -070012import distutils.version
joychen3cb228e2013-06-12 12:13:13 -070013import operator
14import os
joychenf8f07e22013-07-12 17:45:51 -070015import re
joychen3cb228e2013-06-12 12:13:13 -070016import shutil
joychenf8f07e22013-07-12 17:45:51 -070017import time
joychen3cb228e2013-06-12 12:13:13 -070018import threading
19
20import artifact_info
Gabe Black3b567202015-09-23 14:07:59 -070021import build_artifact
22import build_util
joychen3cb228e2013-06-12 12:13:13 -070023import common_util
24import devserver_constants
25import downloader
26import log_util
27
xixuan44b55452016-09-06 15:35:56 -070028# Make sure that chromite is available to import.
29import setup_chromite # pylint: disable=unused-import
30
31try:
32 from chromite.lib import gs
33except ImportError as e:
34 gs = None
35
joychen3cb228e2013-06-12 12:13:13 -070036# Module-local log function.
37def _Log(message, *args):
38 return log_util.LogWithTag('XBUDDY', message, *args)
39
joychen562699a2013-08-13 15:22:14 -070040# xBuddy config constants
41CONFIG_FILE = 'xbuddy_config.ini'
42SHADOW_CONFIG_FILE = 'shadow_xbuddy_config.ini'
43PATH_REWRITES = 'PATH_REWRITES'
44GENERAL = 'GENERAL'
Gilad Arnold896c6d82015-03-13 16:20:29 -070045LOCATION_SUFFIXES = 'LOCATION_SUFFIXES'
joychen921e1fb2013-06-28 11:12:20 -070046
Chris Sosac2abc722013-08-26 17:11:22 -070047# Path for shadow config in chroot.
48CHROOT_SHADOW_DIR = '/mnt/host/source/src/platform/dev'
49
joychen25d25972013-07-30 14:54:16 -070050# XBuddy aliases
51TEST = 'test'
52BASE = 'base'
53DEV = 'dev'
54FULL = 'full_payload'
55RECOVERY = 'recovery'
56STATEFUL = 'stateful'
57AUTOTEST = 'autotest'
Mike Frysingera0e6a282016-09-01 17:29:08 -040058FACTORY_SHIM = 'factory_shim'
joychen25d25972013-07-30 14:54:16 -070059
joychen921e1fb2013-06-28 11:12:20 -070060# Local build constants
joychenc3944cb2013-08-19 10:42:07 -070061ANY = "ANY"
joychen7df67f72013-07-18 14:21:12 -070062LATEST = "latest"
63LOCAL = "local"
64REMOTE = "remote"
Chris Sosa75490802013-09-30 17:21:45 -070065
66# TODO(sosa): Fix a lot of assumptions about these aliases. There is too much
67# implicit logic here that's unnecessary. What should be done:
68# 1) Collapse Alias logic to one set of aliases for xbuddy (not local/remote).
69# 2) Do not use zip when creating these dicts. Better to not rely on ordering.
70# 3) Move alias/artifact mapping to a central module rather than having it here.
71# 4) Be explicit when things are missing i.e. no dev images in image.zip.
72
joychen921e1fb2013-06-28 11:12:20 -070073LOCAL_ALIASES = [
Gilad Arnold5f46d8e2015-02-19 12:17:55 -080074 TEST,
75 DEV,
76 BASE,
77 RECOVERY,
Mike Frysingera0e6a282016-09-01 17:29:08 -040078 FACTORY_SHIM,
Gilad Arnold5f46d8e2015-02-19 12:17:55 -080079 FULL,
80 STATEFUL,
81 ANY,
joychen921e1fb2013-06-28 11:12:20 -070082]
83
84LOCAL_FILE_NAMES = [
Gilad Arnold5f46d8e2015-02-19 12:17:55 -080085 devserver_constants.TEST_IMAGE_FILE,
86 devserver_constants.IMAGE_FILE,
87 devserver_constants.BASE_IMAGE_FILE,
88 devserver_constants.RECOVERY_IMAGE_FILE,
Mike Frysingera0e6a282016-09-01 17:29:08 -040089 devserver_constants.FACTORY_SHIM_IMAGE_FILE,
Gilad Arnold5f46d8e2015-02-19 12:17:55 -080090 devserver_constants.UPDATE_FILE,
91 devserver_constants.STATEFUL_FILE,
92 None, # For ANY.
joychen921e1fb2013-06-28 11:12:20 -070093]
94
95LOCAL_ALIAS_TO_FILENAME = dict(zip(LOCAL_ALIASES, LOCAL_FILE_NAMES))
96
97# Google Storage constants
98GS_ALIASES = [
Gilad Arnold5f46d8e2015-02-19 12:17:55 -080099 TEST,
100 BASE,
101 RECOVERY,
Mike Frysingera0e6a282016-09-01 17:29:08 -0400102 FACTORY_SHIM,
Gilad Arnold5f46d8e2015-02-19 12:17:55 -0800103 FULL,
104 STATEFUL,
105 AUTOTEST,
joychen3cb228e2013-06-12 12:13:13 -0700106]
107
joychen921e1fb2013-06-28 11:12:20 -0700108GS_FILE_NAMES = [
Gilad Arnold5f46d8e2015-02-19 12:17:55 -0800109 devserver_constants.TEST_IMAGE_FILE,
110 devserver_constants.BASE_IMAGE_FILE,
111 devserver_constants.RECOVERY_IMAGE_FILE,
Mike Frysingera0e6a282016-09-01 17:29:08 -0400112 devserver_constants.FACTORY_SHIM_IMAGE_FILE,
Gilad Arnold5f46d8e2015-02-19 12:17:55 -0800113 devserver_constants.UPDATE_FILE,
114 devserver_constants.STATEFUL_FILE,
115 devserver_constants.AUTOTEST_DIR,
joychen3cb228e2013-06-12 12:13:13 -0700116]
117
118ARTIFACTS = [
Gilad Arnold5f46d8e2015-02-19 12:17:55 -0800119 artifact_info.TEST_IMAGE,
120 artifact_info.BASE_IMAGE,
121 artifact_info.RECOVERY_IMAGE,
Mike Frysingera0e6a282016-09-01 17:29:08 -0400122 artifact_info.FACTORY_SHIM_IMAGE,
Gilad Arnold5f46d8e2015-02-19 12:17:55 -0800123 artifact_info.FULL_PAYLOAD,
124 artifact_info.STATEFUL_PAYLOAD,
125 artifact_info.AUTOTEST,
joychen3cb228e2013-06-12 12:13:13 -0700126]
127
joychen921e1fb2013-06-28 11:12:20 -0700128GS_ALIAS_TO_FILENAME = dict(zip(GS_ALIASES, GS_FILE_NAMES))
129GS_ALIAS_TO_ARTIFACT = dict(zip(GS_ALIASES, ARTIFACTS))
joychen3cb228e2013-06-12 12:13:13 -0700130
joychen921e1fb2013-06-28 11:12:20 -0700131LATEST_OFFICIAL = "latest-official"
joychen3cb228e2013-06-12 12:13:13 -0700132
Chris Sosaea734d92013-10-11 11:28:58 -0700133RELEASE = "-release"
joychen3cb228e2013-06-12 12:13:13 -0700134
joychen3cb228e2013-06-12 12:13:13 -0700135
136class XBuddyException(Exception):
137 """Exception classes used by this module."""
138 pass
139
140
141# no __init__ method
142#pylint: disable=W0232
Gilad Arnold5f46d8e2015-02-19 12:17:55 -0800143class Timestamp(object):
joychen3cb228e2013-06-12 12:13:13 -0700144 """Class to translate build path strings and timestamp filenames."""
145
146 _TIMESTAMP_DELIMITER = 'SLASH'
147 XBUDDY_TIMESTAMP_DIR = 'xbuddy_UpdateTimestamps'
148
149 @staticmethod
150 def TimestampToBuild(timestamp_filename):
151 return timestamp_filename.replace(Timestamp._TIMESTAMP_DELIMITER, '/')
152
153 @staticmethod
154 def BuildToTimestamp(build_path):
155 return build_path.replace('/', Timestamp._TIMESTAMP_DELIMITER)
joychen921e1fb2013-06-28 11:12:20 -0700156
157 @staticmethod
158 def UpdateTimestamp(timestamp_dir, build_id):
159 """Update timestamp file of build with build_id."""
160 common_util.MkDirP(timestamp_dir)
joychen562699a2013-08-13 15:22:14 -0700161 _Log("Updating timestamp for %s", build_id)
joychen921e1fb2013-06-28 11:12:20 -0700162 time_file = os.path.join(timestamp_dir,
163 Timestamp.BuildToTimestamp(build_id))
164 with file(time_file, 'a'):
165 os.utime(time_file, None)
joychen3cb228e2013-06-12 12:13:13 -0700166#pylint: enable=W0232
167
168
joychen921e1fb2013-06-28 11:12:20 -0700169class XBuddy(build_util.BuildObject):
joychen3cb228e2013-06-12 12:13:13 -0700170 """Class that manages image retrieval and caching by the devserver.
171
172 Image retrieval by xBuddy path:
173 XBuddy accesses images and artifacts that it stores using an xBuddy
174 path of the form: board/version/alias
175 The primary xbuddy.Get call retrieves the correct artifact or url to where
176 the artifacts can be found.
177
178 Image caching:
179 Images and other artifacts are stored identically to how they would have
180 been if devserver's stage rpc was called and the xBuddy cache replaces
181 build versions on a LRU basis. Timestamps are maintained by last accessed
182 times of representative files in the a directory in the static serve
183 directory (XBUDDY_TIMESTAMP_DIR).
184
185 Private class members:
joychen121fc9b2013-08-02 14:30:30 -0700186 _true_values: used for interpreting boolean values
187 _staging_thread_count: track download requests
188 _timestamp_folder: directory with empty files standing in as timestamps
joychen921e1fb2013-06-28 11:12:20 -0700189 for each image currently cached by xBuddy
joychen3cb228e2013-06-12 12:13:13 -0700190 """
191 _true_values = ['true', 't', 'yes', 'y']
192
193 # Number of threads that are staging images.
194 _staging_thread_count = 0
195 # Lock used to lock increasing/decreasing count.
196 _staging_thread_count_lock = threading.Lock()
197
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800198 def __init__(self, manage_builds=False, board=None, version=None,
199 images_dir=None, log_screen=True, **kwargs):
joychen921e1fb2013-06-28 11:12:20 -0700200 super(XBuddy, self).__init__(**kwargs)
joychenb0dfe552013-07-30 10:02:06 -0700201
Yiming Chenaab488e2014-11-17 14:49:31 -0800202 if not log_screen:
203 cherrypy.config.update({'log.screen': False})
204
joychen562699a2013-08-13 15:22:14 -0700205 self.config = self._ReadConfig()
206 self._manage_builds = manage_builds or self._ManageBuilds()
Chris Sosa75490802013-09-30 17:21:45 -0700207 self._board = board
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800208 self._version = version
joychen921e1fb2013-06-28 11:12:20 -0700209 self._timestamp_folder = os.path.join(self.static_dir,
joychen3cb228e2013-06-12 12:13:13 -0700210 Timestamp.XBUDDY_TIMESTAMP_DIR)
Chris Sosa7cd23202013-10-15 17:22:57 -0700211 if images_dir:
212 self.images_dir = images_dir
213 else:
214 self.images_dir = os.path.join(self.GetSourceRoot(), 'src/build/images')
215
xixuan44b55452016-09-06 15:35:56 -0700216 self._ctx = gs.GSContext() if gs else None
217
joychen7df67f72013-07-18 14:21:12 -0700218 common_util.MkDirP(self._timestamp_folder)
joychen3cb228e2013-06-12 12:13:13 -0700219
220 @classmethod
221 def ParseBoolean(cls, boolean_string):
222 """Evaluate a string to a boolean value"""
223 if boolean_string:
224 return boolean_string.lower() in cls._true_values
225 else:
226 return False
227
joychen562699a2013-08-13 15:22:14 -0700228 def _ReadConfig(self):
229 """Read xbuddy config from ini files.
230
231 Reads the base config from xbuddy_config.ini, and then merges in the
232 shadow config from shadow_xbuddy_config.ini
233
234 Returns:
235 The merged configuration.
Yu-Ju Hongc54658c2014-01-22 09:18:07 -0800236
joychen562699a2013-08-13 15:22:14 -0700237 Raises:
238 XBuddyException if the config file is missing.
239 """
240 xbuddy_config = ConfigParser.ConfigParser()
241 config_file = os.path.join(self.devserver_dir, CONFIG_FILE)
242 if os.path.exists(config_file):
243 xbuddy_config.read(config_file)
244 else:
Yiming Chend9202142014-11-07 14:56:52 -0800245 # Get the directory of xbuddy.py file.
246 file_dir = os.path.dirname(os.path.realpath(__file__))
247 # Read the default xbuddy_config.ini from the directory.
248 xbuddy_config.read(os.path.join(file_dir, CONFIG_FILE))
joychen562699a2013-08-13 15:22:14 -0700249
250 # Read the shadow file if there is one.
Chris Sosac2abc722013-08-26 17:11:22 -0700251 if os.path.isdir(CHROOT_SHADOW_DIR):
252 shadow_config_file = os.path.join(CHROOT_SHADOW_DIR, SHADOW_CONFIG_FILE)
253 else:
254 shadow_config_file = os.path.join(self.devserver_dir, SHADOW_CONFIG_FILE)
255
256 _Log('Using shadow config file stored at %s', shadow_config_file)
joychen562699a2013-08-13 15:22:14 -0700257 if os.path.exists(shadow_config_file):
258 shadow_xbuddy_config = ConfigParser.ConfigParser()
259 shadow_xbuddy_config.read(shadow_config_file)
260
261 # Merge shadow config in.
262 sections = shadow_xbuddy_config.sections()
263 for s in sections:
264 if not xbuddy_config.has_section(s):
265 xbuddy_config.add_section(s)
266 options = shadow_xbuddy_config.options(s)
267 for o in options:
268 val = shadow_xbuddy_config.get(s, o)
269 xbuddy_config.set(s, o, val)
270
271 return xbuddy_config
272
273 def _ManageBuilds(self):
274 """Checks if xBuddy is managing local builds using the current config."""
275 try:
276 return self.ParseBoolean(self.config.get(GENERAL, 'manage_builds'))
277 except ConfigParser.Error:
278 return False
279
280 def _Capacity(self):
281 """Gets the xbuddy capacity from the current config."""
282 try:
283 return int(self.config.get(GENERAL, 'capacity'))
284 except ConfigParser.Error:
285 return 5
286
Gilad Arnold38e828c2015-04-24 13:52:07 -0700287 def LookupAlias(self, alias, board=None, version=None):
joychen562699a2013-08-13 15:22:14 -0700288 """Given the full xbuddy config, look up an alias for path rewrite.
289
290 Args:
291 alias: The xbuddy path that could be one of the aliases in the
292 rewrite table.
293 board: The board to fill in with when paths are rewritten. Can be from
Gilad Arnold38e828c2015-04-24 13:52:07 -0700294 the update request xml or the default board from devserver. If None,
295 defers to the value given during XBuddy initialization.
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800296 version: The version to fill in when rewriting paths. Could be a specific
Gilad Arnold38e828c2015-04-24 13:52:07 -0700297 version number or a version alias like LATEST. If None, defers to the
298 value given during XBuddy initialization, or LATEST.
Yu-Ju Hongc54658c2014-01-22 09:18:07 -0800299
joychen562699a2013-08-13 15:22:14 -0700300 Returns:
Gilad Arnold896c6d82015-03-13 16:20:29 -0700301 A pair (val, suffix) where val is the rewritten path, or the original
302 string if no rewrite was found; and suffix is the assigned location
303 suffix, or the default suffix if none was found.
joychen562699a2013-08-13 15:22:14 -0700304 """
joychen562699a2013-08-13 15:22:14 -0700305 try:
Gilad Arnold896c6d82015-03-13 16:20:29 -0700306 suffix = self.config.get(LOCATION_SUFFIXES, alias)
307 except ConfigParser.Error:
308 suffix = RELEASE
309
310 try:
joychen562699a2013-08-13 15:22:14 -0700311 val = self.config.get(PATH_REWRITES, alias)
312 except ConfigParser.Error:
313 # No alias lookup found. Return original path.
Gilad Arnold896c6d82015-03-13 16:20:29 -0700314 val = None
joychen562699a2013-08-13 15:22:14 -0700315
Gilad Arnold896c6d82015-03-13 16:20:29 -0700316 if not (val and val.strip()):
317 val = alias
joychen562699a2013-08-13 15:22:14 -0700318 else:
Gilad Arnold896c6d82015-03-13 16:20:29 -0700319 # The found value is not an empty string.
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800320 # Fill in the board and version.
Gilad Arnold896c6d82015-03-13 16:20:29 -0700321 val = val.replace("BOARD", "%(board)s")
322 val = val.replace("VERSION", "%(version)s")
Gilad Arnold38e828c2015-04-24 13:52:07 -0700323 val = val % {'board': board or self._board,
324 'version': version or self._version or LATEST}
Gilad Arnold896c6d82015-03-13 16:20:29 -0700325
326 _Log("Path is %s, location suffix is %s", val, suffix)
327 return val, suffix
joychen562699a2013-08-13 15:22:14 -0700328
Simran Basi99e63c02014-05-20 10:39:52 -0700329 @staticmethod
330 def _ResolveImageDir(image_dir):
331 """Clean up and return the image dir to use.
332
333 Args:
334 image_dir: directory in Google Storage to use.
335
336 Returns:
337 |image_dir| if |image_dir| is not None. Otherwise, returns
338 devserver_constants.GS_IMAGE_DIR
339 """
340 image_dir = image_dir or devserver_constants.GS_IMAGE_DIR
341 # Remove trailing slashes.
342 return image_dir.rstrip('/')
343
Gilad Arnold896c6d82015-03-13 16:20:29 -0700344 def _LookupOfficial(self, board, suffix, image_dir=None):
joychenf8f07e22013-07-12 17:45:51 -0700345 """Check LATEST-master for the version number of interest."""
346 _Log("Checking gs for latest %s-%s image", board, suffix)
Simran Basi99e63c02014-05-20 10:39:52 -0700347 image_dir = XBuddy._ResolveImageDir(image_dir)
348 latest_addr = (devserver_constants.GS_LATEST_MASTER %
349 {'image_dir': image_dir,
350 'board': board,
351 'suffix': suffix})
joychen121fc9b2013-08-02 14:30:30 -0700352 # Full release + version is in the LATEST file.
xixuan44b55452016-09-06 15:35:56 -0700353 version = self._ctx.Cat(latest_addr)
joychen3cb228e2013-06-12 12:13:13 -0700354
joychenf8f07e22013-07-12 17:45:51 -0700355 return devserver_constants.IMAGE_DIR % {'board':board,
356 'suffix':suffix,
357 'version':version}
Gilad Arnold869e8ab2015-02-19 23:34:49 -0800358
xixuan44b55452016-09-06 15:35:56 -0700359 def _LS(self, path, list_subdirectory=False):
360 """Does a directory listing of the given gs path.
361
362 Args:
363 path: directory location on google storage to check.
364 list_subdirectory: whether to only list subdirectory for |path|.
365
366 Returns:
367 A list of paths that matched |path|.
368 """
369 if list_subdirectory:
370 return self._ctx.DoCommand(
371 ['ls', '-d', '--', path]).output.splitlines()
372 else:
373 return self._ctx.LS(path)
374
375 def _GetLatestVersionFromGsDir(self, path, list_subdirectory=False,
376 with_release=True):
377 """Returns most recent version number found in a google storage directory.
378
379 This lists out the contents of the given GS bucket or regex to GS buckets,
380 and tries to grab the newest version found in the directory names.
381
382 Args:
383 path: directory location on google storage to check.
384 list_subdirectory: whether to only list subdirectory for |path|.
385 with_release: whether versions include a release milestone (e.g. R12).
386
387 Returns:
388 The most recent version number found.
389 """
390 list_result = self._LS(path, list_subdirectory=list_subdirectory)
391 dir_names = [os.path.basename(p.rstrip('/')) for p in list_result]
392 try:
393 filter_re = re.compile(devserver_constants.VERSION_RE if with_release
394 else devserver_constants.VERSION)
395 versions = filter(filter_re.match, dir_names)
396 latest_version = max(versions, key=distutils.version.LooseVersion)
397 except ValueError:
398 raise gs.GSContextException(
399 'Failed to find most recent builds at %s' % path)
400
401 return latest_version
402
Gilad Arnold896c6d82015-03-13 16:20:29 -0700403 def _LookupChannel(self, board, suffix, channel='stable',
404 image_dir=None):
joychenf8f07e22013-07-12 17:45:51 -0700405 """Check the channel folder for the version number of interest."""
joychen121fc9b2013-08-02 14:30:30 -0700406 # Get all names in channel dir. Get 10 highest directories by version.
joychen7df67f72013-07-18 14:21:12 -0700407 _Log("Checking channel '%s' for latest '%s' image", channel, board)
Yu-Ju Hongc54658c2014-01-22 09:18:07 -0800408 # Due to historical reasons, gs://chromeos-releases uses
409 # daisy-spring as opposed to the board name daisy_spring. Convert
xixuan44b55452016-09-06 15:35:56 -0700410 # he board name for the lookup.
Yu-Ju Hongc54658c2014-01-22 09:18:07 -0800411 channel_dir = devserver_constants.GS_CHANNEL_DIR % {
412 'channel':channel,
413 'board':re.sub('_', '-', board)}
xixuan44b55452016-09-06 15:35:56 -0700414 latest_version = self._GetLatestVersionFromGsDir(channel_dir,
415 with_release=False)
joychenf8f07e22013-07-12 17:45:51 -0700416
joychen121fc9b2013-08-02 14:30:30 -0700417 # Figure out release number from the version number.
joychenc3944cb2013-08-19 10:42:07 -0700418 image_url = devserver_constants.IMAGE_DIR % {
Gilad Arnold896c6d82015-03-13 16:20:29 -0700419 'board': board,
420 'suffix': suffix,
421 'version': 'R*' + latest_version}
Simran Basi99e63c02014-05-20 10:39:52 -0700422 image_dir = XBuddy._ResolveImageDir(image_dir)
423 gs_url = os.path.join(image_dir, image_url)
joychenf8f07e22013-07-12 17:45:51 -0700424
425 # There should only be one match on cros-image-archive.
xixuan44b55452016-09-06 15:35:56 -0700426 full_version = self._GetLatestVersionFromGsDir(gs_url,
427 list_subdirectory=True)
joychenf8f07e22013-07-12 17:45:51 -0700428
Gilad Arnold896c6d82015-03-13 16:20:29 -0700429 return devserver_constants.IMAGE_DIR % {'board': board,
430 'suffix': suffix,
431 'version': full_version}
joychenf8f07e22013-07-12 17:45:51 -0700432
Gilad Arnold896c6d82015-03-13 16:20:29 -0700433 def _LookupVersion(self, board, suffix, version):
joychenf8f07e22013-07-12 17:45:51 -0700434 """Search GS image releases for the highest match to a version prefix."""
joychen121fc9b2013-08-02 14:30:30 -0700435 # Build the pattern for GS to match.
joychen7df67f72013-07-18 14:21:12 -0700436 _Log("Checking gs for latest '%s' image with prefix '%s'", board, version)
Gilad Arnold896c6d82015-03-13 16:20:29 -0700437 image_url = devserver_constants.IMAGE_DIR % {'board': board,
438 'suffix': suffix,
439 'version': version + '*'}
joychenf8f07e22013-07-12 17:45:51 -0700440 image_dir = os.path.join(devserver_constants.GS_IMAGE_DIR, image_url)
441
joychen121fc9b2013-08-02 14:30:30 -0700442 # Grab the newest version of the ones matched.
xixuan44b55452016-09-06 15:35:56 -0700443 full_version = self._GetLatestVersionFromGsDir(image_dir,
444 list_subdirectory=True)
Gilad Arnold896c6d82015-03-13 16:20:29 -0700445 return devserver_constants.IMAGE_DIR % {'board': board,
446 'suffix': suffix,
447 'version': full_version}
joychenf8f07e22013-07-12 17:45:51 -0700448
Gilad Arnold896c6d82015-03-13 16:20:29 -0700449 def _RemoteBuildId(self, board, suffix, version):
Chris Sosaea734d92013-10-11 11:28:58 -0700450 """Returns the remote build_id for the given board and version.
451
452 Raises:
453 XBuddyException: If we failed to resolve the version to a valid build_id.
454 """
Gilad Arnold896c6d82015-03-13 16:20:29 -0700455 build_id_as_is = devserver_constants.IMAGE_DIR % {'board': board,
456 'suffix': '',
457 'version': version}
458 build_id_suffix = devserver_constants.IMAGE_DIR % {'board': board,
459 'suffix': suffix,
460 'version': version}
Chris Sosaea734d92013-10-11 11:28:58 -0700461 # Return the first path that exists. We assume that what the user typed
462 # is better than with a default suffix added i.e. x86-generic/blah is
463 # more valuable than x86-generic-release/blah.
Gilad Arnold896c6d82015-03-13 16:20:29 -0700464 for build_id in build_id_as_is, build_id_suffix:
Chris Sosaea734d92013-10-11 11:28:58 -0700465 try:
xixuan44b55452016-09-06 15:35:56 -0700466 version = self._ctx.LS(
467 '%s/%s' % (devserver_constants.GS_IMAGE_DIR, build_id))
Chris Sosaea734d92013-10-11 11:28:58 -0700468 return build_id
xixuan44b55452016-09-06 15:35:56 -0700469 except (gs.GSCommandError, gs.GSContextException, gs.GSNoSuchKey):
Chris Sosaea734d92013-10-11 11:28:58 -0700470 continue
Gilad Arnold5f46d8e2015-02-19 12:17:55 -0800471
472 raise XBuddyException('Could not find remote build_id for %s %s' % (
473 board, version))
Chris Sosaea734d92013-10-11 11:28:58 -0700474
Gilad Arnold896c6d82015-03-13 16:20:29 -0700475 def _ResolveBuildVersion(self, board, suffix, base_version):
Gilad Arnold869e8ab2015-02-19 23:34:49 -0800476 """Check LATEST-<base_version> and returns a full build version."""
477 _Log('Checking gs for full version for %s of %s', base_version, board)
478 # TODO(garnold) We might want to accommodate version prefixes and pick the
479 # most recent found, as done in _LookupVersion().
480 latest_addr = (devserver_constants.GS_LATEST_BASE_VERSION %
481 {'image_dir': devserver_constants.GS_IMAGE_DIR,
482 'board': board,
Gilad Arnold896c6d82015-03-13 16:20:29 -0700483 'suffix': suffix,
Gilad Arnold869e8ab2015-02-19 23:34:49 -0800484 'base_version': base_version})
Gilad Arnold869e8ab2015-02-19 23:34:49 -0800485 # Full release + version is in the LATEST file.
xixuan44b55452016-09-06 15:35:56 -0700486 return self._ctx.Cat(latest_addr)
Gilad Arnold869e8ab2015-02-19 23:34:49 -0800487
Gilad Arnold896c6d82015-03-13 16:20:29 -0700488 def _ResolveVersionToBuildId(self, board, suffix, version, image_dir=None):
joychen121fc9b2013-08-02 14:30:30 -0700489 """Handle version aliases for remote payloads in GS.
joychen3cb228e2013-06-12 12:13:13 -0700490
491 Args:
492 board: as specified in the original call. (i.e. x86-generic, parrot)
Gilad Arnold896c6d82015-03-13 16:20:29 -0700493 suffix: The location suffix, to be added to board name.
joychen3cb228e2013-06-12 12:13:13 -0700494 version: as entered in the original call. can be
495 {TBD, 0. some custom alias as defined in a config file}
Ningning Xiab2a1af52016-04-22 11:14:42 -0700496 1. fully qualified build version.
Gilad Arnold869e8ab2015-02-19 23:34:49 -0800497 2. latest
498 3. latest-{channel}
499 4. latest-official-{board suffix}
500 5. version prefix (i.e. RX-Y.X, RX-Y, RX)
Simran Basi99e63c02014-05-20 10:39:52 -0700501 image_dir: image directory to check in Google Storage. If none,
502 the default bucket is used.
joychen3cb228e2013-06-12 12:13:13 -0700503
504 Returns:
Chris Sosaea734d92013-10-11 11:28:58 -0700505 Location where the image dir is actually found on GS (build_id)
joychen3cb228e2013-06-12 12:13:13 -0700506
Chris Sosaea734d92013-10-11 11:28:58 -0700507 Raises:
508 XBuddyException: If we failed to resolve the version to a valid url.
joychen3cb228e2013-06-12 12:13:13 -0700509 """
joychenf8f07e22013-07-12 17:45:51 -0700510 # Only the last segment of the alias is variable relative to the rest.
511 version_tuple = version.rsplit('-', 1)
joychen3cb228e2013-06-12 12:13:13 -0700512
joychenf8f07e22013-07-12 17:45:51 -0700513 if re.match(devserver_constants.VERSION_RE, version):
Gilad Arnold896c6d82015-03-13 16:20:29 -0700514 return self._RemoteBuildId(board, suffix, version)
Gilad Arnold869e8ab2015-02-19 23:34:49 -0800515 elif re.match(devserver_constants.VERSION, version):
Ningning Xiab2a1af52016-04-22 11:14:42 -0700516 raise XBuddyException('\'%s\' is not valid. Should provide the fully '
517 'qualified version with a version prefix \'RX-\' '
518 'due to crbug.com/585914' % version)
joychenf8f07e22013-07-12 17:45:51 -0700519 elif version == LATEST_OFFICIAL:
520 # latest-official --> LATEST build in board-release
Gilad Arnold896c6d82015-03-13 16:20:29 -0700521 return self._LookupOfficial(board, suffix, image_dir=image_dir)
joychenf8f07e22013-07-12 17:45:51 -0700522 elif version_tuple[0] == LATEST_OFFICIAL:
523 # latest-official-{suffix} --> LATEST build in board-{suffix}
Gilad Arnold896c6d82015-03-13 16:20:29 -0700524 return self._LookupOfficial(board, version_tuple[1],
525 image_dir=image_dir)
joychenf8f07e22013-07-12 17:45:51 -0700526 elif version == LATEST:
527 # latest --> latest build on stable channel
Gilad Arnold896c6d82015-03-13 16:20:29 -0700528 return self._LookupChannel(board, suffix, image_dir=image_dir)
joychenf8f07e22013-07-12 17:45:51 -0700529 elif version_tuple[0] == LATEST:
530 if re.match(devserver_constants.VERSION_RE, version_tuple[1]):
531 # latest-R* --> most recent qualifying build
Gilad Arnold896c6d82015-03-13 16:20:29 -0700532 return self._LookupVersion(board, suffix, version_tuple[1])
joychenf8f07e22013-07-12 17:45:51 -0700533 else:
534 # latest-{channel} --> latest build within that channel
Gilad Arnold896c6d82015-03-13 16:20:29 -0700535 return self._LookupChannel(board, suffix, channel=version_tuple[1],
Simran Basi99e63c02014-05-20 10:39:52 -0700536 image_dir=image_dir)
joychen3cb228e2013-06-12 12:13:13 -0700537 else:
538 # The given version doesn't match any known patterns.
joychen921e1fb2013-06-28 11:12:20 -0700539 raise XBuddyException("Version %s unknown. Can't find on GS." % version)
joychen3cb228e2013-06-12 12:13:13 -0700540
joychen5260b9a2013-07-16 14:48:01 -0700541 @staticmethod
542 def _Symlink(link, target):
543 """Symlinks link to target, and removes whatever link was there before."""
544 _Log("Linking to %s from %s", link, target)
545 if os.path.lexists(link):
546 os.unlink(link)
547 os.symlink(target, link)
548
joychen121fc9b2013-08-02 14:30:30 -0700549 def _GetLatestLocalVersion(self, board):
joychen921e1fb2013-06-28 11:12:20 -0700550 """Get the version of the latest image built for board by build_image
551
552 Updates the symlink reference within the xBuddy static dir to point to
553 the real image dir in the local /build/images directory.
554
555 Args:
joychenc3944cb2013-08-19 10:42:07 -0700556 board: board that image was built for.
joychen921e1fb2013-06-28 11:12:20 -0700557
558 Returns:
joychen121fc9b2013-08-02 14:30:30 -0700559 The discovered version of the image.
joychenc3944cb2013-08-19 10:42:07 -0700560
561 Raises:
562 XBuddyException if neither test nor dev image was found in latest built
563 directory.
joychen3cb228e2013-06-12 12:13:13 -0700564 """
joychen921e1fb2013-06-28 11:12:20 -0700565 latest_local_dir = self.GetLatestImageDir(board)
joychenb0dfe552013-07-30 10:02:06 -0700566 if not latest_local_dir or not os.path.exists(latest_local_dir):
joychen921e1fb2013-06-28 11:12:20 -0700567 raise XBuddyException('No builds found for %s. Did you run build_image?' %
568 board)
569
joychen121fc9b2013-08-02 14:30:30 -0700570 # Assume that the version number is the name of the directory.
joychenc3944cb2013-08-19 10:42:07 -0700571 return os.path.basename(latest_local_dir.rstrip('/'))
joychen921e1fb2013-06-28 11:12:20 -0700572
joychenc3944cb2013-08-19 10:42:07 -0700573 @staticmethod
574 def _FindAny(local_dir):
575 """Returns the image_type for ANY given the local_dir."""
joychenc3944cb2013-08-19 10:42:07 -0700576 test_image = os.path.join(local_dir, devserver_constants.TEST_IMAGE_FILE)
Yu-Ju Hongc23c79b2014-03-17 12:40:33 -0700577 dev_image = os.path.join(local_dir, devserver_constants.IMAGE_FILE)
578 # Prioritize test images over dev images.
joychenc3944cb2013-08-19 10:42:07 -0700579 if os.path.exists(test_image):
580 return 'test'
581
Yu-Ju Hongc23c79b2014-03-17 12:40:33 -0700582 if os.path.exists(dev_image):
583 return 'dev'
584
joychenc3944cb2013-08-19 10:42:07 -0700585 raise XBuddyException('No images found in %s' % local_dir)
586
587 @staticmethod
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800588 def _InterpretPath(path, default_board=None, default_version=None):
joychen121fc9b2013-08-02 14:30:30 -0700589 """Split and return the pieces of an xBuddy path name
joychen921e1fb2013-06-28 11:12:20 -0700590
joychen121fc9b2013-08-02 14:30:30 -0700591 Args:
592 path: the path xBuddy Get was called with.
Chris Sosa0eecf962014-02-03 14:14:39 -0800593 default_board: board to use in case board isn't in path.
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800594 default_version: Version to use in case version isn't in path.
joychen3cb228e2013-06-12 12:13:13 -0700595
Yu-Ju Hongc54658c2014-01-22 09:18:07 -0800596 Returns:
Chris Sosa75490802013-09-30 17:21:45 -0700597 tuple of (image_type, board, version, whether the path is local)
joychen3cb228e2013-06-12 12:13:13 -0700598
599 Raises:
600 XBuddyException: if the path can't be resolved into valid components
601 """
joychen121fc9b2013-08-02 14:30:30 -0700602 path_list = filter(None, path.split('/'))
joychen7df67f72013-07-18 14:21:12 -0700603
Chris Sosa0eecf962014-02-03 14:14:39 -0800604 # Do the stuff that is well known first. We know that if paths have a
605 # image_type, it must be one of the GS/LOCAL aliases and it must be at the
606 # end. Similarly, local/remote are well-known and must start the path list.
607 is_local = True
608 if path_list and path_list[0] in (REMOTE, LOCAL):
609 is_local = (path_list.pop(0) == LOCAL)
joychen7df67f72013-07-18 14:21:12 -0700610
Chris Sosa0eecf962014-02-03 14:14:39 -0800611 # Default image type is determined by remote vs. local.
612 if is_local:
613 image_type = ANY
614 else:
615 image_type = TEST
joychen7df67f72013-07-18 14:21:12 -0700616
Chris Sosa0eecf962014-02-03 14:14:39 -0800617 if path_list and path_list[-1] in GS_ALIASES + LOCAL_ALIASES:
618 image_type = path_list.pop(-1)
joychen3cb228e2013-06-12 12:13:13 -0700619
Chris Sosa0eecf962014-02-03 14:14:39 -0800620 # Now for the tricky part. We don't actually know at this point if the rest
621 # of the path is just a board | version (like R33-2341.0.0) or just a board
622 # or just a version. So we do our best to do the right thing.
623 board = default_board
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800624 version = default_version or LATEST
Chris Sosa0eecf962014-02-03 14:14:39 -0800625 if len(path_list) == 1:
626 path = path_list.pop(0)
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800627 # Treat this as a version if it's one we know (contains default or
628 # latest), or we were given an actual default board.
629 if default_version in path or LATEST in path or default_board is not None:
Chris Sosa0eecf962014-02-03 14:14:39 -0800630 version = path
joychen7df67f72013-07-18 14:21:12 -0700631 else:
Chris Sosa0eecf962014-02-03 14:14:39 -0800632 board = path
joychen7df67f72013-07-18 14:21:12 -0700633
Chris Sosa0eecf962014-02-03 14:14:39 -0800634 elif len(path_list) == 2:
635 # Assumes board/version.
636 board = path_list.pop(0)
637 version = path_list.pop(0)
638
639 if path_list:
640 raise XBuddyException("Path isn't valid. Could not figure out how to "
641 "parse remaining components: %s." % path_list)
642
643 _Log("Get artifact '%s' with board %s and version %s'. Locally? %s",
joychen7df67f72013-07-18 14:21:12 -0700644 image_type, board, version, is_local)
645
646 return image_type, board, version, is_local
joychen3cb228e2013-06-12 12:13:13 -0700647
joychen921e1fb2013-06-28 11:12:20 -0700648 def _SyncRegistryWithBuildImages(self):
Gilad Arnold5f46d8e2015-02-19 12:17:55 -0800649 """Crawl images_dir for build_ids of images generated from build_image.
joychen5260b9a2013-07-16 14:48:01 -0700650
651 This will find images and symlink them in xBuddy's static dir so that
652 xBuddy's cache can serve them.
653 If xBuddy's _manage_builds option is on, then a timestamp will also be
654 generated, and xBuddy will clear them from the directory they are in, as
655 necessary.
656 """
Yu-Ju Hong235d1b52014-04-16 11:01:47 -0700657 if not os.path.isdir(self.images_dir):
658 # Skip syncing if images_dir does not exist.
659 _Log('Cannot find %s; skip syncing image registry.', self.images_dir)
660 return
661
joychen921e1fb2013-06-28 11:12:20 -0700662 build_ids = []
663 for b in os.listdir(self.images_dir):
joychen5260b9a2013-07-16 14:48:01 -0700664 # Ensure we have directories to track all boards in build/images
665 common_util.MkDirP(os.path.join(self.static_dir, b))
joychen921e1fb2013-06-28 11:12:20 -0700666 board_dir = os.path.join(self.images_dir, b)
667 build_ids.extend(['/'.join([b, v]) for v
joychenc3944cb2013-08-19 10:42:07 -0700668 in os.listdir(board_dir) if not v == LATEST])
joychen921e1fb2013-06-28 11:12:20 -0700669
joychen121fc9b2013-08-02 14:30:30 -0700670 # Symlink undiscovered images, and update timestamps if manage_builds is on.
joychen5260b9a2013-07-16 14:48:01 -0700671 for build_id in build_ids:
672 link = os.path.join(self.static_dir, build_id)
673 target = os.path.join(self.images_dir, build_id)
674 XBuddy._Symlink(link, target)
675 if self._manage_builds:
676 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen921e1fb2013-06-28 11:12:20 -0700677
678 def _ListBuildTimes(self):
Gilad Arnold5f46d8e2015-02-19 12:17:55 -0800679 """Returns the currently cached builds and their last access timestamp.
joychen3cb228e2013-06-12 12:13:13 -0700680
681 Returns:
682 list of tuples that matches xBuddy build/version to timestamps in long
683 """
joychen121fc9b2013-08-02 14:30:30 -0700684 # Update currently cached builds.
joychen3cb228e2013-06-12 12:13:13 -0700685 build_dict = {}
686
joychen7df67f72013-07-18 14:21:12 -0700687 for f in os.listdir(self._timestamp_folder):
joychen3cb228e2013-06-12 12:13:13 -0700688 last_accessed = os.path.getmtime(os.path.join(self._timestamp_folder, f))
689 build_id = Timestamp.TimestampToBuild(f)
joychenc3944cb2013-08-19 10:42:07 -0700690 stale_time = datetime.timedelta(seconds=(time.time() - last_accessed))
joychen921e1fb2013-06-28 11:12:20 -0700691 build_dict[build_id] = stale_time
joychen3cb228e2013-06-12 12:13:13 -0700692 return_tup = sorted(build_dict.iteritems(), key=operator.itemgetter(1))
693 return return_tup
694
Chris Sosa75490802013-09-30 17:21:45 -0700695 def _Download(self, gs_url, artifacts):
696 """Download the artifacts from the given gs_url.
697
698 Raises:
699 build_artifact.ArtifactDownloadError: If we failed to download the
700 artifact.
701 """
joychen3cb228e2013-06-12 12:13:13 -0700702 with XBuddy._staging_thread_count_lock:
703 XBuddy._staging_thread_count += 1
704 try:
Chris Sosa75490802013-09-30 17:21:45 -0700705 _Log("Downloading %s from %s", artifacts, gs_url)
Gabe Black3b567202015-09-23 14:07:59 -0700706 dl = downloader.GoogleStorageDownloader(self.static_dir, gs_url)
707 factory = build_artifact.ChromeOSArtifactFactory(
708 dl.GetBuildDir(), artifacts, [], dl.GetBuild())
709 dl.Download(factory)
joychen3cb228e2013-06-12 12:13:13 -0700710 finally:
711 with XBuddy._staging_thread_count_lock:
712 XBuddy._staging_thread_count -= 1
713
Chris Sosa75490802013-09-30 17:21:45 -0700714 def CleanCache(self):
joychen562699a2013-08-13 15:22:14 -0700715 """Delete all builds besides the newest N builds"""
joychen121fc9b2013-08-02 14:30:30 -0700716 if not self._manage_builds:
717 return
joychen921e1fb2013-06-28 11:12:20 -0700718 cached_builds = [e[0] for e in self._ListBuildTimes()]
joychen3cb228e2013-06-12 12:13:13 -0700719 _Log('In cache now: %s', cached_builds)
720
joychen562699a2013-08-13 15:22:14 -0700721 for b in range(self._Capacity(), len(cached_builds)):
joychen3cb228e2013-06-12 12:13:13 -0700722 b_path = cached_builds[b]
joychen7df67f72013-07-18 14:21:12 -0700723 _Log("Clearing '%s' from cache", b_path)
joychen3cb228e2013-06-12 12:13:13 -0700724
725 time_file = os.path.join(self._timestamp_folder,
726 Timestamp.BuildToTimestamp(b_path))
joychen921e1fb2013-06-28 11:12:20 -0700727 os.unlink(time_file)
728 clear_dir = os.path.join(self.static_dir, b_path)
joychen3cb228e2013-06-12 12:13:13 -0700729 try:
joychen121fc9b2013-08-02 14:30:30 -0700730 # Handle symlinks, in the case of links to local builds if enabled.
731 if os.path.islink(clear_dir):
joychen5260b9a2013-07-16 14:48:01 -0700732 target = os.readlink(clear_dir)
733 _Log('Deleting locally built image at %s', target)
joychen921e1fb2013-06-28 11:12:20 -0700734
735 os.unlink(clear_dir)
joychen5260b9a2013-07-16 14:48:01 -0700736 if os.path.exists(target):
joychen921e1fb2013-06-28 11:12:20 -0700737 shutil.rmtree(target)
738 elif os.path.exists(clear_dir):
joychen5260b9a2013-07-16 14:48:01 -0700739 _Log('Deleting downloaded image at %s', clear_dir)
joychen3cb228e2013-06-12 12:13:13 -0700740 shutil.rmtree(clear_dir)
joychen921e1fb2013-06-28 11:12:20 -0700741
joychen121fc9b2013-08-02 14:30:30 -0700742 except Exception as err:
743 raise XBuddyException('Failed to clear %s: %s' % (clear_dir, err))
joychen3cb228e2013-06-12 12:13:13 -0700744
Simran Basi99e63c02014-05-20 10:39:52 -0700745 def _GetFromGS(self, build_id, image_type, image_dir=None):
Chris Sosa75490802013-09-30 17:21:45 -0700746 """Check if the artifact is available locally. Download from GS if not.
747
Simran Basi99e63c02014-05-20 10:39:52 -0700748 Args:
749 build_id: Path to the image or update directory on the devserver or
750 in Google Storage. e.g. 'x86-generic/R26-4000.0.0'
751 image_type: Image type to download. Look at aliases at top of file for
752 options.
753 image_dir: Google Storage image archive to search in if requesting a
754 remote artifact. If none uses the default bucket.
755
Chris Sosa75490802013-09-30 17:21:45 -0700756 Raises:
757 build_artifact.ArtifactDownloadError: If we failed to download the
758 artifact.
759 """
Simran Basi99e63c02014-05-20 10:39:52 -0700760 image_dir = XBuddy._ResolveImageDir(image_dir)
761 gs_url = os.path.join(image_dir, build_id)
joychen921e1fb2013-06-28 11:12:20 -0700762
joychen121fc9b2013-08-02 14:30:30 -0700763 # Stage image if not found in cache.
joychen921e1fb2013-06-28 11:12:20 -0700764 file_name = GS_ALIAS_TO_FILENAME[image_type]
joychen346531c2013-07-24 16:55:56 -0700765 file_loc = os.path.join(self.static_dir, build_id, file_name)
766 cached = os.path.exists(file_loc)
767
joychen921e1fb2013-06-28 11:12:20 -0700768 if not cached:
Chris Sosa75490802013-09-30 17:21:45 -0700769 artifact = GS_ALIAS_TO_ARTIFACT[image_type]
770 self._Download(gs_url, [artifact])
joychen921e1fb2013-06-28 11:12:20 -0700771 else:
772 _Log('Image already cached.')
773
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800774 def _GetArtifact(self, path_list, board=None, version=None,
775 lookup_only=False, image_dir=None):
joychen346531c2013-07-24 16:55:56 -0700776 """Interpret an xBuddy path and return directory/file_name to resource.
777
Chris Sosa75490802013-09-30 17:21:45 -0700778 Note board can be passed that in but by default if self._board is set,
779 that is used rather than board.
780
Simran Basi99e63c02014-05-20 10:39:52 -0700781 Args:
782 path_list: [board, version, alias] as split from the xbuddy call url.
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800783 board: Board whos artifacts we are looking for. Only used if no board was
784 given during XBuddy initialization.
785 version: Version whose artifacts we are looking for. Used if no version
786 was given during XBuddy initialization. If None, defers to LATEST.
Simran Basi99e63c02014-05-20 10:39:52 -0700787 lookup_only: If true just look up the artifact, if False stage it on
788 the devserver as well.
789 image_dir: Google Storage image archive to search in if requesting a
790 remote artifact. If none uses the default bucket.
791
joychen346531c2013-07-24 16:55:56 -0700792 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -0700793 build_id: Path to the image or update directory on the devserver or
794 in Google Storage. e.g. 'x86-generic/R26-4000.0.0'
795 file_name: of the artifact in the build_id directory.
joychen346531c2013-07-24 16:55:56 -0700796
797 Raises:
joychen121fc9b2013-08-02 14:30:30 -0700798 XBuddyException: if the path could not be translated
Chris Sosa75490802013-09-30 17:21:45 -0700799 build_artifact.ArtifactDownloadError: if we failed to download the
800 artifact.
joychen346531c2013-07-24 16:55:56 -0700801 """
joychen121fc9b2013-08-02 14:30:30 -0700802 path = '/'.join(path_list)
Chris Sosa0eecf962014-02-03 14:14:39 -0800803 default_board = self._board if self._board else board
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800804 default_version = self._version or version or LATEST
joychenb0dfe552013-07-30 10:02:06 -0700805 # Rewrite the path if there is an appropriate default.
Gilad Arnold38e828c2015-04-24 13:52:07 -0700806 path, suffix = self.LookupAlias(path, board=default_board,
807 version=default_version)
joychen121fc9b2013-08-02 14:30:30 -0700808 # Parse the path.
Chris Sosa0eecf962014-02-03 14:14:39 -0800809 image_type, board, version, is_local = self._InterpretPath(
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800810 path, default_board, default_version)
joychen7df67f72013-07-18 14:21:12 -0700811 if is_local:
joychen121fc9b2013-08-02 14:30:30 -0700812 # Get a local image.
joychen7df67f72013-07-18 14:21:12 -0700813 if version == LATEST:
joychen121fc9b2013-08-02 14:30:30 -0700814 # Get the latest local image for the given board.
815 version = self._GetLatestLocalVersion(board)
joychen7df67f72013-07-18 14:21:12 -0700816
joychenc3944cb2013-08-19 10:42:07 -0700817 build_id = os.path.join(board, version)
818 artifact_dir = os.path.join(self.static_dir, build_id)
819 if image_type == ANY:
820 image_type = self._FindAny(artifact_dir)
joychen121fc9b2013-08-02 14:30:30 -0700821
joychenc3944cb2013-08-19 10:42:07 -0700822 file_name = LOCAL_ALIAS_TO_FILENAME[image_type]
823 artifact_path = os.path.join(artifact_dir, file_name)
824 if not os.path.exists(artifact_path):
825 raise XBuddyException('Local %s artifact not in static_dir at %s' %
826 (image_type, artifact_path))
joychen121fc9b2013-08-02 14:30:30 -0700827
joychen921e1fb2013-06-28 11:12:20 -0700828 else:
joychen121fc9b2013-08-02 14:30:30 -0700829 # Get a remote image.
joychen921e1fb2013-06-28 11:12:20 -0700830 if image_type not in GS_ALIASES:
joychen7df67f72013-07-18 14:21:12 -0700831 raise XBuddyException('Bad remote image type: %s. Use one of: %s' %
joychen921e1fb2013-06-28 11:12:20 -0700832 (image_type, GS_ALIASES))
Gilad Arnold896c6d82015-03-13 16:20:29 -0700833 build_id = self._ResolveVersionToBuildId(board, suffix, version,
Simran Basi99e63c02014-05-20 10:39:52 -0700834 image_dir=image_dir)
Chris Sosa75490802013-09-30 17:21:45 -0700835 _Log('Resolved version %s to %s.', version, build_id)
836 file_name = GS_ALIAS_TO_FILENAME[image_type]
837 if not lookup_only:
Simran Basi99e63c02014-05-20 10:39:52 -0700838 self._GetFromGS(build_id, image_type, image_dir=image_dir)
joychenf8f07e22013-07-12 17:45:51 -0700839
joychenc3944cb2013-08-19 10:42:07 -0700840 return build_id, file_name
joychen3cb228e2013-06-12 12:13:13 -0700841
842 ############################ BEGIN PUBLIC METHODS
843
844 def List(self):
845 """Lists the currently available images & time since last access."""
joychen921e1fb2013-06-28 11:12:20 -0700846 self._SyncRegistryWithBuildImages()
847 builds = self._ListBuildTimes()
848 return_string = ''
849 for build, timestamp in builds:
850 return_string += '<b>' + build + '</b> '
851 return_string += '(time since last access: ' + str(timestamp) + ')<br>'
852 return return_string
joychen3cb228e2013-06-12 12:13:13 -0700853
854 def Capacity(self):
855 """Returns the number of images cached by xBuddy."""
joychen562699a2013-08-13 15:22:14 -0700856 return str(self._Capacity())
joychen3cb228e2013-06-12 12:13:13 -0700857
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800858 def Translate(self, path_list, board=None, version=None, image_dir=None):
joychen346531c2013-07-24 16:55:56 -0700859 """Translates an xBuddy path to a real path to artifact if it exists.
860
joychen121fc9b2013-08-02 14:30:30 -0700861 Equivalent to the Get call, minus downloading and updating timestamps,
joychen346531c2013-07-24 16:55:56 -0700862
Simran Basi99e63c02014-05-20 10:39:52 -0700863 Args:
864 path_list: [board, version, alias] as split from the xbuddy call url.
865 board: Board whos artifacts we are looking for. If None, use the board
866 XBuddy was initialized to use.
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800867 version: Version whose artifacts we are looking for. If None, use the
868 version XBuddy was initialized with, or LATEST.
Simran Basi99e63c02014-05-20 10:39:52 -0700869 image_dir: image directory to check in Google Storage. If none,
870 the default bucket is used.
871
joychen7c2054a2013-07-25 11:14:07 -0700872 Returns:
joychen121fc9b2013-08-02 14:30:30 -0700873 build_id: Path to the image or update directory on the devserver.
874 e.g. 'x86-generic/R26-4000.0.0'
875 The returned path is always the path to the directory within
876 static_dir, so it is always the build_id of the image.
877 file_name: The file name of the artifact. Can take any of the file
878 values in devserver_constants.
879 e.g. 'chromiumos_test_image.bin' or 'update.gz' if the path list
880 specified 'test' or 'full_payload' artifacts, respectively.
joychen7c2054a2013-07-25 11:14:07 -0700881
joychen121fc9b2013-08-02 14:30:30 -0700882 Raises:
883 XBuddyException: if the path couldn't be translated
joychen346531c2013-07-24 16:55:56 -0700884 """
885 self._SyncRegistryWithBuildImages()
Chris Sosa75490802013-09-30 17:21:45 -0700886 build_id, file_name = self._GetArtifact(path_list, board=board,
Gilad Arnoldd04fcab2015-02-19 12:00:45 -0800887 version=version,
Simran Basi99e63c02014-05-20 10:39:52 -0700888 lookup_only=True,
889 image_dir=image_dir)
joychen346531c2013-07-24 16:55:56 -0700890
joychen121fc9b2013-08-02 14:30:30 -0700891 _Log('Returning path to payload: %s/%s', build_id, file_name)
892 return build_id, file_name
joychen346531c2013-07-24 16:55:56 -0700893
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700894 def StageTestArtifactsForUpdate(self, path_list):
Chris Sosa75490802013-09-30 17:21:45 -0700895 """Stages test artifacts for update and returns build_id.
896
897 Raises:
898 XBuddyException: if the path could not be translated
899 build_artifact.ArtifactDownloadError: if we failed to download the test
900 artifacts.
901 """
902 build_id, file_name = self.Translate(path_list)
903 if file_name == devserver_constants.TEST_IMAGE_FILE:
904 gs_url = os.path.join(devserver_constants.GS_IMAGE_DIR,
905 build_id)
906 artifacts = [FULL, STATEFUL]
907 self._Download(gs_url, artifacts)
908 return build_id
909
Simran Basi99e63c02014-05-20 10:39:52 -0700910 def Get(self, path_list, image_dir=None):
joychen921e1fb2013-06-28 11:12:20 -0700911 """The full xBuddy call, returns resource specified by path_list.
joychen3cb228e2013-06-12 12:13:13 -0700912
913 Please see devserver.py:xbuddy for full documentation.
joychen121fc9b2013-08-02 14:30:30 -0700914
joychen3cb228e2013-06-12 12:13:13 -0700915 Args:
Simran Basi99e63c02014-05-20 10:39:52 -0700916 path_list: [board, version, alias] as split from the xbuddy call url.
917 image_dir: image directory to check in Google Storage. If none,
918 the default bucket is used.
joychen3cb228e2013-06-12 12:13:13 -0700919
920 Returns:
joychen121fc9b2013-08-02 14:30:30 -0700921 build_id: Path to the image or update directory on the devserver.
Simran Basi99e63c02014-05-20 10:39:52 -0700922 e.g. 'x86-generic/R26-4000.0.0'
923 The returned path is always the path to the directory within
924 static_dir, so it is always the build_id of the image.
joychen121fc9b2013-08-02 14:30:30 -0700925 file_name: The file name of the artifact. Can take any of the file
Simran Basi99e63c02014-05-20 10:39:52 -0700926 values in devserver_constants.
927 e.g. 'chromiumos_test_image.bin' or 'update.gz' if the path list
928 specified 'test' or 'full_payload' artifacts, respectively.
joychen3cb228e2013-06-12 12:13:13 -0700929
930 Raises:
Chris Sosa75490802013-09-30 17:21:45 -0700931 XBuddyException: if the path could not be translated
932 build_artifact.ArtifactDownloadError: if we failed to download the
933 artifact.
joychen3cb228e2013-06-12 12:13:13 -0700934 """
joychen7df67f72013-07-18 14:21:12 -0700935 self._SyncRegistryWithBuildImages()
Simran Basi99e63c02014-05-20 10:39:52 -0700936 build_id, file_name = self._GetArtifact(path_list, image_dir=image_dir)
joychen921e1fb2013-06-28 11:12:20 -0700937 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen3cb228e2013-06-12 12:13:13 -0700938 #TODO (joyc): run in sep thread
Chris Sosa75490802013-09-30 17:21:45 -0700939 self.CleanCache()
joychen3cb228e2013-06-12 12:13:13 -0700940
joychen121fc9b2013-08-02 14:30:30 -0700941 _Log('Returning path to payload: %s/%s', build_id, file_name)
942 return build_id, file_name