blob: d0e72bd45a086f59cbd921512543f0ca905c0166 [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,
joychen121fc9b2013-08-02 14:30:30 -070072 devserver_constants.STATEFUL_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:
joychen121fc9b2013-08-02 14:30:30 -0700142 _true_values: used for interpreting boolean values
143 _staging_thread_count: track download requests
144 _timestamp_folder: directory with empty files standing in as timestamps
joychen921e1fb2013-06-28 11:12:20 -0700145 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)
joychenb0dfe552013-07-30 10:02:06 -0700163
joychen921e1fb2013-06-28 11:12:20 -0700164 self._timestamp_folder = os.path.join(self.static_dir,
joychen3cb228e2013-06-12 12:13:13 -0700165 Timestamp.XBUDDY_TIMESTAMP_DIR)
joychen7df67f72013-07-18 14:21:12 -0700166 common_util.MkDirP(self._timestamp_folder)
joychen3cb228e2013-06-12 12:13:13 -0700167
168 @classmethod
169 def ParseBoolean(cls, boolean_string):
170 """Evaluate a string to a boolean value"""
171 if boolean_string:
172 return boolean_string.lower() in cls._true_values
173 else:
174 return False
175
joychenf8f07e22013-07-12 17:45:51 -0700176 def _LookupOfficial(self, board, suffix=RELEASE):
177 """Check LATEST-master for the version number of interest."""
178 _Log("Checking gs for latest %s-%s image", board, suffix)
179 latest_addr = devserver_constants.GS_LATEST_MASTER % {'board':board,
180 'suffix':suffix}
181 cmd = 'gsutil cat %s' % latest_addr
182 msg = 'Failed to find build at %s' % latest_addr
joychen121fc9b2013-08-02 14:30:30 -0700183 # Full release + version is in the LATEST file.
joychenf8f07e22013-07-12 17:45:51 -0700184 version = gsutil_util.GSUtilRun(cmd, msg)
joychen3cb228e2013-06-12 12:13:13 -0700185
joychenf8f07e22013-07-12 17:45:51 -0700186 return devserver_constants.IMAGE_DIR % {'board':board,
187 'suffix':suffix,
188 'version':version}
189
190 def _LookupChannel(self, board, channel='stable'):
191 """Check the channel folder for the version number of interest."""
joychen121fc9b2013-08-02 14:30:30 -0700192 # Get all names in channel dir. Get 10 highest directories by version.
joychen7df67f72013-07-18 14:21:12 -0700193 _Log("Checking channel '%s' for latest '%s' image", channel, board)
joychenf8f07e22013-07-12 17:45:51 -0700194 channel_dir = devserver_constants.GS_CHANNEL_DIR % {'channel':channel,
195 'board':board}
196 latest_version = gsutil_util.GetLatestVersionFromGSDir(channel_dir)
197
joychen121fc9b2013-08-02 14:30:30 -0700198 # Figure out release number from the version number.
joychenf8f07e22013-07-12 17:45:51 -0700199 image_url = devserver_constants.IMAGE_DIR % {'board':board,
200 'suffix':RELEASE,
201 'version':'R*'+latest_version}
202 image_dir = os.path.join(devserver_constants.GS_IMAGE_DIR, image_url)
203
204 # There should only be one match on cros-image-archive.
205 full_version = gsutil_util.GetLatestVersionFromGSDir(image_dir)
206
207 return devserver_constants.IMAGE_DIR % {'board':board,
208 'suffix':RELEASE,
209 'version':full_version}
210
211 def _LookupVersion(self, board, version):
212 """Search GS image releases for the highest match to a version prefix."""
joychen121fc9b2013-08-02 14:30:30 -0700213 # Build the pattern for GS to match.
joychen7df67f72013-07-18 14:21:12 -0700214 _Log("Checking gs for latest '%s' image with prefix '%s'", board, version)
joychenf8f07e22013-07-12 17:45:51 -0700215 image_url = devserver_constants.IMAGE_DIR % {'board':board,
216 'suffix':RELEASE,
217 'version':version + '*'}
218 image_dir = os.path.join(devserver_constants.GS_IMAGE_DIR, image_url)
219
joychen121fc9b2013-08-02 14:30:30 -0700220 # Grab the newest version of the ones matched.
joychenf8f07e22013-07-12 17:45:51 -0700221 full_version = gsutil_util.GetLatestVersionFromGSDir(image_dir)
222 return devserver_constants.IMAGE_DIR % {'board':board,
223 'suffix':RELEASE,
224 'version':full_version}
225
226 def _ResolveVersionToUrl(self, board, version):
joychen121fc9b2013-08-02 14:30:30 -0700227 """Handle version aliases for remote payloads in GS.
joychen3cb228e2013-06-12 12:13:13 -0700228
229 Args:
230 board: as specified in the original call. (i.e. x86-generic, parrot)
231 version: as entered in the original call. can be
232 {TBD, 0. some custom alias as defined in a config file}
233 1. latest
234 2. latest-{channel}
235 3. latest-official-{board suffix}
236 4. version prefix (i.e. RX-Y.X, RX-Y, RX)
joychen3cb228e2013-06-12 12:13:13 -0700237
238 Returns:
joychen121fc9b2013-08-02 14:30:30 -0700239 Location where the image dir is actually found on GS
joychen3cb228e2013-06-12 12:13:13 -0700240
241 """
joychen121fc9b2013-08-02 14:30:30 -0700242 # TODO(joychen): Convert separate calls to a dict + error out bad paths.
joychen3cb228e2013-06-12 12:13:13 -0700243
joychenf8f07e22013-07-12 17:45:51 -0700244 # Only the last segment of the alias is variable relative to the rest.
245 version_tuple = version.rsplit('-', 1)
joychen3cb228e2013-06-12 12:13:13 -0700246
joychenf8f07e22013-07-12 17:45:51 -0700247 if re.match(devserver_constants.VERSION_RE, version):
248 # This is supposed to be a complete version number on GS. Return it.
249 return devserver_constants.IMAGE_DIR % {'board':board,
250 'suffix':RELEASE,
251 'version':version}
252 elif version == LATEST_OFFICIAL:
253 # latest-official --> LATEST build in board-release
254 return self._LookupOfficial(board)
255 elif version_tuple[0] == LATEST_OFFICIAL:
256 # latest-official-{suffix} --> LATEST build in board-{suffix}
257 return self._LookupOfficial(board, version_tuple[1])
258 elif version == LATEST:
259 # latest --> latest build on stable channel
260 return self._LookupChannel(board)
261 elif version_tuple[0] == LATEST:
262 if re.match(devserver_constants.VERSION_RE, version_tuple[1]):
263 # latest-R* --> most recent qualifying build
264 return self._LookupVersion(board, version_tuple[1])
265 else:
266 # latest-{channel} --> latest build within that channel
267 return self._LookupChannel(board, version_tuple[1])
joychen3cb228e2013-06-12 12:13:13 -0700268 else:
269 # The given version doesn't match any known patterns.
joychen921e1fb2013-06-28 11:12:20 -0700270 raise XBuddyException("Version %s unknown. Can't find on GS." % version)
joychen3cb228e2013-06-12 12:13:13 -0700271
joychen5260b9a2013-07-16 14:48:01 -0700272 @staticmethod
273 def _Symlink(link, target):
274 """Symlinks link to target, and removes whatever link was there before."""
275 _Log("Linking to %s from %s", link, target)
276 if os.path.lexists(link):
277 os.unlink(link)
278 os.symlink(target, link)
279
joychen121fc9b2013-08-02 14:30:30 -0700280 def _GetLatestLocalVersion(self, board):
joychen921e1fb2013-06-28 11:12:20 -0700281 """Get the version of the latest image built for board by build_image
282
283 Updates the symlink reference within the xBuddy static dir to point to
284 the real image dir in the local /build/images directory.
285
286 Args:
joychen121fc9b2013-08-02 14:30:30 -0700287 board: board that image was built for.jj
joychen921e1fb2013-06-28 11:12:20 -0700288
289 Returns:
joychen121fc9b2013-08-02 14:30:30 -0700290 The discovered version of the image.
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
joychen121fc9b2013-08-02 14:30:30 -0700297 # Assume that the version number is the name of the directory.
298 return os.path.basename(latest_local_dir)
joychen921e1fb2013-06-28 11:12:20 -0700299
joychen121fc9b2013-08-02 14:30:30 -0700300 def _InterpretPath(self, path):
301 """Split and return the pieces of an xBuddy path name
joychen921e1fb2013-06-28 11:12:20 -0700302
joychen121fc9b2013-08-02 14:30:30 -0700303 Args:
304 path: the path xBuddy Get was called with.
joychen3cb228e2013-06-12 12:13:13 -0700305
306 Return:
joychenf8f07e22013-07-12 17:45:51 -0700307 tuple of (image_type, board, version)
joychen3cb228e2013-06-12 12:13:13 -0700308
309 Raises:
310 XBuddyException: if the path can't be resolved into valid components
311 """
joychen121fc9b2013-08-02 14:30:30 -0700312 path_list = filter(None, path.split('/'))
joychen7df67f72013-07-18 14:21:12 -0700313
314 # Required parts of path parsing.
315 try:
316 # Determine if image is explicitly local or remote.
joychen121fc9b2013-08-02 14:30:30 -0700317 is_local = True
318 if path_list[0] in (REMOTE, LOCAL):
joychen18737f32013-08-16 17:18:12 -0700319 is_local = (path_list.pop(0) == LOCAL)
joychen7df67f72013-07-18 14:21:12 -0700320
joychen121fc9b2013-08-02 14:30:30 -0700321 # Set board.
joychen7df67f72013-07-18 14:21:12 -0700322 board = path_list.pop(0)
joychen7df67f72013-07-18 14:21:12 -0700323
joychen121fc9b2013-08-02 14:30:30 -0700324 # Set defaults.
joychen3cb228e2013-06-12 12:13:13 -0700325 version = LATEST
joychen921e1fb2013-06-28 11:12:20 -0700326 image_type = GS_ALIASES[0]
joychen7df67f72013-07-18 14:21:12 -0700327 except IndexError:
328 msg = "Specify at least the board in your xBuddy call. Your path: %s"
329 raise XBuddyException(msg % os.path.join(path_list))
joychen3cb228e2013-06-12 12:13:13 -0700330
joychen121fc9b2013-08-02 14:30:30 -0700331 # Read as much of the xBuddy path as possible.
joychen7df67f72013-07-18 14:21:12 -0700332 try:
joychen121fc9b2013-08-02 14:30:30 -0700333 # Override default if terminal is a valid artifact alias or a version.
joychen7df67f72013-07-18 14:21:12 -0700334 terminal = path_list[-1]
335 if terminal in GS_ALIASES + LOCAL_ALIASES:
336 image_type = terminal
337 version = path_list[-2]
338 else:
339 version = terminal
340 except IndexError:
341 # This path doesn't have an alias or a version. That's fine.
342 _Log("Some parts of the path not specified. Using defaults.")
343
joychen346531c2013-07-24 16:55:56 -0700344 _Log("Get artifact '%s' in '%s/%s'. Locally? %s",
joychen7df67f72013-07-18 14:21:12 -0700345 image_type, board, version, is_local)
346
347 return image_type, board, version, is_local
joychen3cb228e2013-06-12 12:13:13 -0700348
joychen921e1fb2013-06-28 11:12:20 -0700349 def _SyncRegistryWithBuildImages(self):
joychen5260b9a2013-07-16 14:48:01 -0700350 """ Crawl images_dir for build_ids of images generated from build_image.
351
352 This will find images and symlink them in xBuddy's static dir so that
353 xBuddy's cache can serve them.
354 If xBuddy's _manage_builds option is on, then a timestamp will also be
355 generated, and xBuddy will clear them from the directory they are in, as
356 necessary.
357 """
joychen921e1fb2013-06-28 11:12:20 -0700358 build_ids = []
359 for b in os.listdir(self.images_dir):
joychen5260b9a2013-07-16 14:48:01 -0700360 # Ensure we have directories to track all boards in build/images
361 common_util.MkDirP(os.path.join(self.static_dir, b))
joychen921e1fb2013-06-28 11:12:20 -0700362 board_dir = os.path.join(self.images_dir, b)
363 build_ids.extend(['/'.join([b, v]) for v
364 in os.listdir(board_dir) if not v==LATEST])
365
joychen121fc9b2013-08-02 14:30:30 -0700366 # Check currently registered images.
joychen921e1fb2013-06-28 11:12:20 -0700367 for f in os.listdir(self._timestamp_folder):
368 build_id = Timestamp.TimestampToBuild(f)
369 if build_id in build_ids:
370 build_ids.remove(build_id)
371
joychen121fc9b2013-08-02 14:30:30 -0700372 # Symlink undiscovered images, and update timestamps if manage_builds is on.
joychen5260b9a2013-07-16 14:48:01 -0700373 for build_id in build_ids:
374 link = os.path.join(self.static_dir, build_id)
375 target = os.path.join(self.images_dir, build_id)
376 XBuddy._Symlink(link, target)
377 if self._manage_builds:
378 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen921e1fb2013-06-28 11:12:20 -0700379
380 def _ListBuildTimes(self):
joychen3cb228e2013-06-12 12:13:13 -0700381 """ Returns the currently cached builds and their last access timestamp.
382
383 Returns:
384 list of tuples that matches xBuddy build/version to timestamps in long
385 """
joychen121fc9b2013-08-02 14:30:30 -0700386 # Update currently cached builds.
joychen3cb228e2013-06-12 12:13:13 -0700387 build_dict = {}
388
joychen7df67f72013-07-18 14:21:12 -0700389 for f in os.listdir(self._timestamp_folder):
joychen3cb228e2013-06-12 12:13:13 -0700390 last_accessed = os.path.getmtime(os.path.join(self._timestamp_folder, f))
391 build_id = Timestamp.TimestampToBuild(f)
392 stale_time = datetime.timedelta(seconds = (time.time()-last_accessed))
joychen921e1fb2013-06-28 11:12:20 -0700393 build_dict[build_id] = stale_time
joychen3cb228e2013-06-12 12:13:13 -0700394 return_tup = sorted(build_dict.iteritems(), key=operator.itemgetter(1))
395 return return_tup
396
joychen3cb228e2013-06-12 12:13:13 -0700397 def _Download(self, gs_url, artifact):
398 """Download the single artifact from the given gs_url."""
399 with XBuddy._staging_thread_count_lock:
400 XBuddy._staging_thread_count += 1
401 try:
joychen7df67f72013-07-18 14:21:12 -0700402 _Log("Downloading '%s' from '%s'", artifact, gs_url)
joychen921e1fb2013-06-28 11:12:20 -0700403 downloader.Downloader(self.static_dir, gs_url).Download(
joychen18737f32013-08-16 17:18:12 -0700404 [artifact], [])
joychen3cb228e2013-06-12 12:13:13 -0700405 finally:
406 with XBuddy._staging_thread_count_lock:
407 XBuddy._staging_thread_count -= 1
408
409 def _CleanCache(self):
410 """Delete all builds besides the first _XBUDDY_CAPACITY builds"""
joychen121fc9b2013-08-02 14:30:30 -0700411 if not self._manage_builds:
412 return
413
joychen921e1fb2013-06-28 11:12:20 -0700414 cached_builds = [e[0] for e in self._ListBuildTimes()]
joychen3cb228e2013-06-12 12:13:13 -0700415 _Log('In cache now: %s', cached_builds)
416
417 for b in range(_XBUDDY_CAPACITY, len(cached_builds)):
418 b_path = cached_builds[b]
joychen7df67f72013-07-18 14:21:12 -0700419 _Log("Clearing '%s' from cache", b_path)
joychen3cb228e2013-06-12 12:13:13 -0700420
421 time_file = os.path.join(self._timestamp_folder,
422 Timestamp.BuildToTimestamp(b_path))
joychen921e1fb2013-06-28 11:12:20 -0700423 os.unlink(time_file)
424 clear_dir = os.path.join(self.static_dir, b_path)
joychen3cb228e2013-06-12 12:13:13 -0700425 try:
joychen121fc9b2013-08-02 14:30:30 -0700426 # Handle symlinks, in the case of links to local builds if enabled.
427 if os.path.islink(clear_dir):
joychen5260b9a2013-07-16 14:48:01 -0700428 target = os.readlink(clear_dir)
429 _Log('Deleting locally built image at %s', target)
joychen921e1fb2013-06-28 11:12:20 -0700430
431 os.unlink(clear_dir)
joychen5260b9a2013-07-16 14:48:01 -0700432 if os.path.exists(target):
joychen921e1fb2013-06-28 11:12:20 -0700433 shutil.rmtree(target)
434 elif os.path.exists(clear_dir):
joychen5260b9a2013-07-16 14:48:01 -0700435 _Log('Deleting downloaded image at %s', clear_dir)
joychen3cb228e2013-06-12 12:13:13 -0700436 shutil.rmtree(clear_dir)
joychen921e1fb2013-06-28 11:12:20 -0700437
joychen121fc9b2013-08-02 14:30:30 -0700438 except Exception as err:
439 raise XBuddyException('Failed to clear %s: %s' % (clear_dir, err))
joychen3cb228e2013-06-12 12:13:13 -0700440
joychen346531c2013-07-24 16:55:56 -0700441 def _GetFromGS(self, build_id, image_type, lookup_only):
joychen121fc9b2013-08-02 14:30:30 -0700442 """Check if the artifact is available locally. Download from GS if not."""
joychenf8f07e22013-07-12 17:45:51 -0700443 gs_url = os.path.join(devserver_constants.GS_IMAGE_DIR,
joychen921e1fb2013-06-28 11:12:20 -0700444 build_id)
445
joychen121fc9b2013-08-02 14:30:30 -0700446 # Stage image if not found in cache.
joychen921e1fb2013-06-28 11:12:20 -0700447 file_name = GS_ALIAS_TO_FILENAME[image_type]
joychen346531c2013-07-24 16:55:56 -0700448 file_loc = os.path.join(self.static_dir, build_id, file_name)
449 cached = os.path.exists(file_loc)
450
joychen921e1fb2013-06-28 11:12:20 -0700451 if not cached:
joychen121fc9b2013-08-02 14:30:30 -0700452 if not lookup_only:
joychen346531c2013-07-24 16:55:56 -0700453 artifact = GS_ALIAS_TO_ARTIFACT[image_type]
454 self._Download(gs_url, artifact)
joychen921e1fb2013-06-28 11:12:20 -0700455 else:
456 _Log('Image already cached.')
457
joychen121fc9b2013-08-02 14:30:30 -0700458 def _GetArtifact(self, path_list, board=None, lookup_only=False):
joychen346531c2013-07-24 16:55:56 -0700459 """Interpret an xBuddy path and return directory/file_name to resource.
460
461 Returns:
462 image_url to the directory
463 file_name of the artifact
joychen346531c2013-07-24 16:55:56 -0700464
465 Raises:
joychen121fc9b2013-08-02 14:30:30 -0700466 XBuddyException: if the path could not be translated
joychen346531c2013-07-24 16:55:56 -0700467 """
joychen121fc9b2013-08-02 14:30:30 -0700468 path = '/'.join(path_list)
joychenb0dfe552013-07-30 10:02:06 -0700469 # Rewrite the path if there is an appropriate default.
joychen121fc9b2013-08-02 14:30:30 -0700470 path = xbuddy_lookup_table.paths().get(path, path)
471 # Fill in the board if the string needs it.
472 path = path % {'board': board or self._board}
joychenb0dfe552013-07-30 10:02:06 -0700473
joychen121fc9b2013-08-02 14:30:30 -0700474 # Parse the path.
joychen7df67f72013-07-18 14:21:12 -0700475 image_type, board, version, is_local = self._InterpretPath(path)
joychen921e1fb2013-06-28 11:12:20 -0700476
joychen7df67f72013-07-18 14:21:12 -0700477 if is_local:
joychen121fc9b2013-08-02 14:30:30 -0700478 # Get a local image.
joychen921e1fb2013-06-28 11:12:20 -0700479 if image_type not in LOCAL_ALIASES:
joychen7df67f72013-07-18 14:21:12 -0700480 raise XBuddyException('Bad local image type: %s. Use one of: %s' %
joychen921e1fb2013-06-28 11:12:20 -0700481 (image_type, LOCAL_ALIASES))
joychen921e1fb2013-06-28 11:12:20 -0700482 file_name = LOCAL_ALIAS_TO_FILENAME[image_type]
joychen7df67f72013-07-18 14:21:12 -0700483
484 if version == LATEST:
joychen121fc9b2013-08-02 14:30:30 -0700485 # Get the latest local image for the given board.
486 version = self._GetLatestLocalVersion(board)
joychen7df67f72013-07-18 14:21:12 -0700487
joychenf8f07e22013-07-12 17:45:51 -0700488 image_url = os.path.join(board, version)
joychen121fc9b2013-08-02 14:30:30 -0700489
490 artifact_url = os.path.join(self.static_dir, image_url, file_name)
491 if not os.path.exists(artifact_url):
492 raise XBuddyException('Local artifact not in static_dir at %s/%s' %
493 (image_url, file_name))
494
joychen921e1fb2013-06-28 11:12:20 -0700495 else:
joychen121fc9b2013-08-02 14:30:30 -0700496 # Get a remote image.
joychen921e1fb2013-06-28 11:12:20 -0700497 if image_type not in GS_ALIASES:
joychen7df67f72013-07-18 14:21:12 -0700498 raise XBuddyException('Bad remote image type: %s. Use one of: %s' %
joychen921e1fb2013-06-28 11:12:20 -0700499 (image_type, GS_ALIASES))
joychen921e1fb2013-06-28 11:12:20 -0700500 file_name = GS_ALIAS_TO_FILENAME[image_type]
joychen921e1fb2013-06-28 11:12:20 -0700501
joychen121fc9b2013-08-02 14:30:30 -0700502 # Interpret the version (alias), and get gs address.
joychenf8f07e22013-07-12 17:45:51 -0700503 image_url = self._ResolveVersionToUrl(board, version)
joychen121fc9b2013-08-02 14:30:30 -0700504 self._GetFromGS(image_url, image_type, lookup_only)
joychenf8f07e22013-07-12 17:45:51 -0700505
joychen121fc9b2013-08-02 14:30:30 -0700506 return image_url, file_name
joychen3cb228e2013-06-12 12:13:13 -0700507
508 ############################ BEGIN PUBLIC METHODS
509
510 def List(self):
511 """Lists the currently available images & time since last access."""
joychen921e1fb2013-06-28 11:12:20 -0700512 self._SyncRegistryWithBuildImages()
513 builds = self._ListBuildTimes()
514 return_string = ''
515 for build, timestamp in builds:
516 return_string += '<b>' + build + '</b> '
517 return_string += '(time since last access: ' + str(timestamp) + ')<br>'
518 return return_string
joychen3cb228e2013-06-12 12:13:13 -0700519
520 def Capacity(self):
521 """Returns the number of images cached by xBuddy."""
522 return str(_XBUDDY_CAPACITY)
523
joychen121fc9b2013-08-02 14:30:30 -0700524 def Translate(self, path_list, board=None):
joychen346531c2013-07-24 16:55:56 -0700525 """Translates an xBuddy path to a real path to artifact if it exists.
526
joychen121fc9b2013-08-02 14:30:30 -0700527 Equivalent to the Get call, minus downloading and updating timestamps,
joychen346531c2013-07-24 16:55:56 -0700528
joychen7c2054a2013-07-25 11:14:07 -0700529 Returns:
joychen121fc9b2013-08-02 14:30:30 -0700530 build_id: Path to the image or update directory on the devserver.
531 e.g. 'x86-generic/R26-4000.0.0'
532 The returned path is always the path to the directory within
533 static_dir, so it is always the build_id of the image.
534 file_name: The file name of the artifact. Can take any of the file
535 values in devserver_constants.
536 e.g. 'chromiumos_test_image.bin' or 'update.gz' if the path list
537 specified 'test' or 'full_payload' artifacts, respectively.
joychen7c2054a2013-07-25 11:14:07 -0700538
joychen121fc9b2013-08-02 14:30:30 -0700539 Raises:
540 XBuddyException: if the path couldn't be translated
joychen346531c2013-07-24 16:55:56 -0700541 """
542 self._SyncRegistryWithBuildImages()
joychen121fc9b2013-08-02 14:30:30 -0700543 build_id, file_name = self._GetArtifact(path_list, board, lookup_only=True)
joychen346531c2013-07-24 16:55:56 -0700544
joychen121fc9b2013-08-02 14:30:30 -0700545 _Log('Returning path to payload: %s/%s', build_id, file_name)
546 return build_id, file_name
joychen346531c2013-07-24 16:55:56 -0700547
joychen121fc9b2013-08-02 14:30:30 -0700548 def Get(self, path_list, board=None):
joychen921e1fb2013-06-28 11:12:20 -0700549 """The full xBuddy call, returns resource specified by path_list.
joychen3cb228e2013-06-12 12:13:13 -0700550
551 Please see devserver.py:xbuddy for full documentation.
joychen121fc9b2013-08-02 14:30:30 -0700552
joychen3cb228e2013-06-12 12:13:13 -0700553 Args:
joychen921e1fb2013-06-28 11:12:20 -0700554 path_list: [board, version, alias] as split from the xbuddy call url
joychen121fc9b2013-08-02 14:30:30 -0700555 board: string, if set, it will override the default board in path rewrites
joychen3cb228e2013-06-12 12:13:13 -0700556
557 Returns:
joychen121fc9b2013-08-02 14:30:30 -0700558 build_id: Path to the image or update directory on the devserver.
559 e.g. 'x86-generic/R26-4000.0.0'
560 The returned path is always the path to the directory within
561 static_dir, so it is always the build_id of the image.
562 file_name: The file name of the artifact. Can take any of the file
563 values in devserver_constants.
564 e.g. 'chromiumos_test_image.bin' or 'update.gz' if the path list
565 specified 'test' or 'full_payload' artifacts, respectively.
joychen3cb228e2013-06-12 12:13:13 -0700566
567 Raises:
joychen121fc9b2013-08-02 14:30:30 -0700568 XBuddyException: if path is invalid
joychen3cb228e2013-06-12 12:13:13 -0700569 """
joychen7df67f72013-07-18 14:21:12 -0700570 self._SyncRegistryWithBuildImages()
joychen121fc9b2013-08-02 14:30:30 -0700571 build_id, file_name = self._GetArtifact(path_list, board)
joychen3cb228e2013-06-12 12:13:13 -0700572
joychen921e1fb2013-06-28 11:12:20 -0700573 Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
joychen3cb228e2013-06-12 12:13:13 -0700574 #TODO (joyc): run in sep thread
575 self._CleanCache()
576
joychen121fc9b2013-08-02 14:30:30 -0700577 _Log('Returning path to payload: %s/%s', build_id, file_name)
578 return build_id, file_name