blob: f517ea96dc035201120bba08cc8ce8da7b9f37fa [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
joychen3cb228e2013-06-12 12:13:13 -070015import common_util
16import devserver_constants
17import downloader
joychenf8f07e22013-07-12 17:45:51 -070018import gsutil_util
joychen3cb228e2013-06-12 12:13:13 -070019import log_util
joychenb0dfe552013-07-30 10:02:06 -070020import xbuddy_lookup_table
joychen3cb228e2013-06-12 12:13:13 -070021
22# Module-local log function.
23def _Log(message, *args):
24 return log_util.LogWithTag('XBUDDY', message, *args)
25
joychen3cb228e2013-06-12 12:13:13 -070026_XBUDDY_CAPACITY = 5
joychen921e1fb2013-06-28 11:12:20 -070027
joychen25d25972013-07-30 14:54:16 -070028# XBuddy aliases
29TEST = 'test'
30BASE = 'base'
31DEV = 'dev'
32FULL = 'full_payload'
33RECOVERY = 'recovery'
34STATEFUL = 'stateful'
35AUTOTEST = 'autotest'
36
joychen921e1fb2013-06-28 11:12:20 -070037# Local build constants
joychen7df67f72013-07-18 14:21:12 -070038LATEST = "latest"
39LOCAL = "local"
40REMOTE = "remote"
joychen921e1fb2013-06-28 11:12:20 -070041LOCAL_ALIASES = [
joychen25d25972013-07-30 14:54:16 -070042 TEST,
43 BASE,
44 DEV,
45 FULL
joychen921e1fb2013-06-28 11:12:20 -070046]
47
48LOCAL_FILE_NAMES = [
49 devserver_constants.TEST_IMAGE_FILE,
50 devserver_constants.BASE_IMAGE_FILE,
51 devserver_constants.IMAGE_FILE,
joychen7c2054a2013-07-25 11:14:07 -070052 devserver_constants.UPDATE_FILE,
joychen921e1fb2013-06-28 11:12:20 -070053]
54
55LOCAL_ALIAS_TO_FILENAME = dict(zip(LOCAL_ALIASES, LOCAL_FILE_NAMES))
56
57# Google Storage constants
58GS_ALIASES = [
joychen25d25972013-07-30 14:54:16 -070059 TEST,
60 BASE,
61 RECOVERY,
62 FULL,
63 STATEFUL,
64 AUTOTEST,
joychen3cb228e2013-06-12 12:13:13 -070065]
66
joychen921e1fb2013-06-28 11:12:20 -070067GS_FILE_NAMES = [
68 devserver_constants.TEST_IMAGE_FILE,
69 devserver_constants.BASE_IMAGE_FILE,
70 devserver_constants.RECOVERY_IMAGE_FILE,
joychen7c2054a2013-07-25 11:14:07 -070071 devserver_constants.UPDATE_FILE,
joychen25d25972013-07-30 14:54:16 -070072 devserver_constants.STATEFUL_UPDATE_FILE,
joychen3cb228e2013-06-12 12:13:13 -070073 devserver_constants.AUTOTEST_DIR,
74]
75
76ARTIFACTS = [
77 artifact_info.TEST_IMAGE,
78 artifact_info.BASE_IMAGE,
79 artifact_info.RECOVERY_IMAGE,
80 artifact_info.FULL_PAYLOAD,
81 artifact_info.STATEFUL_PAYLOAD,
82 artifact_info.AUTOTEST,
83]
84
joychen921e1fb2013-06-28 11:12:20 -070085GS_ALIAS_TO_FILENAME = dict(zip(GS_ALIASES, GS_FILE_NAMES))
86GS_ALIAS_TO_ARTIFACT = dict(zip(GS_ALIASES, ARTIFACTS))
joychen3cb228e2013-06-12 12:13:13 -070087
joychen921e1fb2013-06-28 11:12:20 -070088LATEST_OFFICIAL = "latest-official"
joychen3cb228e2013-06-12 12:13:13 -070089
joychenf8f07e22013-07-12 17:45:51 -070090RELEASE = "release"
joychen3cb228e2013-06-12 12:13:13 -070091
joychen3cb228e2013-06-12 12:13:13 -070092
93class XBuddyException(Exception):
94 """Exception classes used by this module."""
95 pass
96
97
98# no __init__ method
99#pylint: disable=W0232
100class Timestamp():
101 """Class to translate build path strings and timestamp filenames."""
102
103 _TIMESTAMP_DELIMITER = 'SLASH'
104 XBUDDY_TIMESTAMP_DIR = 'xbuddy_UpdateTimestamps'
105
106 @staticmethod
107 def TimestampToBuild(timestamp_filename):
108 return timestamp_filename.replace(Timestamp._TIMESTAMP_DELIMITER, '/')
109
110 @staticmethod
111 def BuildToTimestamp(build_path):
112 return build_path.replace('/', Timestamp._TIMESTAMP_DELIMITER)
joychen921e1fb2013-06-28 11:12:20 -0700113
114 @staticmethod
115 def UpdateTimestamp(timestamp_dir, build_id):
116 """Update timestamp file of build with build_id."""
117 common_util.MkDirP(timestamp_dir)
118 time_file = os.path.join(timestamp_dir,
119 Timestamp.BuildToTimestamp(build_id))
120 with file(time_file, 'a'):
121 os.utime(time_file, None)
joychen3cb228e2013-06-12 12:13:13 -0700122#pylint: enable=W0232
123
124
joychen921e1fb2013-06-28 11:12:20 -0700125class XBuddy(build_util.BuildObject):
joychen3cb228e2013-06-12 12:13:13 -0700126 """Class that manages image retrieval and caching by the devserver.
127
128 Image retrieval by xBuddy path:
129 XBuddy accesses images and artifacts that it stores using an xBuddy
130 path of the form: board/version/alias
131 The primary xbuddy.Get call retrieves the correct artifact or url to where
132 the artifacts can be found.
133
134 Image caching:
135 Images and other artifacts are stored identically to how they would have
136 been if devserver's stage rpc was called and the xBuddy cache replaces
137 build versions on a LRU basis. Timestamps are maintained by last accessed
138 times of representative files in the a directory in the static serve
139 directory (XBUDDY_TIMESTAMP_DIR).
140
141 Private class members:
142 _true_values - used for interpreting boolean values
143 _staging_thread_count - track download requests
joychen921e1fb2013-06-28 11:12:20 -0700144 _timestamp_folder - directory with empty files standing in as timestamps
145 for each image currently cached by xBuddy
joychen3cb228e2013-06-12 12:13:13 -0700146 """
147 _true_values = ['true', 't', 'yes', 'y']
148
149 # Number of threads that are staging images.
150 _staging_thread_count = 0
151 # Lock used to lock increasing/decreasing count.
152 _staging_thread_count_lock = threading.Lock()
153
joychenb0dfe552013-07-30 10:02:06 -0700154 def __init__(self, manage_builds=False, board=None, **kwargs):
joychen921e1fb2013-06-28 11:12:20 -0700155 super(XBuddy, self).__init__(**kwargs)
joychen5260b9a2013-07-16 14:48:01 -0700156 self._manage_builds = manage_builds
joychenb0dfe552013-07-30 10:02:06 -0700157
158 # Choose a default board, using the --board flag if given, or
159 # src/scripts/.default_board if it exists.
160 # Default to x86-generic, if that isn't set.
161 self._board = (board or self.GetDefaultBoardID())
162 _Log("Default board used by xBuddy: %s", self._board)
163 self._path_lookup_table = xbuddy_lookup_table.paths(self._board)
164
joychen921e1fb2013-06-28 11:12:20 -0700165 self._timestamp_folder = os.path.join(self.static_dir,
joychen3cb228e2013-06-12 12:13:13 -0700166 Timestamp.XBUDDY_TIMESTAMP_DIR)
joychen7df67f72013-07-18 14:21:12 -0700167 common_util.MkDirP(self._timestamp_folder)
joychen3cb228e2013-06-12 12:13:13 -0700168
169 @classmethod
170 def ParseBoolean(cls, boolean_string):
171 """Evaluate a string to a boolean value"""
172 if boolean_string:
173 return boolean_string.lower() in cls._true_values
174 else:
175 return False
176
joychenf8f07e22013-07-12 17:45:51 -0700177 def _LookupOfficial(self, board, suffix=RELEASE):
178 """Check LATEST-master for the version number of interest."""
179 _Log("Checking gs for latest %s-%s image", board, suffix)
180 latest_addr = devserver_constants.GS_LATEST_MASTER % {'board':board,
181 'suffix':suffix}
182 cmd = 'gsutil cat %s' % latest_addr
183 msg = 'Failed to find build at %s' % latest_addr
184 # Full release + version is in the LATEST file
185 version = gsutil_util.GSUtilRun(cmd, msg)
joychen3cb228e2013-06-12 12:13:13 -0700186
joychenf8f07e22013-07-12 17:45:51 -0700187 return devserver_constants.IMAGE_DIR % {'board':board,
188 'suffix':suffix,
189 'version':version}
190
191 def _LookupChannel(self, board, channel='stable'):
192 """Check the channel folder for the version number of interest."""
193 # Get all names in channel dir. Get 10 highest directories by version
joychen7df67f72013-07-18 14:21:12 -0700194 _Log("Checking channel '%s' for latest '%s' image", channel, board)
joychenf8f07e22013-07-12 17:45:51 -0700195 channel_dir = devserver_constants.GS_CHANNEL_DIR % {'channel':channel,
196 'board':board}
197 latest_version = gsutil_util.GetLatestVersionFromGSDir(channel_dir)
198
199 # Figure out release number from the version number
200 image_url = devserver_constants.IMAGE_DIR % {'board':board,
201 'suffix':RELEASE,
202 'version':'R*'+latest_version}
203 image_dir = os.path.join(devserver_constants.GS_IMAGE_DIR, image_url)
204
205 # There should only be one match on cros-image-archive.
206 full_version = gsutil_util.GetLatestVersionFromGSDir(image_dir)
207
208 return devserver_constants.IMAGE_DIR % {'board':board,
209 'suffix':RELEASE,
210 'version':full_version}
211
212 def _LookupVersion(self, board, version):
213 """Search GS image releases for the highest match to a version prefix."""
214 # Build the pattern for GS to match
joychen7df67f72013-07-18 14:21:12 -0700215 _Log("Checking gs for latest '%s' image with prefix '%s'", board, version)
joychenf8f07e22013-07-12 17:45:51 -0700216 image_url = devserver_constants.IMAGE_DIR % {'board':board,
217 'suffix':RELEASE,
218 'version':version + '*'}
219 image_dir = os.path.join(devserver_constants.GS_IMAGE_DIR, image_url)
220
221 # grab the newest version of the ones matched
222 full_version = gsutil_util.GetLatestVersionFromGSDir(image_dir)
223 return devserver_constants.IMAGE_DIR % {'board':board,
224 'suffix':RELEASE,
225 'version':full_version}
226
227 def _ResolveVersionToUrl(self, board, version):
joychen3cb228e2013-06-12 12:13:13 -0700228 """
joychen921e1fb2013-06-28 11:12:20 -0700229 Handle version aliases for remote payloads in GS.
joychen3cb228e2013-06-12 12:13:13 -0700230
231 Args:
232 board: as specified in the original call. (i.e. x86-generic, parrot)
233 version: as entered in the original call. can be
234 {TBD, 0. some custom alias as defined in a config file}
235 1. latest
236 2. latest-{channel}
237 3. latest-official-{board suffix}
238 4. version prefix (i.e. RX-Y.X, RX-Y, RX)
joychen3cb228e2013-06-12 12:13:13 -0700239
240 Returns:
joychenf8f07e22013-07-12 17:45:51 -0700241 image_url is where the image dir is actually found on GS
joychen3cb228e2013-06-12 12:13:13 -0700242
243 """
joychenf8f07e22013-07-12 17:45:51 -0700244 # TODO (joychen) Convert separate calls to a dict + error out bad paths
joychen3cb228e2013-06-12 12:13:13 -0700245
joychenf8f07e22013-07-12 17:45:51 -0700246 # Only the last segment of the alias is variable relative to the rest.
247 version_tuple = version.rsplit('-', 1)
joychen3cb228e2013-06-12 12:13:13 -0700248
joychenf8f07e22013-07-12 17:45:51 -0700249 if re.match(devserver_constants.VERSION_RE, version):
250 # This is supposed to be a complete version number on GS. Return it.
251 return devserver_constants.IMAGE_DIR % {'board':board,
252 'suffix':RELEASE,
253 'version':version}
254 elif version == LATEST_OFFICIAL:
255 # latest-official --> LATEST build in board-release
256 return self._LookupOfficial(board)
257 elif version_tuple[0] == LATEST_OFFICIAL:
258 # latest-official-{suffix} --> LATEST build in board-{suffix}
259 return self._LookupOfficial(board, version_tuple[1])
260 elif version == LATEST:
261 # latest --> latest build on stable channel
262 return self._LookupChannel(board)
263 elif version_tuple[0] == LATEST:
264 if re.match(devserver_constants.VERSION_RE, version_tuple[1]):
265 # latest-R* --> most recent qualifying build
266 return self._LookupVersion(board, version_tuple[1])
267 else:
268 # latest-{channel} --> latest build within that channel
269 return self._LookupChannel(board, version_tuple[1])
joychen3cb228e2013-06-12 12:13:13 -0700270 else:
271 # The given version doesn't match any known patterns.
joychen921e1fb2013-06-28 11:12:20 -0700272 raise XBuddyException("Version %s unknown. Can't find on GS." % version)
joychen3cb228e2013-06-12 12:13:13 -0700273
joychen5260b9a2013-07-16 14:48:01 -0700274 @staticmethod
275 def _Symlink(link, target):
276 """Symlinks link to target, and removes whatever link was there before."""
277 _Log("Linking to %s from %s", link, target)
278 if os.path.lexists(link):
279 os.unlink(link)
280 os.symlink(target, link)
281
joychen921e1fb2013-06-28 11:12:20 -0700282 def _GetLatestLocalVersion(self, board, file_name):
283 """Get the version of the latest image built for board by build_image
284
285 Updates the symlink reference within the xBuddy static dir to point to
286 the real image dir in the local /build/images directory.
287
288 Args:
289 board - board-suffix
290 file_name - the filename of the image we have cached
291
292 Returns:
293 version - the discovered version of the image.
joychen346531c2013-07-24 16:55:56 -0700294 found - True if file was found
joychen3cb228e2013-06-12 12:13:13 -0700295 """
joychen921e1fb2013-06-28 11:12:20 -0700296 latest_local_dir = self.GetLatestImageDir(board)
joychenb0dfe552013-07-30 10:02:06 -0700297 if not latest_local_dir or not os.path.exists(latest_local_dir):
joychen921e1fb2013-06-28 11:12:20 -0700298 raise XBuddyException('No builds found for %s. Did you run build_image?' %
299 board)
300
301 # assume that the version number is the name of the directory
302 version = os.path.basename(latest_local_dir)
303
304 path_to_image = os.path.join(latest_local_dir, file_name)
joychen346531c2013-07-24 16:55:56 -0700305 if os.path.exists(path_to_image):
306 return version, True
307 else:
308 return version, False
joychen921e1fb2013-06-28 11:12:20 -0700309
310 def _InterpretPath(self, path_list):
311 """
312 Split and return the pieces of an xBuddy path name
joychen3cb228e2013-06-12 12:13:13 -0700313
314 input:
joychen921e1fb2013-06-28 11:12:20 -0700315 path_list: the segments of the path xBuddy Get was called with.
316 Documentation of path_list can be found in devserver.py:xbuddy
joychen3cb228e2013-06-12 12:13:13 -0700317
318 Return:
joychenf8f07e22013-07-12 17:45:51 -0700319 tuple of (image_type, board, version)
joychen3cb228e2013-06-12 12:13:13 -0700320
321 Raises:
322 XBuddyException: if the path can't be resolved into valid components
323 """
joychen7df67f72013-07-18 14:21:12 -0700324 path_list = list(path_list)
325
326 # Required parts of path parsing.
327 try:
328 # Determine if image is explicitly local or remote.
329 is_local = False
330 if path_list[0] == LOCAL:
331 path_list.pop(0)
332 is_local = True
333 elif path_list[0] == REMOTE:
334 path_list.pop(0)
joychen7df67f72013-07-18 14:21:12 -0700335
336 # Set board
337 board = path_list.pop(0)
joychen7df67f72013-07-18 14:21:12 -0700338
339 # Set defaults
joychen3cb228e2013-06-12 12:13:13 -0700340 version = LATEST
joychen921e1fb2013-06-28 11:12:20 -0700341 image_type = GS_ALIASES[0]
joychen7df67f72013-07-18 14:21:12 -0700342 except IndexError:
343 msg = "Specify at least the board in your xBuddy call. Your path: %s"
344 raise XBuddyException(msg % os.path.join(path_list))
joychen3cb228e2013-06-12 12:13:13 -0700345
joychen7df67f72013-07-18 14:21:12 -0700346 # Read as much of the xBuddy path as possible
347 try:
348 # Override default if terminal is a valid artifact alias or a version
349 terminal = path_list[-1]
350 if terminal in GS_ALIASES + LOCAL_ALIASES:
351 image_type = terminal
352 version = path_list[-2]
353 else:
354 version = terminal
355 except IndexError:
356 # This path doesn't have an alias or a version. That's fine.
357 _Log("Some parts of the path not specified. Using defaults.")
358
joychen346531c2013-07-24 16:55:56 -0700359 _Log("Get artifact '%s' in '%s/%s'. Locally? %s",
joychen7df67f72013-07-18 14:21:12 -0700360 image_type, board, version, is_local)
361
362 return image_type, board, version, is_local
joychen3cb228e2013-06-12 12:13:13 -0700363
joychen921e1fb2013-06-28 11:12:20 -0700364 def _SyncRegistryWithBuildImages(self):
joychen5260b9a2013-07-16 14:48:01 -0700365 """ Crawl images_dir for build_ids of images generated from build_image.
366
367 This will find images and symlink them in xBuddy's static dir so that
368 xBuddy's cache can serve them.
369 If xBuddy's _manage_builds option is on, then a timestamp will also be
370 generated, and xBuddy will clear them from the directory they are in, as
371 necessary.
372 """
joychen921e1fb2013-06-28 11:12:20 -0700373 build_ids = []
374 for b in os.listdir(self.images_dir):
joychen5260b9a2013-07-16 14:48:01 -0700375 # Ensure we have directories to track all boards in build/images
376 common_util.MkDirP(os.path.join(self.static_dir, b))
joychen921e1fb2013-06-28 11:12:20 -0700377 board_dir = os.path.join(self.images_dir, b)
378 build_ids.extend(['/'.join([b, v]) for v
379 in os.listdir(board_dir) if not v==LATEST])
380
381 # Check currently registered images
382 for f in os.listdir(self._timestamp_folder):
383 build_id = Timestamp.TimestampToBuild(f)
384 if build_id in build_ids:
385 build_ids.remove(build_id)
386
joychen5260b9a2013-07-16 14:48:01 -0700387 # Symlink undiscovered images, and update timestamps if manage_builds is on
388 for build_id in build_ids:
389 link = os.path.join(self.static_dir, build_id)
390 target = os.path.join(self.images_dir, build_id)
391 XBuddy._Symlink(link, target)
392 if self._manage_builds:
393 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen921e1fb2013-06-28 11:12:20 -0700394
395 def _ListBuildTimes(self):
joychen3cb228e2013-06-12 12:13:13 -0700396 """ Returns the currently cached builds and their last access timestamp.
397
398 Returns:
399 list of tuples that matches xBuddy build/version to timestamps in long
400 """
401 # update currently cached builds
402 build_dict = {}
403
joychen7df67f72013-07-18 14:21:12 -0700404 for f in os.listdir(self._timestamp_folder):
joychen3cb228e2013-06-12 12:13:13 -0700405 last_accessed = os.path.getmtime(os.path.join(self._timestamp_folder, f))
406 build_id = Timestamp.TimestampToBuild(f)
407 stale_time = datetime.timedelta(seconds = (time.time()-last_accessed))
joychen921e1fb2013-06-28 11:12:20 -0700408 build_dict[build_id] = stale_time
joychen3cb228e2013-06-12 12:13:13 -0700409 return_tup = sorted(build_dict.iteritems(), key=operator.itemgetter(1))
410 return return_tup
411
joychen3cb228e2013-06-12 12:13:13 -0700412 def _Download(self, gs_url, artifact):
413 """Download the single artifact from the given gs_url."""
414 with XBuddy._staging_thread_count_lock:
415 XBuddy._staging_thread_count += 1
416 try:
joychen7df67f72013-07-18 14:21:12 -0700417 _Log("Downloading '%s' from '%s'", artifact, gs_url)
joychen921e1fb2013-06-28 11:12:20 -0700418 downloader.Downloader(self.static_dir, gs_url).Download(
joychen3cb228e2013-06-12 12:13:13 -0700419 [artifact])
420 finally:
421 with XBuddy._staging_thread_count_lock:
422 XBuddy._staging_thread_count -= 1
423
424 def _CleanCache(self):
425 """Delete all builds besides the first _XBUDDY_CAPACITY builds"""
joychen921e1fb2013-06-28 11:12:20 -0700426 cached_builds = [e[0] for e in self._ListBuildTimes()]
joychen3cb228e2013-06-12 12:13:13 -0700427 _Log('In cache now: %s', cached_builds)
428
429 for b in range(_XBUDDY_CAPACITY, len(cached_builds)):
430 b_path = cached_builds[b]
joychen7df67f72013-07-18 14:21:12 -0700431 _Log("Clearing '%s' from cache", b_path)
joychen3cb228e2013-06-12 12:13:13 -0700432
433 time_file = os.path.join(self._timestamp_folder,
434 Timestamp.BuildToTimestamp(b_path))
joychen921e1fb2013-06-28 11:12:20 -0700435 os.unlink(time_file)
436 clear_dir = os.path.join(self.static_dir, b_path)
joychen3cb228e2013-06-12 12:13:13 -0700437 try:
joychen5260b9a2013-07-16 14:48:01 -0700438 # handle symlinks, in the case of links to local builds if enabled
439 if self._manage_builds and os.path.islink(clear_dir):
440 target = os.readlink(clear_dir)
441 _Log('Deleting locally built image at %s', target)
joychen921e1fb2013-06-28 11:12:20 -0700442
443 os.unlink(clear_dir)
joychen5260b9a2013-07-16 14:48:01 -0700444 if os.path.exists(target):
joychen921e1fb2013-06-28 11:12:20 -0700445 shutil.rmtree(target)
446 elif os.path.exists(clear_dir):
joychen5260b9a2013-07-16 14:48:01 -0700447 _Log('Deleting downloaded image at %s', clear_dir)
joychen3cb228e2013-06-12 12:13:13 -0700448 shutil.rmtree(clear_dir)
joychen921e1fb2013-06-28 11:12:20 -0700449
joychen3cb228e2013-06-12 12:13:13 -0700450 except Exception:
451 raise XBuddyException('Failed to clear build in %s.' % clear_dir)
452
joychen346531c2013-07-24 16:55:56 -0700453 def _GetFromGS(self, build_id, image_type, lookup_only):
454 """Check if the artifact is available locally. Download from GS if not.
455
456 Return:
457 boolean - True if cached.
458 """
joychenf8f07e22013-07-12 17:45:51 -0700459 gs_url = os.path.join(devserver_constants.GS_IMAGE_DIR,
joychen921e1fb2013-06-28 11:12:20 -0700460 build_id)
461
462 # stage image if not found in cache
463 file_name = GS_ALIAS_TO_FILENAME[image_type]
joychen346531c2013-07-24 16:55:56 -0700464 file_loc = os.path.join(self.static_dir, build_id, file_name)
465 cached = os.path.exists(file_loc)
466
joychen921e1fb2013-06-28 11:12:20 -0700467 if not cached:
joychen346531c2013-07-24 16:55:56 -0700468 if lookup_only:
469 return False
470 else:
471 artifact = GS_ALIAS_TO_ARTIFACT[image_type]
472 self._Download(gs_url, artifact)
473 return True
joychen921e1fb2013-06-28 11:12:20 -0700474 else:
475 _Log('Image already cached.')
joychen346531c2013-07-24 16:55:56 -0700476 return True
joychen921e1fb2013-06-28 11:12:20 -0700477
joychen346531c2013-07-24 16:55:56 -0700478 def _GetArtifact(self, path, lookup_only=False):
479 """Interpret an xBuddy path and return directory/file_name to resource.
480
481 Returns:
482 image_url to the directory
483 file_name of the artifact
484 found = True if the artifact is cached
485
486 Raises:
487 XBuddyException if the path could not be translated
488 """
joychenb0dfe552013-07-30 10:02:06 -0700489 # Rewrite the path if there is an appropriate default.
490 path = self._path_lookup_table.get('/'.join(path), path)
491
492 # Parse the path
joychen7df67f72013-07-18 14:21:12 -0700493 image_type, board, version, is_local = self._InterpretPath(path)
joychen921e1fb2013-06-28 11:12:20 -0700494
joychen346531c2013-07-24 16:55:56 -0700495 found = False
joychen7df67f72013-07-18 14:21:12 -0700496 if is_local:
joychen921e1fb2013-06-28 11:12:20 -0700497 # Get a local image
498 if image_type not in LOCAL_ALIASES:
joychen7df67f72013-07-18 14:21:12 -0700499 raise XBuddyException('Bad local image type: %s. Use one of: %s' %
joychen921e1fb2013-06-28 11:12:20 -0700500 (image_type, LOCAL_ALIASES))
joychen921e1fb2013-06-28 11:12:20 -0700501 file_name = LOCAL_ALIAS_TO_FILENAME[image_type]
joychen7df67f72013-07-18 14:21:12 -0700502
503 if version == LATEST:
504 # Get the latest local image for the given board
joychen346531c2013-07-24 16:55:56 -0700505 version, found = self._GetLatestLocalVersion(board, file_name)
joychen7df67f72013-07-18 14:21:12 -0700506 else:
507 # An exact version path in build/images was specified for this board
508 local_file = os.path.join(self.images_dir, board, version, file_name)
joychen346531c2013-07-24 16:55:56 -0700509 if os.path.exists(local_file):
510 found = True
joychen7df67f72013-07-18 14:21:12 -0700511
joychenf8f07e22013-07-12 17:45:51 -0700512 image_url = os.path.join(board, version)
joychen921e1fb2013-06-28 11:12:20 -0700513 else:
514 # Get a remote image
515 if image_type not in GS_ALIASES:
joychen7df67f72013-07-18 14:21:12 -0700516 raise XBuddyException('Bad remote image type: %s. Use one of: %s' %
joychen921e1fb2013-06-28 11:12:20 -0700517 (image_type, GS_ALIASES))
joychen921e1fb2013-06-28 11:12:20 -0700518 file_name = GS_ALIAS_TO_FILENAME[image_type]
joychen921e1fb2013-06-28 11:12:20 -0700519
joychenf8f07e22013-07-12 17:45:51 -0700520 # Interpret the version (alias), and get gs address
521 image_url = self._ResolveVersionToUrl(board, version)
joychen346531c2013-07-24 16:55:56 -0700522 found = self._GetFromGS(image_url, image_type, lookup_only)
joychenf8f07e22013-07-12 17:45:51 -0700523
joychen346531c2013-07-24 16:55:56 -0700524 return image_url, file_name, found
joychen3cb228e2013-06-12 12:13:13 -0700525
526 ############################ BEGIN PUBLIC METHODS
527
528 def List(self):
529 """Lists the currently available images & time since last access."""
joychen921e1fb2013-06-28 11:12:20 -0700530 self._SyncRegistryWithBuildImages()
531 builds = self._ListBuildTimes()
532 return_string = ''
533 for build, timestamp in builds:
534 return_string += '<b>' + build + '</b> '
535 return_string += '(time since last access: ' + str(timestamp) + ')<br>'
536 return return_string
joychen3cb228e2013-06-12 12:13:13 -0700537
538 def Capacity(self):
539 """Returns the number of images cached by xBuddy."""
540 return str(_XBUDDY_CAPACITY)
541
joychen346531c2013-07-24 16:55:56 -0700542 def Translate(self, path_list):
543 """Translates an xBuddy path to a real path to artifact if it exists.
544
545 Equivalent to the Get call, minus downloading and updating timestamps.
546 The returned path is always the path to the directory.
547
joychen7c2054a2013-07-25 11:14:07 -0700548 Returns:
549 build_id - Path to the image or update directory on the devserver.
550 e.g. x86-generic/R26-4000.0.0/chromium-test-image.bin
551 or x86-generic/R26-4000.0.0/
552
553 found - Whether or not the given artifact is currently cached.
554
joychen346531c2013-07-24 16:55:56 -0700555 Throws:
556 XBuddyException - if the path couldn't be translated
557 """
558 self._SyncRegistryWithBuildImages()
559
560 build_id, _file_name, found = self._GetArtifact(path_list, lookup_only=True)
561
562 _Log('Returning path to payload: %s', build_id)
563 return build_id, found
564
joychen921e1fb2013-06-28 11:12:20 -0700565 def Get(self, path_list, return_dir=False):
566 """The full xBuddy call, returns resource specified by path_list.
joychen3cb228e2013-06-12 12:13:13 -0700567
568 Please see devserver.py:xbuddy for full documentation.
569 Args:
joychen921e1fb2013-06-28 11:12:20 -0700570 path_list: [board, version, alias] as split from the xbuddy call url
joychen3cb228e2013-06-12 12:13:13 -0700571 return_dir: boolean, if set to true, returns the dir name instead.
572
573 Returns:
574 Path to the image or update directory on the devserver.
joychen7c2054a2013-07-25 11:14:07 -0700575 e.g. x86-generic/R26-4000.0.0/chromium-test-image.bin
576 or x86-generic/R26-4000.0.0/
joychen3cb228e2013-06-12 12:13:13 -0700577
578 Raises:
joychen346531c2013-07-24 16:55:56 -0700579 XBuddyException if path is invalid
joychen3cb228e2013-06-12 12:13:13 -0700580 """
joychen7df67f72013-07-18 14:21:12 -0700581 self._SyncRegistryWithBuildImages()
joychen346531c2013-07-24 16:55:56 -0700582 build_id, file_name, _found = self._GetArtifact(path_list)
joychen3cb228e2013-06-12 12:13:13 -0700583
joychen921e1fb2013-06-28 11:12:20 -0700584 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen3cb228e2013-06-12 12:13:13 -0700585
586 #TODO (joyc): run in sep thread
587 self._CleanCache()
588
joychen921e1fb2013-06-28 11:12:20 -0700589 return_url = os.path.join('static', build_id)
joychen3cb228e2013-06-12 12:13:13 -0700590 if not return_dir:
591 return_url = os.path.join(return_url, file_name)
592
593 _Log('Returning path to payload: %s', return_url)
594 return return_url