blob: f9c274b25feb1577c245a004d37a117953fae1c6 [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
5import datetime
6import operator
7import os
joychenf8f07e22013-07-12 17:45:51 -07008import re
joychen3cb228e2013-06-12 12:13:13 -07009import shutil
joychenf8f07e22013-07-12 17:45:51 -070010import time
joychen3cb228e2013-06-12 12:13:13 -070011import threading
12
joychen921e1fb2013-06-28 11:12:20 -070013import build_util
joychen3cb228e2013-06-12 12:13:13 -070014import artifact_info
15import build_artifact
16import common_util
17import devserver_constants
18import downloader
joychenf8f07e22013-07-12 17:45:51 -070019import gsutil_util
joychen3cb228e2013-06-12 12:13:13 -070020import log_util
joychenb0dfe552013-07-30 10:02:06 -070021import xbuddy_lookup_table
joychen3cb228e2013-06-12 12:13:13 -070022
23# Module-local log function.
24def _Log(message, *args):
25 return log_util.LogWithTag('XBUDDY', message, *args)
26
joychen3cb228e2013-06-12 12:13:13 -070027_XBUDDY_CAPACITY = 5
joychen921e1fb2013-06-28 11:12:20 -070028
29# Local build constants
joychen7df67f72013-07-18 14:21:12 -070030LATEST = "latest"
31LOCAL = "local"
32REMOTE = "remote"
joychen921e1fb2013-06-28 11:12:20 -070033LOCAL_ALIASES = [
34 'test',
35 'base',
36 'dev',
joychen346531c2013-07-24 16:55:56 -070037 'full_payload',
joychen921e1fb2013-06-28 11:12:20 -070038]
39
40LOCAL_FILE_NAMES = [
41 devserver_constants.TEST_IMAGE_FILE,
42 devserver_constants.BASE_IMAGE_FILE,
43 devserver_constants.IMAGE_FILE,
joychen7c2054a2013-07-25 11:14:07 -070044 devserver_constants.UPDATE_FILE,
joychen921e1fb2013-06-28 11:12:20 -070045]
46
47LOCAL_ALIAS_TO_FILENAME = dict(zip(LOCAL_ALIASES, LOCAL_FILE_NAMES))
48
49# Google Storage constants
50GS_ALIASES = [
joychen3cb228e2013-06-12 12:13:13 -070051 'test',
52 'base',
53 'recovery',
54 'full_payload',
55 'stateful',
56 'autotest',
57]
58
59# TODO(joyc) these should become devserver constants.
60# currently, storage locations are embedded in the artifact classes defined in
61# build_artifact
62
joychen921e1fb2013-06-28 11:12:20 -070063GS_FILE_NAMES = [
64 devserver_constants.TEST_IMAGE_FILE,
65 devserver_constants.BASE_IMAGE_FILE,
66 devserver_constants.RECOVERY_IMAGE_FILE,
joychen7c2054a2013-07-25 11:14:07 -070067 devserver_constants.UPDATE_FILE,
joychen3cb228e2013-06-12 12:13:13 -070068 build_artifact.STATEFUL_UPDATE_FILE,
69 devserver_constants.AUTOTEST_DIR,
70]
71
72ARTIFACTS = [
73 artifact_info.TEST_IMAGE,
74 artifact_info.BASE_IMAGE,
75 artifact_info.RECOVERY_IMAGE,
76 artifact_info.FULL_PAYLOAD,
77 artifact_info.STATEFUL_PAYLOAD,
78 artifact_info.AUTOTEST,
79]
80
joychen921e1fb2013-06-28 11:12:20 -070081GS_ALIAS_TO_FILENAME = dict(zip(GS_ALIASES, GS_FILE_NAMES))
82GS_ALIAS_TO_ARTIFACT = dict(zip(GS_ALIASES, ARTIFACTS))
joychen3cb228e2013-06-12 12:13:13 -070083
joychen921e1fb2013-06-28 11:12:20 -070084LATEST_OFFICIAL = "latest-official"
joychen3cb228e2013-06-12 12:13:13 -070085
joychenf8f07e22013-07-12 17:45:51 -070086RELEASE = "release"
joychen3cb228e2013-06-12 12:13:13 -070087
joychen3cb228e2013-06-12 12:13:13 -070088
89class XBuddyException(Exception):
90 """Exception classes used by this module."""
91 pass
92
93
94# no __init__ method
95#pylint: disable=W0232
96class Timestamp():
97 """Class to translate build path strings and timestamp filenames."""
98
99 _TIMESTAMP_DELIMITER = 'SLASH'
100 XBUDDY_TIMESTAMP_DIR = 'xbuddy_UpdateTimestamps'
101
102 @staticmethod
103 def TimestampToBuild(timestamp_filename):
104 return timestamp_filename.replace(Timestamp._TIMESTAMP_DELIMITER, '/')
105
106 @staticmethod
107 def BuildToTimestamp(build_path):
108 return build_path.replace('/', Timestamp._TIMESTAMP_DELIMITER)
joychen921e1fb2013-06-28 11:12:20 -0700109
110 @staticmethod
111 def UpdateTimestamp(timestamp_dir, build_id):
112 """Update timestamp file of build with build_id."""
113 common_util.MkDirP(timestamp_dir)
114 time_file = os.path.join(timestamp_dir,
115 Timestamp.BuildToTimestamp(build_id))
116 with file(time_file, 'a'):
117 os.utime(time_file, None)
joychen3cb228e2013-06-12 12:13:13 -0700118#pylint: enable=W0232
119
120
joychen921e1fb2013-06-28 11:12:20 -0700121class XBuddy(build_util.BuildObject):
joychen3cb228e2013-06-12 12:13:13 -0700122 """Class that manages image retrieval and caching by the devserver.
123
124 Image retrieval by xBuddy path:
125 XBuddy accesses images and artifacts that it stores using an xBuddy
126 path of the form: board/version/alias
127 The primary xbuddy.Get call retrieves the correct artifact or url to where
128 the artifacts can be found.
129
130 Image caching:
131 Images and other artifacts are stored identically to how they would have
132 been if devserver's stage rpc was called and the xBuddy cache replaces
133 build versions on a LRU basis. Timestamps are maintained by last accessed
134 times of representative files in the a directory in the static serve
135 directory (XBUDDY_TIMESTAMP_DIR).
136
137 Private class members:
138 _true_values - used for interpreting boolean values
139 _staging_thread_count - track download requests
joychen921e1fb2013-06-28 11:12:20 -0700140 _timestamp_folder - directory with empty files standing in as timestamps
141 for each image currently cached by xBuddy
joychen3cb228e2013-06-12 12:13:13 -0700142 """
143 _true_values = ['true', 't', 'yes', 'y']
144
145 # Number of threads that are staging images.
146 _staging_thread_count = 0
147 # Lock used to lock increasing/decreasing count.
148 _staging_thread_count_lock = threading.Lock()
149
joychenb0dfe552013-07-30 10:02:06 -0700150 def __init__(self, manage_builds=False, board=None, **kwargs):
joychen921e1fb2013-06-28 11:12:20 -0700151 super(XBuddy, self).__init__(**kwargs)
joychen5260b9a2013-07-16 14:48:01 -0700152 self._manage_builds = manage_builds
joychenb0dfe552013-07-30 10:02:06 -0700153
154 # Choose a default board, using the --board flag if given, or
155 # src/scripts/.default_board if it exists.
156 # Default to x86-generic, if that isn't set.
157 self._board = (board or self.GetDefaultBoardID())
158 _Log("Default board used by xBuddy: %s", self._board)
159 self._path_lookup_table = xbuddy_lookup_table.paths(self._board)
160
joychen921e1fb2013-06-28 11:12:20 -0700161 self._timestamp_folder = os.path.join(self.static_dir,
joychen3cb228e2013-06-12 12:13:13 -0700162 Timestamp.XBUDDY_TIMESTAMP_DIR)
joychen7df67f72013-07-18 14:21:12 -0700163 common_util.MkDirP(self._timestamp_folder)
joychen3cb228e2013-06-12 12:13:13 -0700164
165 @classmethod
166 def ParseBoolean(cls, boolean_string):
167 """Evaluate a string to a boolean value"""
168 if boolean_string:
169 return boolean_string.lower() in cls._true_values
170 else:
171 return False
172
joychenf8f07e22013-07-12 17:45:51 -0700173 def _LookupOfficial(self, board, suffix=RELEASE):
174 """Check LATEST-master for the version number of interest."""
175 _Log("Checking gs for latest %s-%s image", board, suffix)
176 latest_addr = devserver_constants.GS_LATEST_MASTER % {'board':board,
177 'suffix':suffix}
178 cmd = 'gsutil cat %s' % latest_addr
179 msg = 'Failed to find build at %s' % latest_addr
180 # Full release + version is in the LATEST file
181 version = gsutil_util.GSUtilRun(cmd, msg)
joychen3cb228e2013-06-12 12:13:13 -0700182
joychenf8f07e22013-07-12 17:45:51 -0700183 return devserver_constants.IMAGE_DIR % {'board':board,
184 'suffix':suffix,
185 'version':version}
186
187 def _LookupChannel(self, board, channel='stable'):
188 """Check the channel folder for the version number of interest."""
189 # Get all names in channel dir. Get 10 highest directories by version
joychen7df67f72013-07-18 14:21:12 -0700190 _Log("Checking channel '%s' for latest '%s' image", channel, board)
joychenf8f07e22013-07-12 17:45:51 -0700191 channel_dir = devserver_constants.GS_CHANNEL_DIR % {'channel':channel,
192 'board':board}
193 latest_version = gsutil_util.GetLatestVersionFromGSDir(channel_dir)
194
195 # Figure out release number from the version number
196 image_url = devserver_constants.IMAGE_DIR % {'board':board,
197 'suffix':RELEASE,
198 'version':'R*'+latest_version}
199 image_dir = os.path.join(devserver_constants.GS_IMAGE_DIR, image_url)
200
201 # There should only be one match on cros-image-archive.
202 full_version = gsutil_util.GetLatestVersionFromGSDir(image_dir)
203
204 return devserver_constants.IMAGE_DIR % {'board':board,
205 'suffix':RELEASE,
206 'version':full_version}
207
208 def _LookupVersion(self, board, version):
209 """Search GS image releases for the highest match to a version prefix."""
210 # Build the pattern for GS to match
joychen7df67f72013-07-18 14:21:12 -0700211 _Log("Checking gs for latest '%s' image with prefix '%s'", board, version)
joychenf8f07e22013-07-12 17:45:51 -0700212 image_url = devserver_constants.IMAGE_DIR % {'board':board,
213 'suffix':RELEASE,
214 'version':version + '*'}
215 image_dir = os.path.join(devserver_constants.GS_IMAGE_DIR, image_url)
216
217 # grab the newest version of the ones matched
218 full_version = gsutil_util.GetLatestVersionFromGSDir(image_dir)
219 return devserver_constants.IMAGE_DIR % {'board':board,
220 'suffix':RELEASE,
221 'version':full_version}
222
223 def _ResolveVersionToUrl(self, board, version):
joychen3cb228e2013-06-12 12:13:13 -0700224 """
joychen921e1fb2013-06-28 11:12:20 -0700225 Handle version aliases for remote payloads in GS.
joychen3cb228e2013-06-12 12:13:13 -0700226
227 Args:
228 board: as specified in the original call. (i.e. x86-generic, parrot)
229 version: as entered in the original call. can be
230 {TBD, 0. some custom alias as defined in a config file}
231 1. latest
232 2. latest-{channel}
233 3. latest-official-{board suffix}
234 4. version prefix (i.e. RX-Y.X, RX-Y, RX)
joychen3cb228e2013-06-12 12:13:13 -0700235
236 Returns:
joychenf8f07e22013-07-12 17:45:51 -0700237 image_url is where the image dir is actually found on GS
joychen3cb228e2013-06-12 12:13:13 -0700238
239 """
joychenf8f07e22013-07-12 17:45:51 -0700240 # TODO (joychen) Convert separate calls to a dict + error out bad paths
joychen3cb228e2013-06-12 12:13:13 -0700241
joychenf8f07e22013-07-12 17:45:51 -0700242 # Only the last segment of the alias is variable relative to the rest.
243 version_tuple = version.rsplit('-', 1)
joychen3cb228e2013-06-12 12:13:13 -0700244
joychenf8f07e22013-07-12 17:45:51 -0700245 if re.match(devserver_constants.VERSION_RE, version):
246 # This is supposed to be a complete version number on GS. Return it.
247 return devserver_constants.IMAGE_DIR % {'board':board,
248 'suffix':RELEASE,
249 'version':version}
250 elif version == LATEST_OFFICIAL:
251 # latest-official --> LATEST build in board-release
252 return self._LookupOfficial(board)
253 elif version_tuple[0] == LATEST_OFFICIAL:
254 # latest-official-{suffix} --> LATEST build in board-{suffix}
255 return self._LookupOfficial(board, version_tuple[1])
256 elif version == LATEST:
257 # latest --> latest build on stable channel
258 return self._LookupChannel(board)
259 elif version_tuple[0] == LATEST:
260 if re.match(devserver_constants.VERSION_RE, version_tuple[1]):
261 # latest-R* --> most recent qualifying build
262 return self._LookupVersion(board, version_tuple[1])
263 else:
264 # latest-{channel} --> latest build within that channel
265 return self._LookupChannel(board, version_tuple[1])
joychen3cb228e2013-06-12 12:13:13 -0700266 else:
267 # The given version doesn't match any known patterns.
joychen921e1fb2013-06-28 11:12:20 -0700268 raise XBuddyException("Version %s unknown. Can't find on GS." % version)
joychen3cb228e2013-06-12 12:13:13 -0700269
joychen5260b9a2013-07-16 14:48:01 -0700270 @staticmethod
271 def _Symlink(link, target):
272 """Symlinks link to target, and removes whatever link was there before."""
273 _Log("Linking to %s from %s", link, target)
274 if os.path.lexists(link):
275 os.unlink(link)
276 os.symlink(target, link)
277
joychen921e1fb2013-06-28 11:12:20 -0700278 def _GetLatestLocalVersion(self, board, file_name):
279 """Get the version of the latest image built for board by build_image
280
281 Updates the symlink reference within the xBuddy static dir to point to
282 the real image dir in the local /build/images directory.
283
284 Args:
285 board - board-suffix
286 file_name - the filename of the image we have cached
287
288 Returns:
289 version - the discovered version of the image.
joychen346531c2013-07-24 16:55:56 -0700290 found - True if file was found
joychen3cb228e2013-06-12 12:13:13 -0700291 """
joychen921e1fb2013-06-28 11:12:20 -0700292 latest_local_dir = self.GetLatestImageDir(board)
joychenb0dfe552013-07-30 10:02:06 -0700293 if not latest_local_dir or not os.path.exists(latest_local_dir):
joychen921e1fb2013-06-28 11:12:20 -0700294 raise XBuddyException('No builds found for %s. Did you run build_image?' %
295 board)
296
297 # assume that the version number is the name of the directory
298 version = os.path.basename(latest_local_dir)
299
300 path_to_image = os.path.join(latest_local_dir, file_name)
joychen346531c2013-07-24 16:55:56 -0700301 if os.path.exists(path_to_image):
302 return version, True
303 else:
304 return version, False
joychen921e1fb2013-06-28 11:12:20 -0700305
306 def _InterpretPath(self, path_list):
307 """
308 Split and return the pieces of an xBuddy path name
joychen3cb228e2013-06-12 12:13:13 -0700309
310 input:
joychen921e1fb2013-06-28 11:12:20 -0700311 path_list: the segments of the path xBuddy Get was called with.
312 Documentation of path_list can be found in devserver.py:xbuddy
joychen3cb228e2013-06-12 12:13:13 -0700313
314 Return:
joychenf8f07e22013-07-12 17:45:51 -0700315 tuple of (image_type, board, version)
joychen3cb228e2013-06-12 12:13:13 -0700316
317 Raises:
318 XBuddyException: if the path can't be resolved into valid components
319 """
joychen7df67f72013-07-18 14:21:12 -0700320 path_list = list(path_list)
321
322 # Required parts of path parsing.
323 try:
324 # Determine if image is explicitly local or remote.
325 is_local = False
326 if path_list[0] == LOCAL:
327 path_list.pop(0)
328 is_local = True
329 elif path_list[0] == REMOTE:
330 path_list.pop(0)
joychen7df67f72013-07-18 14:21:12 -0700331
332 # Set board
333 board = path_list.pop(0)
joychen7df67f72013-07-18 14:21:12 -0700334
335 # Set defaults
joychen3cb228e2013-06-12 12:13:13 -0700336 version = LATEST
joychen921e1fb2013-06-28 11:12:20 -0700337 image_type = GS_ALIASES[0]
joychen7df67f72013-07-18 14:21:12 -0700338 except IndexError:
339 msg = "Specify at least the board in your xBuddy call. Your path: %s"
340 raise XBuddyException(msg % os.path.join(path_list))
joychen3cb228e2013-06-12 12:13:13 -0700341
joychen7df67f72013-07-18 14:21:12 -0700342 # Read as much of the xBuddy path as possible
343 try:
344 # Override default if terminal is a valid artifact alias or a version
345 terminal = path_list[-1]
346 if terminal in GS_ALIASES + LOCAL_ALIASES:
347 image_type = terminal
348 version = path_list[-2]
349 else:
350 version = terminal
351 except IndexError:
352 # This path doesn't have an alias or a version. That's fine.
353 _Log("Some parts of the path not specified. Using defaults.")
354
joychen346531c2013-07-24 16:55:56 -0700355 _Log("Get artifact '%s' in '%s/%s'. Locally? %s",
joychen7df67f72013-07-18 14:21:12 -0700356 image_type, board, version, is_local)
357
358 return image_type, board, version, is_local
joychen3cb228e2013-06-12 12:13:13 -0700359
joychen921e1fb2013-06-28 11:12:20 -0700360 def _SyncRegistryWithBuildImages(self):
joychen5260b9a2013-07-16 14:48:01 -0700361 """ Crawl images_dir for build_ids of images generated from build_image.
362
363 This will find images and symlink them in xBuddy's static dir so that
364 xBuddy's cache can serve them.
365 If xBuddy's _manage_builds option is on, then a timestamp will also be
366 generated, and xBuddy will clear them from the directory they are in, as
367 necessary.
368 """
joychen921e1fb2013-06-28 11:12:20 -0700369 build_ids = []
370 for b in os.listdir(self.images_dir):
joychen5260b9a2013-07-16 14:48:01 -0700371 # Ensure we have directories to track all boards in build/images
372 common_util.MkDirP(os.path.join(self.static_dir, b))
joychen921e1fb2013-06-28 11:12:20 -0700373 board_dir = os.path.join(self.images_dir, b)
374 build_ids.extend(['/'.join([b, v]) for v
375 in os.listdir(board_dir) if not v==LATEST])
376
377 # Check currently registered images
378 for f in os.listdir(self._timestamp_folder):
379 build_id = Timestamp.TimestampToBuild(f)
380 if build_id in build_ids:
381 build_ids.remove(build_id)
382
joychen5260b9a2013-07-16 14:48:01 -0700383 # Symlink undiscovered images, and update timestamps if manage_builds is on
384 for build_id in build_ids:
385 link = os.path.join(self.static_dir, build_id)
386 target = os.path.join(self.images_dir, build_id)
387 XBuddy._Symlink(link, target)
388 if self._manage_builds:
389 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen921e1fb2013-06-28 11:12:20 -0700390
391 def _ListBuildTimes(self):
joychen3cb228e2013-06-12 12:13:13 -0700392 """ Returns the currently cached builds and their last access timestamp.
393
394 Returns:
395 list of tuples that matches xBuddy build/version to timestamps in long
396 """
397 # update currently cached builds
398 build_dict = {}
399
joychen7df67f72013-07-18 14:21:12 -0700400 for f in os.listdir(self._timestamp_folder):
joychen3cb228e2013-06-12 12:13:13 -0700401 last_accessed = os.path.getmtime(os.path.join(self._timestamp_folder, f))
402 build_id = Timestamp.TimestampToBuild(f)
403 stale_time = datetime.timedelta(seconds = (time.time()-last_accessed))
joychen921e1fb2013-06-28 11:12:20 -0700404 build_dict[build_id] = stale_time
joychen3cb228e2013-06-12 12:13:13 -0700405 return_tup = sorted(build_dict.iteritems(), key=operator.itemgetter(1))
406 return return_tup
407
joychen3cb228e2013-06-12 12:13:13 -0700408 def _Download(self, gs_url, artifact):
409 """Download the single artifact from the given gs_url."""
410 with XBuddy._staging_thread_count_lock:
411 XBuddy._staging_thread_count += 1
412 try:
joychen7df67f72013-07-18 14:21:12 -0700413 _Log("Downloading '%s' from '%s'", artifact, gs_url)
joychen921e1fb2013-06-28 11:12:20 -0700414 downloader.Downloader(self.static_dir, gs_url).Download(
joychen3cb228e2013-06-12 12:13:13 -0700415 [artifact])
416 finally:
417 with XBuddy._staging_thread_count_lock:
418 XBuddy._staging_thread_count -= 1
419
420 def _CleanCache(self):
421 """Delete all builds besides the first _XBUDDY_CAPACITY builds"""
joychen921e1fb2013-06-28 11:12:20 -0700422 cached_builds = [e[0] for e in self._ListBuildTimes()]
joychen3cb228e2013-06-12 12:13:13 -0700423 _Log('In cache now: %s', cached_builds)
424
425 for b in range(_XBUDDY_CAPACITY, len(cached_builds)):
426 b_path = cached_builds[b]
joychen7df67f72013-07-18 14:21:12 -0700427 _Log("Clearing '%s' from cache", b_path)
joychen3cb228e2013-06-12 12:13:13 -0700428
429 time_file = os.path.join(self._timestamp_folder,
430 Timestamp.BuildToTimestamp(b_path))
joychen921e1fb2013-06-28 11:12:20 -0700431 os.unlink(time_file)
432 clear_dir = os.path.join(self.static_dir, b_path)
joychen3cb228e2013-06-12 12:13:13 -0700433 try:
joychen5260b9a2013-07-16 14:48:01 -0700434 # handle symlinks, in the case of links to local builds if enabled
435 if self._manage_builds and os.path.islink(clear_dir):
436 target = os.readlink(clear_dir)
437 _Log('Deleting locally built image at %s', target)
joychen921e1fb2013-06-28 11:12:20 -0700438
439 os.unlink(clear_dir)
joychen5260b9a2013-07-16 14:48:01 -0700440 if os.path.exists(target):
joychen921e1fb2013-06-28 11:12:20 -0700441 shutil.rmtree(target)
442 elif os.path.exists(clear_dir):
joychen5260b9a2013-07-16 14:48:01 -0700443 _Log('Deleting downloaded image at %s', clear_dir)
joychen3cb228e2013-06-12 12:13:13 -0700444 shutil.rmtree(clear_dir)
joychen921e1fb2013-06-28 11:12:20 -0700445
joychen3cb228e2013-06-12 12:13:13 -0700446 except Exception:
447 raise XBuddyException('Failed to clear build in %s.' % clear_dir)
448
joychen346531c2013-07-24 16:55:56 -0700449 def _GetFromGS(self, build_id, image_type, lookup_only):
450 """Check if the artifact is available locally. Download from GS if not.
451
452 Return:
453 boolean - True if cached.
454 """
joychenf8f07e22013-07-12 17:45:51 -0700455 gs_url = os.path.join(devserver_constants.GS_IMAGE_DIR,
joychen921e1fb2013-06-28 11:12:20 -0700456 build_id)
457
458 # stage image if not found in cache
459 file_name = GS_ALIAS_TO_FILENAME[image_type]
joychen346531c2013-07-24 16:55:56 -0700460 file_loc = os.path.join(self.static_dir, build_id, file_name)
461 cached = os.path.exists(file_loc)
462
joychen921e1fb2013-06-28 11:12:20 -0700463 if not cached:
joychen346531c2013-07-24 16:55:56 -0700464 if lookup_only:
465 return False
466 else:
467 artifact = GS_ALIAS_TO_ARTIFACT[image_type]
468 self._Download(gs_url, artifact)
469 return True
joychen921e1fb2013-06-28 11:12:20 -0700470 else:
471 _Log('Image already cached.')
joychen346531c2013-07-24 16:55:56 -0700472 return True
joychen921e1fb2013-06-28 11:12:20 -0700473
joychen346531c2013-07-24 16:55:56 -0700474 def _GetArtifact(self, path, lookup_only=False):
475 """Interpret an xBuddy path and return directory/file_name to resource.
476
477 Returns:
478 image_url to the directory
479 file_name of the artifact
480 found = True if the artifact is cached
481
482 Raises:
483 XBuddyException if the path could not be translated
484 """
joychenb0dfe552013-07-30 10:02:06 -0700485 # Rewrite the path if there is an appropriate default.
486 path = self._path_lookup_table.get('/'.join(path), path)
487
488 # Parse the path
joychen7df67f72013-07-18 14:21:12 -0700489 image_type, board, version, is_local = self._InterpretPath(path)
joychen921e1fb2013-06-28 11:12:20 -0700490
joychen346531c2013-07-24 16:55:56 -0700491 found = False
joychen7df67f72013-07-18 14:21:12 -0700492 if is_local:
joychen921e1fb2013-06-28 11:12:20 -0700493 # Get a local image
494 if image_type not in LOCAL_ALIASES:
joychen7df67f72013-07-18 14:21:12 -0700495 raise XBuddyException('Bad local image type: %s. Use one of: %s' %
joychen921e1fb2013-06-28 11:12:20 -0700496 (image_type, LOCAL_ALIASES))
joychen921e1fb2013-06-28 11:12:20 -0700497 file_name = LOCAL_ALIAS_TO_FILENAME[image_type]
joychen7df67f72013-07-18 14:21:12 -0700498
499 if version == LATEST:
500 # Get the latest local image for the given board
joychen346531c2013-07-24 16:55:56 -0700501 version, found = self._GetLatestLocalVersion(board, file_name)
joychen7df67f72013-07-18 14:21:12 -0700502 else:
503 # An exact version path in build/images was specified for this board
504 local_file = os.path.join(self.images_dir, board, version, file_name)
joychen346531c2013-07-24 16:55:56 -0700505 if os.path.exists(local_file):
506 found = True
joychen7df67f72013-07-18 14:21:12 -0700507
joychenf8f07e22013-07-12 17:45:51 -0700508 image_url = os.path.join(board, version)
joychen921e1fb2013-06-28 11:12:20 -0700509 else:
510 # Get a remote image
511 if image_type not in GS_ALIASES:
joychen7df67f72013-07-18 14:21:12 -0700512 raise XBuddyException('Bad remote image type: %s. Use one of: %s' %
joychen921e1fb2013-06-28 11:12:20 -0700513 (image_type, GS_ALIASES))
joychen921e1fb2013-06-28 11:12:20 -0700514 file_name = GS_ALIAS_TO_FILENAME[image_type]
joychen921e1fb2013-06-28 11:12:20 -0700515
joychenf8f07e22013-07-12 17:45:51 -0700516 # Interpret the version (alias), and get gs address
517 image_url = self._ResolveVersionToUrl(board, version)
joychen346531c2013-07-24 16:55:56 -0700518 found = self._GetFromGS(image_url, image_type, lookup_only)
joychenf8f07e22013-07-12 17:45:51 -0700519
joychen346531c2013-07-24 16:55:56 -0700520 return image_url, file_name, found
joychen3cb228e2013-06-12 12:13:13 -0700521
522 ############################ BEGIN PUBLIC METHODS
523
524 def List(self):
525 """Lists the currently available images & time since last access."""
joychen921e1fb2013-06-28 11:12:20 -0700526 self._SyncRegistryWithBuildImages()
527 builds = self._ListBuildTimes()
528 return_string = ''
529 for build, timestamp in builds:
530 return_string += '<b>' + build + '</b> '
531 return_string += '(time since last access: ' + str(timestamp) + ')<br>'
532 return return_string
joychen3cb228e2013-06-12 12:13:13 -0700533
534 def Capacity(self):
535 """Returns the number of images cached by xBuddy."""
536 return str(_XBUDDY_CAPACITY)
537
joychen346531c2013-07-24 16:55:56 -0700538 def Translate(self, path_list):
539 """Translates an xBuddy path to a real path to artifact if it exists.
540
541 Equivalent to the Get call, minus downloading and updating timestamps.
542 The returned path is always the path to the directory.
543
joychen7c2054a2013-07-25 11:14:07 -0700544 Returns:
545 build_id - Path to the image or update directory on the devserver.
546 e.g. x86-generic/R26-4000.0.0/chromium-test-image.bin
547 or x86-generic/R26-4000.0.0/
548
549 found - Whether or not the given artifact is currently cached.
550
joychen346531c2013-07-24 16:55:56 -0700551 Throws:
552 XBuddyException - if the path couldn't be translated
553 """
554 self._SyncRegistryWithBuildImages()
555
556 build_id, _file_name, found = self._GetArtifact(path_list, lookup_only=True)
557
558 _Log('Returning path to payload: %s', build_id)
559 return build_id, found
560
joychen921e1fb2013-06-28 11:12:20 -0700561 def Get(self, path_list, return_dir=False):
562 """The full xBuddy call, returns resource specified by path_list.
joychen3cb228e2013-06-12 12:13:13 -0700563
564 Please see devserver.py:xbuddy for full documentation.
565 Args:
joychen921e1fb2013-06-28 11:12:20 -0700566 path_list: [board, version, alias] as split from the xbuddy call url
joychen3cb228e2013-06-12 12:13:13 -0700567 return_dir: boolean, if set to true, returns the dir name instead.
568
569 Returns:
570 Path to the image or update directory on the devserver.
joychen7c2054a2013-07-25 11:14:07 -0700571 e.g. x86-generic/R26-4000.0.0/chromium-test-image.bin
572 or x86-generic/R26-4000.0.0/
joychen3cb228e2013-06-12 12:13:13 -0700573
574 Raises:
joychen346531c2013-07-24 16:55:56 -0700575 XBuddyException if path is invalid
joychen3cb228e2013-06-12 12:13:13 -0700576 """
joychen7df67f72013-07-18 14:21:12 -0700577 self._SyncRegistryWithBuildImages()
joychen346531c2013-07-24 16:55:56 -0700578 build_id, file_name, _found = self._GetArtifact(path_list)
joychen3cb228e2013-06-12 12:13:13 -0700579
joychen921e1fb2013-06-28 11:12:20 -0700580 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen3cb228e2013-06-12 12:13:13 -0700581
582 #TODO (joyc): run in sep thread
583 self._CleanCache()
584
joychen921e1fb2013-06-28 11:12:20 -0700585 return_url = os.path.join('static', build_id)
joychen3cb228e2013-06-12 12:13:13 -0700586 if not return_dir:
587 return_url = os.path.join(return_url, file_name)
588
589 _Log('Returning path to payload: %s', return_url)
590 return return_url