blob: 1bd4b6d7c088364832cf64151165b1d1125e5b08 [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
21
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
28# Local build constants
joychen7df67f72013-07-18 14:21:12 -070029LATEST = "latest"
30LOCAL = "local"
31REMOTE = "remote"
joychen921e1fb2013-06-28 11:12:20 -070032LOCAL_ALIASES = [
33 'test',
34 'base',
35 'dev',
joychen346531c2013-07-24 16:55:56 -070036 'full_payload',
joychen921e1fb2013-06-28 11:12:20 -070037]
38
39LOCAL_FILE_NAMES = [
40 devserver_constants.TEST_IMAGE_FILE,
41 devserver_constants.BASE_IMAGE_FILE,
42 devserver_constants.IMAGE_FILE,
joychen346531c2013-07-24 16:55:56 -070043 devserver_constants.ROOT_UPDATE_FILE,
joychen921e1fb2013-06-28 11:12:20 -070044]
45
46LOCAL_ALIAS_TO_FILENAME = dict(zip(LOCAL_ALIASES, LOCAL_FILE_NAMES))
47
48# Google Storage constants
49GS_ALIASES = [
joychen3cb228e2013-06-12 12:13:13 -070050 'test',
51 'base',
52 'recovery',
53 'full_payload',
54 'stateful',
55 'autotest',
56]
57
58# TODO(joyc) these should become devserver constants.
59# currently, storage locations are embedded in the artifact classes defined in
60# build_artifact
61
joychen921e1fb2013-06-28 11:12:20 -070062GS_FILE_NAMES = [
63 devserver_constants.TEST_IMAGE_FILE,
64 devserver_constants.BASE_IMAGE_FILE,
65 devserver_constants.RECOVERY_IMAGE_FILE,
joychen3cb228e2013-06-12 12:13:13 -070066 devserver_constants.ROOT_UPDATE_FILE,
67 build_artifact.STATEFUL_UPDATE_FILE,
68 devserver_constants.AUTOTEST_DIR,
69]
70
71ARTIFACTS = [
72 artifact_info.TEST_IMAGE,
73 artifact_info.BASE_IMAGE,
74 artifact_info.RECOVERY_IMAGE,
75 artifact_info.FULL_PAYLOAD,
76 artifact_info.STATEFUL_PAYLOAD,
77 artifact_info.AUTOTEST,
78]
79
joychen921e1fb2013-06-28 11:12:20 -070080GS_ALIAS_TO_FILENAME = dict(zip(GS_ALIASES, GS_FILE_NAMES))
81GS_ALIAS_TO_ARTIFACT = dict(zip(GS_ALIASES, ARTIFACTS))
joychen3cb228e2013-06-12 12:13:13 -070082
joychen921e1fb2013-06-28 11:12:20 -070083LATEST_OFFICIAL = "latest-official"
joychen3cb228e2013-06-12 12:13:13 -070084
joychenf8f07e22013-07-12 17:45:51 -070085RELEASE = "release"
joychen3cb228e2013-06-12 12:13:13 -070086
joychen3cb228e2013-06-12 12:13:13 -070087
88class XBuddyException(Exception):
89 """Exception classes used by this module."""
90 pass
91
92
93# no __init__ method
94#pylint: disable=W0232
95class Timestamp():
96 """Class to translate build path strings and timestamp filenames."""
97
98 _TIMESTAMP_DELIMITER = 'SLASH'
99 XBUDDY_TIMESTAMP_DIR = 'xbuddy_UpdateTimestamps'
100
101 @staticmethod
102 def TimestampToBuild(timestamp_filename):
103 return timestamp_filename.replace(Timestamp._TIMESTAMP_DELIMITER, '/')
104
105 @staticmethod
106 def BuildToTimestamp(build_path):
107 return build_path.replace('/', Timestamp._TIMESTAMP_DELIMITER)
joychen921e1fb2013-06-28 11:12:20 -0700108
109 @staticmethod
110 def UpdateTimestamp(timestamp_dir, build_id):
111 """Update timestamp file of build with build_id."""
112 common_util.MkDirP(timestamp_dir)
113 time_file = os.path.join(timestamp_dir,
114 Timestamp.BuildToTimestamp(build_id))
115 with file(time_file, 'a'):
116 os.utime(time_file, None)
joychen3cb228e2013-06-12 12:13:13 -0700117#pylint: enable=W0232
118
119
joychen921e1fb2013-06-28 11:12:20 -0700120class XBuddy(build_util.BuildObject):
joychen3cb228e2013-06-12 12:13:13 -0700121 """Class that manages image retrieval and caching by the devserver.
122
123 Image retrieval by xBuddy path:
124 XBuddy accesses images and artifacts that it stores using an xBuddy
125 path of the form: board/version/alias
126 The primary xbuddy.Get call retrieves the correct artifact or url to where
127 the artifacts can be found.
128
129 Image caching:
130 Images and other artifacts are stored identically to how they would have
131 been if devserver's stage rpc was called and the xBuddy cache replaces
132 build versions on a LRU basis. Timestamps are maintained by last accessed
133 times of representative files in the a directory in the static serve
134 directory (XBUDDY_TIMESTAMP_DIR).
135
136 Private class members:
137 _true_values - used for interpreting boolean values
138 _staging_thread_count - track download requests
joychen921e1fb2013-06-28 11:12:20 -0700139 _timestamp_folder - directory with empty files standing in as timestamps
140 for each image currently cached by xBuddy
joychen3cb228e2013-06-12 12:13:13 -0700141 """
142 _true_values = ['true', 't', 'yes', 'y']
143
144 # Number of threads that are staging images.
145 _staging_thread_count = 0
146 # Lock used to lock increasing/decreasing count.
147 _staging_thread_count_lock = threading.Lock()
148
joychen5260b9a2013-07-16 14:48:01 -0700149 def __init__(self, manage_builds=False, **kwargs):
joychen921e1fb2013-06-28 11:12:20 -0700150 super(XBuddy, self).__init__(**kwargs)
joychen5260b9a2013-07-16 14:48:01 -0700151 self._manage_builds = manage_builds
joychen921e1fb2013-06-28 11:12:20 -0700152 self._timestamp_folder = os.path.join(self.static_dir,
joychen3cb228e2013-06-12 12:13:13 -0700153 Timestamp.XBUDDY_TIMESTAMP_DIR)
joychen7df67f72013-07-18 14:21:12 -0700154 common_util.MkDirP(self._timestamp_folder)
joychen3cb228e2013-06-12 12:13:13 -0700155
156 @classmethod
157 def ParseBoolean(cls, boolean_string):
158 """Evaluate a string to a boolean value"""
159 if boolean_string:
160 return boolean_string.lower() in cls._true_values
161 else:
162 return False
163
joychenf8f07e22013-07-12 17:45:51 -0700164 def _LookupOfficial(self, board, suffix=RELEASE):
165 """Check LATEST-master for the version number of interest."""
166 _Log("Checking gs for latest %s-%s image", board, suffix)
167 latest_addr = devserver_constants.GS_LATEST_MASTER % {'board':board,
168 'suffix':suffix}
169 cmd = 'gsutil cat %s' % latest_addr
170 msg = 'Failed to find build at %s' % latest_addr
171 # Full release + version is in the LATEST file
172 version = gsutil_util.GSUtilRun(cmd, msg)
joychen3cb228e2013-06-12 12:13:13 -0700173
joychenf8f07e22013-07-12 17:45:51 -0700174 return devserver_constants.IMAGE_DIR % {'board':board,
175 'suffix':suffix,
176 'version':version}
177
178 def _LookupChannel(self, board, channel='stable'):
179 """Check the channel folder for the version number of interest."""
180 # Get all names in channel dir. Get 10 highest directories by version
joychen7df67f72013-07-18 14:21:12 -0700181 _Log("Checking channel '%s' for latest '%s' image", channel, board)
joychenf8f07e22013-07-12 17:45:51 -0700182 channel_dir = devserver_constants.GS_CHANNEL_DIR % {'channel':channel,
183 'board':board}
184 latest_version = gsutil_util.GetLatestVersionFromGSDir(channel_dir)
185
186 # Figure out release number from the version number
187 image_url = devserver_constants.IMAGE_DIR % {'board':board,
188 'suffix':RELEASE,
189 'version':'R*'+latest_version}
190 image_dir = os.path.join(devserver_constants.GS_IMAGE_DIR, image_url)
191
192 # There should only be one match on cros-image-archive.
193 full_version = gsutil_util.GetLatestVersionFromGSDir(image_dir)
194
195 return devserver_constants.IMAGE_DIR % {'board':board,
196 'suffix':RELEASE,
197 'version':full_version}
198
199 def _LookupVersion(self, board, version):
200 """Search GS image releases for the highest match to a version prefix."""
201 # Build the pattern for GS to match
joychen7df67f72013-07-18 14:21:12 -0700202 _Log("Checking gs for latest '%s' image with prefix '%s'", board, version)
joychenf8f07e22013-07-12 17:45:51 -0700203 image_url = devserver_constants.IMAGE_DIR % {'board':board,
204 'suffix':RELEASE,
205 'version':version + '*'}
206 image_dir = os.path.join(devserver_constants.GS_IMAGE_DIR, image_url)
207
208 # grab the newest version of the ones matched
209 full_version = gsutil_util.GetLatestVersionFromGSDir(image_dir)
210 return devserver_constants.IMAGE_DIR % {'board':board,
211 'suffix':RELEASE,
212 'version':full_version}
213
214 def _ResolveVersionToUrl(self, board, version):
joychen3cb228e2013-06-12 12:13:13 -0700215 """
joychen921e1fb2013-06-28 11:12:20 -0700216 Handle version aliases for remote payloads in GS.
joychen3cb228e2013-06-12 12:13:13 -0700217
218 Args:
219 board: as specified in the original call. (i.e. x86-generic, parrot)
220 version: as entered in the original call. can be
221 {TBD, 0. some custom alias as defined in a config file}
222 1. latest
223 2. latest-{channel}
224 3. latest-official-{board suffix}
225 4. version prefix (i.e. RX-Y.X, RX-Y, RX)
joychen3cb228e2013-06-12 12:13:13 -0700226
227 Returns:
joychenf8f07e22013-07-12 17:45:51 -0700228 image_url is where the image dir is actually found on GS
joychen3cb228e2013-06-12 12:13:13 -0700229
230 """
joychenf8f07e22013-07-12 17:45:51 -0700231 # TODO (joychen) Convert separate calls to a dict + error out bad paths
joychen3cb228e2013-06-12 12:13:13 -0700232
joychenf8f07e22013-07-12 17:45:51 -0700233 # Only the last segment of the alias is variable relative to the rest.
234 version_tuple = version.rsplit('-', 1)
joychen3cb228e2013-06-12 12:13:13 -0700235
joychenf8f07e22013-07-12 17:45:51 -0700236 if re.match(devserver_constants.VERSION_RE, version):
237 # This is supposed to be a complete version number on GS. Return it.
238 return devserver_constants.IMAGE_DIR % {'board':board,
239 'suffix':RELEASE,
240 'version':version}
241 elif version == LATEST_OFFICIAL:
242 # latest-official --> LATEST build in board-release
243 return self._LookupOfficial(board)
244 elif version_tuple[0] == LATEST_OFFICIAL:
245 # latest-official-{suffix} --> LATEST build in board-{suffix}
246 return self._LookupOfficial(board, version_tuple[1])
247 elif version == LATEST:
248 # latest --> latest build on stable channel
249 return self._LookupChannel(board)
250 elif version_tuple[0] == LATEST:
251 if re.match(devserver_constants.VERSION_RE, version_tuple[1]):
252 # latest-R* --> most recent qualifying build
253 return self._LookupVersion(board, version_tuple[1])
254 else:
255 # latest-{channel} --> latest build within that channel
256 return self._LookupChannel(board, version_tuple[1])
joychen3cb228e2013-06-12 12:13:13 -0700257 else:
258 # The given version doesn't match any known patterns.
joychen921e1fb2013-06-28 11:12:20 -0700259 raise XBuddyException("Version %s unknown. Can't find on GS." % version)
joychen3cb228e2013-06-12 12:13:13 -0700260
joychen5260b9a2013-07-16 14:48:01 -0700261 @staticmethod
262 def _Symlink(link, target):
263 """Symlinks link to target, and removes whatever link was there before."""
264 _Log("Linking to %s from %s", link, target)
265 if os.path.lexists(link):
266 os.unlink(link)
267 os.symlink(target, link)
268
joychen921e1fb2013-06-28 11:12:20 -0700269 def _GetLatestLocalVersion(self, board, file_name):
270 """Get the version of the latest image built for board by build_image
271
272 Updates the symlink reference within the xBuddy static dir to point to
273 the real image dir in the local /build/images directory.
274
275 Args:
276 board - board-suffix
277 file_name - the filename of the image we have cached
278
279 Returns:
280 version - the discovered version of the image.
joychen346531c2013-07-24 16:55:56 -0700281 found - True if file was found
joychen3cb228e2013-06-12 12:13:13 -0700282 """
joychen921e1fb2013-06-28 11:12:20 -0700283 latest_local_dir = self.GetLatestImageDir(board)
joychen7df67f72013-07-18 14:21:12 -0700284 if not latest_local_dir and os.path.exists(latest_local_dir):
joychen921e1fb2013-06-28 11:12:20 -0700285 raise XBuddyException('No builds found for %s. Did you run build_image?' %
286 board)
287
288 # assume that the version number is the name of the directory
289 version = os.path.basename(latest_local_dir)
290
291 path_to_image = os.path.join(latest_local_dir, file_name)
joychen346531c2013-07-24 16:55:56 -0700292 if os.path.exists(path_to_image):
293 return version, True
294 else:
295 return version, False
joychen921e1fb2013-06-28 11:12:20 -0700296
297 def _InterpretPath(self, path_list):
298 """
299 Split and return the pieces of an xBuddy path name
joychen3cb228e2013-06-12 12:13:13 -0700300
301 input:
joychen921e1fb2013-06-28 11:12:20 -0700302 path_list: the segments of the path xBuddy Get was called with.
303 Documentation of path_list can be found in devserver.py:xbuddy
joychen3cb228e2013-06-12 12:13:13 -0700304
305 Return:
joychenf8f07e22013-07-12 17:45:51 -0700306 tuple of (image_type, board, version)
joychen3cb228e2013-06-12 12:13:13 -0700307
308 Raises:
309 XBuddyException: if the path can't be resolved into valid components
310 """
joychen7df67f72013-07-18 14:21:12 -0700311 path_list = list(path_list)
312
313 # Required parts of path parsing.
314 try:
315 # Determine if image is explicitly local or remote.
316 is_local = False
317 if path_list[0] == LOCAL:
318 path_list.pop(0)
319 is_local = True
320 elif path_list[0] == REMOTE:
321 path_list.pop(0)
322 _Log(str(path_list))
323
324 # Set board
325 board = path_list.pop(0)
326 _Log(str(path_list))
327
328 # Set defaults
joychen3cb228e2013-06-12 12:13:13 -0700329 version = LATEST
joychen921e1fb2013-06-28 11:12:20 -0700330 image_type = GS_ALIASES[0]
joychen7df67f72013-07-18 14:21:12 -0700331 except IndexError:
332 msg = "Specify at least the board in your xBuddy call. Your path: %s"
333 raise XBuddyException(msg % os.path.join(path_list))
joychen3cb228e2013-06-12 12:13:13 -0700334
joychen7df67f72013-07-18 14:21:12 -0700335 # Read as much of the xBuddy path as possible
336 try:
337 # Override default if terminal is a valid artifact alias or a version
338 terminal = path_list[-1]
339 if terminal in GS_ALIASES + LOCAL_ALIASES:
340 image_type = terminal
341 version = path_list[-2]
342 else:
343 version = terminal
344 except IndexError:
345 # This path doesn't have an alias or a version. That's fine.
346 _Log("Some parts of the path not specified. Using defaults.")
347
joychen346531c2013-07-24 16:55:56 -0700348 _Log("Get artifact '%s' in '%s/%s'. Locally? %s",
joychen7df67f72013-07-18 14:21:12 -0700349 image_type, board, version, is_local)
350
351 return image_type, board, version, is_local
joychen3cb228e2013-06-12 12:13:13 -0700352
joychen921e1fb2013-06-28 11:12:20 -0700353 def _SyncRegistryWithBuildImages(self):
joychen5260b9a2013-07-16 14:48:01 -0700354 """ Crawl images_dir for build_ids of images generated from build_image.
355
356 This will find images and symlink them in xBuddy's static dir so that
357 xBuddy's cache can serve them.
358 If xBuddy's _manage_builds option is on, then a timestamp will also be
359 generated, and xBuddy will clear them from the directory they are in, as
360 necessary.
361 """
joychen921e1fb2013-06-28 11:12:20 -0700362 build_ids = []
363 for b in os.listdir(self.images_dir):
joychen5260b9a2013-07-16 14:48:01 -0700364 # Ensure we have directories to track all boards in build/images
365 common_util.MkDirP(os.path.join(self.static_dir, b))
joychen921e1fb2013-06-28 11:12:20 -0700366 board_dir = os.path.join(self.images_dir, b)
367 build_ids.extend(['/'.join([b, v]) for v
368 in os.listdir(board_dir) if not v==LATEST])
369
370 # Check currently registered images
371 for f in os.listdir(self._timestamp_folder):
372 build_id = Timestamp.TimestampToBuild(f)
373 if build_id in build_ids:
374 build_ids.remove(build_id)
375
joychen5260b9a2013-07-16 14:48:01 -0700376 # Symlink undiscovered images, and update timestamps if manage_builds is on
377 for build_id in build_ids:
378 link = os.path.join(self.static_dir, build_id)
379 target = os.path.join(self.images_dir, build_id)
380 XBuddy._Symlink(link, target)
381 if self._manage_builds:
382 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen921e1fb2013-06-28 11:12:20 -0700383
384 def _ListBuildTimes(self):
joychen3cb228e2013-06-12 12:13:13 -0700385 """ Returns the currently cached builds and their last access timestamp.
386
387 Returns:
388 list of tuples that matches xBuddy build/version to timestamps in long
389 """
390 # update currently cached builds
391 build_dict = {}
392
joychen7df67f72013-07-18 14:21:12 -0700393 for f in os.listdir(self._timestamp_folder):
joychen3cb228e2013-06-12 12:13:13 -0700394 last_accessed = os.path.getmtime(os.path.join(self._timestamp_folder, f))
395 build_id = Timestamp.TimestampToBuild(f)
396 stale_time = datetime.timedelta(seconds = (time.time()-last_accessed))
joychen921e1fb2013-06-28 11:12:20 -0700397 build_dict[build_id] = stale_time
joychen3cb228e2013-06-12 12:13:13 -0700398 return_tup = sorted(build_dict.iteritems(), key=operator.itemgetter(1))
399 return return_tup
400
joychen3cb228e2013-06-12 12:13:13 -0700401 def _Download(self, gs_url, artifact):
402 """Download the single artifact from the given gs_url."""
403 with XBuddy._staging_thread_count_lock:
404 XBuddy._staging_thread_count += 1
405 try:
joychen7df67f72013-07-18 14:21:12 -0700406 _Log("Downloading '%s' from '%s'", artifact, gs_url)
joychen921e1fb2013-06-28 11:12:20 -0700407 downloader.Downloader(self.static_dir, gs_url).Download(
joychen3cb228e2013-06-12 12:13:13 -0700408 [artifact])
409 finally:
410 with XBuddy._staging_thread_count_lock:
411 XBuddy._staging_thread_count -= 1
412
413 def _CleanCache(self):
414 """Delete all builds besides the first _XBUDDY_CAPACITY builds"""
joychen921e1fb2013-06-28 11:12:20 -0700415 cached_builds = [e[0] for e in self._ListBuildTimes()]
joychen3cb228e2013-06-12 12:13:13 -0700416 _Log('In cache now: %s', cached_builds)
417
418 for b in range(_XBUDDY_CAPACITY, len(cached_builds)):
419 b_path = cached_builds[b]
joychen7df67f72013-07-18 14:21:12 -0700420 _Log("Clearing '%s' from cache", b_path)
joychen3cb228e2013-06-12 12:13:13 -0700421
422 time_file = os.path.join(self._timestamp_folder,
423 Timestamp.BuildToTimestamp(b_path))
joychen921e1fb2013-06-28 11:12:20 -0700424 os.unlink(time_file)
425 clear_dir = os.path.join(self.static_dir, b_path)
joychen3cb228e2013-06-12 12:13:13 -0700426 try:
joychen5260b9a2013-07-16 14:48:01 -0700427 # handle symlinks, in the case of links to local builds if enabled
428 if self._manage_builds and os.path.islink(clear_dir):
429 target = os.readlink(clear_dir)
430 _Log('Deleting locally built image at %s', target)
joychen921e1fb2013-06-28 11:12:20 -0700431
432 os.unlink(clear_dir)
joychen5260b9a2013-07-16 14:48:01 -0700433 if os.path.exists(target):
joychen921e1fb2013-06-28 11:12:20 -0700434 shutil.rmtree(target)
435 elif os.path.exists(clear_dir):
joychen5260b9a2013-07-16 14:48:01 -0700436 _Log('Deleting downloaded image at %s', clear_dir)
joychen3cb228e2013-06-12 12:13:13 -0700437 shutil.rmtree(clear_dir)
joychen921e1fb2013-06-28 11:12:20 -0700438
joychen3cb228e2013-06-12 12:13:13 -0700439 except Exception:
440 raise XBuddyException('Failed to clear build in %s.' % clear_dir)
441
joychen346531c2013-07-24 16:55:56 -0700442 def _GetFromGS(self, build_id, image_type, lookup_only):
443 """Check if the artifact is available locally. Download from GS if not.
444
445 Return:
446 boolean - True if cached.
447 """
joychenf8f07e22013-07-12 17:45:51 -0700448 gs_url = os.path.join(devserver_constants.GS_IMAGE_DIR,
joychen921e1fb2013-06-28 11:12:20 -0700449 build_id)
450
451 # stage image if not found in cache
452 file_name = GS_ALIAS_TO_FILENAME[image_type]
joychen346531c2013-07-24 16:55:56 -0700453 file_loc = os.path.join(self.static_dir, build_id, file_name)
454 cached = os.path.exists(file_loc)
455
joychen921e1fb2013-06-28 11:12:20 -0700456 if not cached:
joychen346531c2013-07-24 16:55:56 -0700457 if lookup_only:
458 return False
459 else:
460 artifact = GS_ALIAS_TO_ARTIFACT[image_type]
461 self._Download(gs_url, artifact)
462 return True
joychen921e1fb2013-06-28 11:12:20 -0700463 else:
464 _Log('Image already cached.')
joychen346531c2013-07-24 16:55:56 -0700465 return True
joychen921e1fb2013-06-28 11:12:20 -0700466
joychen346531c2013-07-24 16:55:56 -0700467 def _GetArtifact(self, path, lookup_only=False):
468 """Interpret an xBuddy path and return directory/file_name to resource.
469
470 Returns:
471 image_url to the directory
472 file_name of the artifact
473 found = True if the artifact is cached
474
475 Raises:
476 XBuddyException if the path could not be translated
477 """
joychen7df67f72013-07-18 14:21:12 -0700478 image_type, board, version, is_local = self._InterpretPath(path)
joychen921e1fb2013-06-28 11:12:20 -0700479
joychen346531c2013-07-24 16:55:56 -0700480 found = False
joychen7df67f72013-07-18 14:21:12 -0700481 if is_local:
joychen921e1fb2013-06-28 11:12:20 -0700482 # Get a local image
483 if image_type not in LOCAL_ALIASES:
joychen7df67f72013-07-18 14:21:12 -0700484 raise XBuddyException('Bad local image type: %s. Use one of: %s' %
joychen921e1fb2013-06-28 11:12:20 -0700485 (image_type, LOCAL_ALIASES))
joychen921e1fb2013-06-28 11:12:20 -0700486 file_name = LOCAL_ALIAS_TO_FILENAME[image_type]
joychen7df67f72013-07-18 14:21:12 -0700487
488 if version == LATEST:
489 # Get the latest local image for the given board
joychen346531c2013-07-24 16:55:56 -0700490 version, found = self._GetLatestLocalVersion(board, file_name)
joychen7df67f72013-07-18 14:21:12 -0700491 else:
492 # An exact version path in build/images was specified for this board
493 local_file = os.path.join(self.images_dir, board, version, file_name)
joychen346531c2013-07-24 16:55:56 -0700494 if os.path.exists(local_file):
495 found = True
joychen7df67f72013-07-18 14:21:12 -0700496
joychenf8f07e22013-07-12 17:45:51 -0700497 image_url = os.path.join(board, version)
joychen921e1fb2013-06-28 11:12:20 -0700498 else:
499 # Get a remote image
500 if image_type not in GS_ALIASES:
joychen7df67f72013-07-18 14:21:12 -0700501 raise XBuddyException('Bad remote image type: %s. Use one of: %s' %
joychen921e1fb2013-06-28 11:12:20 -0700502 (image_type, GS_ALIASES))
joychen921e1fb2013-06-28 11:12:20 -0700503 file_name = GS_ALIAS_TO_FILENAME[image_type]
joychen921e1fb2013-06-28 11:12:20 -0700504
joychenf8f07e22013-07-12 17:45:51 -0700505 # Interpret the version (alias), and get gs address
506 image_url = self._ResolveVersionToUrl(board, version)
joychen346531c2013-07-24 16:55:56 -0700507 found = self._GetFromGS(image_url, image_type, lookup_only)
joychenf8f07e22013-07-12 17:45:51 -0700508
joychen346531c2013-07-24 16:55:56 -0700509 return image_url, file_name, found
joychen3cb228e2013-06-12 12:13:13 -0700510
511 ############################ BEGIN PUBLIC METHODS
512
513 def List(self):
514 """Lists the currently available images & time since last access."""
joychen921e1fb2013-06-28 11:12:20 -0700515 self._SyncRegistryWithBuildImages()
516 builds = self._ListBuildTimes()
517 return_string = ''
518 for build, timestamp in builds:
519 return_string += '<b>' + build + '</b> '
520 return_string += '(time since last access: ' + str(timestamp) + ')<br>'
521 return return_string
joychen3cb228e2013-06-12 12:13:13 -0700522
523 def Capacity(self):
524 """Returns the number of images cached by xBuddy."""
525 return str(_XBUDDY_CAPACITY)
526
joychen346531c2013-07-24 16:55:56 -0700527 def Translate(self, path_list):
528 """Translates an xBuddy path to a real path to artifact if it exists.
529
530 Equivalent to the Get call, minus downloading and updating timestamps.
531 The returned path is always the path to the directory.
532
533 Throws:
534 XBuddyException - if the path couldn't be translated
535 """
536 self._SyncRegistryWithBuildImages()
537
538 build_id, _file_name, found = self._GetArtifact(path_list, lookup_only=True)
539
540 _Log('Returning path to payload: %s', build_id)
541 return build_id, found
542
joychen921e1fb2013-06-28 11:12:20 -0700543 def Get(self, path_list, return_dir=False):
544 """The full xBuddy call, returns resource specified by path_list.
joychen3cb228e2013-06-12 12:13:13 -0700545
546 Please see devserver.py:xbuddy for full documentation.
547 Args:
joychen921e1fb2013-06-28 11:12:20 -0700548 path_list: [board, version, alias] as split from the xbuddy call url
joychen3cb228e2013-06-12 12:13:13 -0700549 return_dir: boolean, if set to true, returns the dir name instead.
550
551 Returns:
552 Path to the image or update directory on the devserver.
joychenf8f07e22013-07-12 17:45:51 -0700553 e.g. http://host/static/x86-generic/
joychen3cb228e2013-06-12 12:13:13 -0700554 R26-4000.0.0/chromium-test-image.bin
555 or
joychenf8f07e22013-07-12 17:45:51 -0700556 http://host/static/x86-generic/R26-4000.0.0/
joychen3cb228e2013-06-12 12:13:13 -0700557
558 Raises:
joychen346531c2013-07-24 16:55:56 -0700559 XBuddyException if path is invalid
joychen3cb228e2013-06-12 12:13:13 -0700560 """
joychen7df67f72013-07-18 14:21:12 -0700561 self._SyncRegistryWithBuildImages()
joychen346531c2013-07-24 16:55:56 -0700562 build_id, file_name, _found = self._GetArtifact(path_list)
joychen3cb228e2013-06-12 12:13:13 -0700563
joychen921e1fb2013-06-28 11:12:20 -0700564 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen3cb228e2013-06-12 12:13:13 -0700565
566 #TODO (joyc): run in sep thread
567 self._CleanCache()
568
joychen921e1fb2013-06-28 11:12:20 -0700569 return_url = os.path.join('static', build_id)
joychen3cb228e2013-06-12 12:13:13 -0700570 if not return_dir:
571 return_url = os.path.join(return_url, file_name)
572
573 _Log('Returning path to payload: %s', return_url)
574 return return_url