blob: cf76bf81b2d69cca3a5d6b58128f3acc88f611a1 [file] [log] [blame]
Chris Sosa47a7d4e2012-03-28 11:26:55 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Frank Farzan37761d12011-12-01 14:29:08 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Helper class for interacting with the Dev Server."""
6
7import cherrypy
8import distutils.version
9import errno
Gilad Arnold5174ca22012-09-12 10:49:09 -070010import lockfile
Frank Farzan37761d12011-12-01 14:29:08 -080011import os
Chris Masone816e38c2012-05-02 12:22:36 -070012import random
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070013import re
Frank Farzan37761d12011-12-01 14:29:08 -080014import shutil
Chris Masone816e38c2012-05-02 12:22:36 -070015import time
Frank Farzan37761d12011-12-01 14:29:08 -080016
Chris Sosa47a7d4e2012-03-28 11:26:55 -070017import downloadable_artifact
18import gsutil_util
Frank Farzan37761d12011-12-01 14:29:08 -080019
20AU_BASE = 'au'
21NTON_DIR_SUFFIX = '_nton'
22MTON_DIR_SUFFIX = '_mton'
Frank Farzan37761d12011-12-01 14:29:08 -080023DEV_BUILD_PREFIX = 'dev'
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070024UPLOADED_LIST = 'UPLOADED'
Gilad Arnold5174ca22012-09-12 10:49:09 -070025DEVSERVER_LOCK_FILE = 'devserver'
Frank Farzan37761d12011-12-01 14:29:08 -080026
27class DevServerUtilError(Exception):
28 """Exception classes used by this module."""
29 pass
30
31
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070032def ParsePayloadList(archive_url, payload_list):
Frank Farzan37761d12011-12-01 14:29:08 -080033 """Parse and return the full/delta payload URLs.
34
35 Args:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070036 archive_url: The URL of the Google Storage bucket.
37 payload_list: A list filenames.
Frank Farzan37761d12011-12-01 14:29:08 -080038
39 Returns:
40 Tuple of 3 payloads URLs: (full, nton, mton).
41
42 Raises:
43 DevServerUtilError: If payloads missing or invalid.
44 """
45 full_payload_url = None
46 mton_payload_url = None
47 nton_payload_url = None
48 for payload in payload_list:
49 if '_full_' in payload:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070050 full_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080051 elif '_delta_' in payload:
52 # e.g. chromeos_{from_version}_{to_version}_x86-generic_delta_dev.bin
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070053 from_version, to_version = payload.split('_')[1:3]
Frank Farzan37761d12011-12-01 14:29:08 -080054 if from_version == to_version:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070055 nton_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080056 else:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070057 mton_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080058
Chris Sosa1228a1a2012-05-22 17:12:13 -070059 if not full_payload_url:
Frank Farzan37761d12011-12-01 14:29:08 -080060 raise DevServerUtilError(
Chris Sosa1228a1a2012-05-22 17:12:13 -070061 'Full payload is missing or has unexpected name format.', payload_list)
Frank Farzan37761d12011-12-01 14:29:08 -080062
63 return full_payload_url, nton_payload_url, mton_payload_url
64
65
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070066def IsAvailable(pattern_list, uploaded_list):
67 """Checks whether the target artifacts we wait for are available.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -070068
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070069 This method searches the uploaded_list for a match for every pattern
70 in the pattern_list. It aborts and returns false if no filename
71 matches a given pattern.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -070072
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070073 Args:
74 pattern_list: List of regular expression patterns to identify
75 the target artifacts.
76 uploaded_list: List of all uploaded files.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -070077
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070078 Returns:
79 True if there is a match for every pattern; false otherwise.
80 """
Yu-Ju Honge61cbe92012-07-10 14:10:26 -070081
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070082 # Pre-compile the regular expression patterns
83 compiled_patterns = []
84 for p in pattern_list:
85 compiled_patterns.append(re.compile(p))
86
87 for pattern in compiled_patterns:
88 found = False
89 for filename in uploaded_list:
90 if re.search(pattern, filename):
91 found = True
92 break
93 if not found:
94 return False
95
96 return True
97
98
99def WaitUntilAvailable(to_wait_list, archive_url, err_str, timeout=600,
100 delay=10):
101 """Waits until all target artifacts are available in Google Storage or
102 until the request times out.
103
104 This method polls Google Storage until all target artifacts are
105 available or until the timeout occurs. Because we may not know the
106 exact name of the target artifacts, the method accepts to_wait_list, a
107 list of filename patterns, to identify whether an artifact whose name
108 matches the pattern exists (e.g. use pattern '_full_' to search for
109 the full payload 'chromeos_R17-1413.0.0-a1_x86-mario_full_dev.bin').
110
111 Args:
112 to_wait_list: List of regular expression patterns to identify
113 the target artifacts.
114 archive_url: URL of the Google Storage bucket.
115 err_str: String to display in the error message.
116
117 Returns:
118 The list of artifacts in the Google Storage bucket.
119
120 Raises:
121 DevServerUtilError: If timeout occurs.
122 """
123
124 cmd = 'gsutil cat %s/%s' % (archive_url, UPLOADED_LIST)
125 msg = 'Failed to get a list of uploaded files.'
126
127 deadline = time.time() + timeout
128 while time.time() < deadline:
Yu-Ju Hong92784052012-08-22 13:08:54 -0700129 uploaded_list = []
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700130 to_delay = delay + random.uniform(.5 * delay, 1.5 * delay)
Yu-Ju Hong92784052012-08-22 13:08:54 -0700131 try:
132 # Run "gsutil cat" to retrieve the list.
133 uploaded_list = gsutil_util.GSUtilRun(cmd, msg).splitlines()
134 except gsutil_util.GSUtilError:
135 # For backward compatibility, fallling back to use "gsutil ls"
136 # when the manifest file is not present.
137 cmd = 'gsutil ls %s/*' % archive_url
138 msg = 'Failed to list payloads.'
139 payload_list = gsutil_util.GSUtilRun(cmd, msg).splitlines()
140 for payload in payload_list:
141 uploaded_list.append(payload.rsplit('/', 1)[1])
142
143 # Check if all target artifacts are available.
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700144 if IsAvailable(to_wait_list, uploaded_list):
145 return uploaded_list
146 cherrypy.log('Retrying in %f seconds...%s' % (to_delay, err_str))
147 time.sleep(to_delay)
148
149 raise DevServerUtilError('Missing %s for %s.' % (err_str, archive_url))
150
151
152def GatherArtifactDownloads(main_staging_dir, archive_url, build, build_dir,
153 timeout=600, delay=10):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700154 """Generates artifacts that we mean to download and install for autotest.
Frank Farzan37761d12011-12-01 14:29:08 -0800155
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700156 This method generates the list of artifacts we will need for autotest. These
Chris Masone816e38c2012-05-02 12:22:36 -0700157 artifacts are instances of downloadable_artifact.DownloadableArtifact.
158
159 Note, these artifacts can be downloaded asynchronously iff
160 !artifact.Synchronous().
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700161 """
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700162
163 # Wait up to 10 minutes for the full payload to be uploaded because we
164 # do not know the exact name of the full payload.
165
166 # We also wait for 'autotest.tar' because we do not know what type of
167 # autotest tarballs (tar or tar.bz2) is available
168 # (crosbug.com/32312). This dependency can be removed once all
169 # branches move to the new 'tar' format.
170 to_wait_list = ['_full_', 'autotest.tar']
171 err_str = 'full payload or autotest tarball'
172 uploaded_list = WaitUntilAvailable(to_wait_list, archive_url, err_str,
173 timeout=600)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700174
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700175 # First we gather the urls/paths for the update payloads.
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700176 full_url, nton_url, mton_url = ParsePayloadList(archive_url, uploaded_list)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700177
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700178 full_payload = os.path.join(build_dir, downloadable_artifact.ROOT_UPDATE)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700179
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700180 artifacts = []
181 artifacts.append(downloadable_artifact.DownloadableArtifact(full_url,
182 main_staging_dir, full_payload, synchronous=True))
Chris Sosa1228a1a2012-05-22 17:12:13 -0700183
184 if nton_url:
185 nton_payload = os.path.join(build_dir, AU_BASE, build + NTON_DIR_SUFFIX,
186 downloadable_artifact.ROOT_UPDATE)
187 artifacts.append(downloadable_artifact.AUTestPayload(nton_url,
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700188 main_staging_dir, nton_payload))
Chris Sosa1228a1a2012-05-22 17:12:13 -0700189
Chris Sosa781ba6d2012-04-11 12:44:43 -0700190 if mton_url:
191 mton_payload = os.path.join(build_dir, AU_BASE, build + MTON_DIR_SUFFIX,
192 downloadable_artifact.ROOT_UPDATE)
193 artifacts.append(downloadable_artifact.AUTestPayload(
194 mton_url, main_staging_dir, mton_payload))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700195
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700196
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700197 # Gather information about autotest tarballs. Use autotest.tar if available.
198 if downloadable_artifact.AUTOTEST_PACKAGE in uploaded_list:
199 autotest_url = '%s/%s' % (archive_url,
200 downloadable_artifact.AUTOTEST_PACKAGE)
201 else:
202 # Use autotest.tar.bz for backward compatibility. This can be
203 # removed once all branches start using "autotest.tar"
204 autotest_url = '%s/%s' % (archive_url,
205 downloadable_artifact.AUTOTEST_ZIPPED_PACKAGE)
206
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700207 # Next we gather the miscellaneous payloads.
208 stateful_url = archive_url + '/' + downloadable_artifact.STATEFUL_UPDATE
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700209 test_suites_url = (archive_url + '/' +
210 downloadable_artifact.TEST_SUITES_PACKAGE)
211
212 stateful_payload = os.path.join(build_dir,
213 downloadable_artifact.STATEFUL_UPDATE)
214
215 artifacts.append(downloadable_artifact.DownloadableArtifact(
216 stateful_url, main_staging_dir, stateful_payload, synchronous=True))
217 artifacts.append(downloadable_artifact.AutotestTarball(
218 autotest_url, main_staging_dir, build_dir))
219 artifacts.append(downloadable_artifact.Tarball(
220 test_suites_url, main_staging_dir, build_dir, synchronous=True))
221 return artifacts
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700222
223
Chris Masone816e38c2012-05-02 12:22:36 -0700224def GatherSymbolArtifactDownloads(temp_download_dir, archive_url, staging_dir,
225 timeout=600, delay=10):
226 """Generates debug symbol artifacts that we mean to download and stage.
227
228 This method generates the list of artifacts we will need to
229 symbolicate crash dumps that occur during autotest runs. These
230 artifacts are instances of downloadable_artifact.DownloadableArtifact.
231
232 This will poll google storage until the debug symbol artifact becomes
233 available, or until the 10 minute timeout is up.
234
235 @param temp_download_dir: the tempdir into which we're downloading artifacts
236 prior to staging them.
237 @param archive_url: the google storage url of the bucket where the debug
238 symbols for the desired build are stored.
239 @param staging_dir: the dir into which to stage the symbols
240
241 @return an iterable of one DebugTarball pointing to the right debug symbols.
242 This is an iterable so that it's similar to GatherArtifactDownloads.
243 Also, it's possible that someday we might have more than one.
244 """
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700245
246 # Wait up to 10 minutes for the debug symbols to be uploaded.
247 to_wait_list = [downloadable_artifact.DEBUG_SYMBOLS]
248 err_str = 'debug symbols'
249 WaitUntilAvailable(to_wait_list, archive_url, err_str, timeout=timeout,
250 delay=delay)
251
Chris Masone816e38c2012-05-02 12:22:36 -0700252 symbol_url = archive_url + '/' + downloadable_artifact.DEBUG_SYMBOLS
Chris Masone816e38c2012-05-02 12:22:36 -0700253 return [downloadable_artifact.DebugTarball(symbol_url, temp_download_dir,
254 staging_dir)]
255
256
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700257def PrepareBuildDirectory(build_dir):
258 """Preliminary staging of installation directory for build.
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700259
260 Args:
Frank Farzan37761d12011-12-01 14:29:08 -0800261 build_dir: Directory to install build components into.
262 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700263 if not os.path.isdir(build_dir):
264 os.path.makedirs(build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800265
266 # Create blank chromiumos_test_image.bin. Otherwise the Dev Server will
267 # try to rebuild it unnecessarily.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700268 test_image = os.path.join(build_dir, downloadable_artifact.TEST_IMAGE)
Frank Farzan37761d12011-12-01 14:29:08 -0800269 open(test_image, 'a').close()
270
Frank Farzan37761d12011-12-01 14:29:08 -0800271
272def SafeSandboxAccess(static_dir, path):
273 """Verify that the path is in static_dir.
274
275 Args:
276 static_dir: Directory where builds are served from.
277 path: Path to verify.
278
279 Returns:
280 True if path is in static_dir, False otherwise
281 """
282 static_dir = os.path.realpath(static_dir)
283 path = os.path.realpath(path)
284 return (path.startswith(static_dir) and path != static_dir)
285
286
Gilad Arnold5174ca22012-09-12 10:49:09 -0700287def AcquireLock(static_dir, tag, create_once=True):
Frank Farzan37761d12011-12-01 14:29:08 -0800288 """Acquires a lock for a given tag.
289
Gilad Arnold5174ca22012-09-12 10:49:09 -0700290 Creates a directory for the specified tag, and atomically creates a lock file
291 in it. This tells other components the resource/task represented by the tag
292 is unavailable.
Frank Farzan37761d12011-12-01 14:29:08 -0800293
294 Args:
Gilad Arnold5174ca22012-09-12 10:49:09 -0700295 static_dir: Directory where builds are served from.
296 tag: Unique resource/task identifier. Use '/' for nested tags.
297 create_once: Determines whether the directory must be freshly created; this
298 preserves previous semantics of the lock acquisition.
Frank Farzan37761d12011-12-01 14:29:08 -0800299
300 Returns:
301 Path to the created directory or None if creation failed.
302
303 Raises:
304 DevServerUtilError: If lock can't be acquired.
305 """
306 build_dir = os.path.join(static_dir, tag)
307 if not SafeSandboxAccess(static_dir, build_dir):
Chris Sosa9164ca32012-03-28 11:04:50 -0700308 raise DevServerUtilError('Invalid tag "%s".' % tag)
Frank Farzan37761d12011-12-01 14:29:08 -0800309
Gilad Arnold5174ca22012-09-12 10:49:09 -0700310 # Create the directory.
311 is_created = False
Frank Farzan37761d12011-12-01 14:29:08 -0800312 try:
313 os.makedirs(build_dir)
Gilad Arnold5174ca22012-09-12 10:49:09 -0700314 is_created = True
Frank Farzan37761d12011-12-01 14:29:08 -0800315 except OSError, e:
316 if e.errno == errno.EEXIST:
Gilad Arnold5174ca22012-09-12 10:49:09 -0700317 if create_once:
318 raise DevServerUtilError(str(e))
Frank Farzan37761d12011-12-01 14:29:08 -0800319 else:
320 raise
321
Gilad Arnold5174ca22012-09-12 10:49:09 -0700322 # Lock the directory.
323 try:
324 lock = lockfile.FileLock(os.path.join(build_dir, DEVSERVER_LOCK_FILE))
325 lock.acquire(timeout=0)
326 except lockfile.AlreadyLocked, e:
327 raise DevServerUtilError(str(e))
328 except:
329 # In any other case, remove the directory if we actually created it, so
330 # that subsequent attempts won't fail to re-create it.
331 if is_created:
332 shutil.rmtree(build_dir)
333 raise
334
Frank Farzan37761d12011-12-01 14:29:08 -0800335 return build_dir
336
337
Gilad Arnold5174ca22012-09-12 10:49:09 -0700338def ReleaseLock(static_dir, tag, destroy=False):
339 """Releases the lock for a given tag.
340
341 Optionally, removes the locked directory entirely.
Frank Farzan37761d12011-12-01 14:29:08 -0800342
343 Args:
344 static_dir: Directory where builds are served from.
Gilad Arnold5174ca22012-09-12 10:49:09 -0700345 tag: Unique resource/task identifier. Use '/' for nested tags.
346 destroy: Determines whether the locked directory should be removed
347 entirely.
Frank Farzan37761d12011-12-01 14:29:08 -0800348
349 Raises:
350 DevServerUtilError: If lock can't be released.
351 """
352 build_dir = os.path.join(static_dir, tag)
353 if not SafeSandboxAccess(static_dir, build_dir):
354 raise DevServerUtilError('Invaid tag "%s".' % tag)
355
Gilad Arnold5174ca22012-09-12 10:49:09 -0700356 lock = lockfile.FileLock(os.path.join(build_dir, DEVSERVER_LOCK_FILE))
357 if lock.i_am_locking():
358 try:
359 lock.release()
360 if destroy:
361 shutil.rmtree(build_dir)
362 except Exception, e:
363 raise DevServerUtilError(str(e))
364 else:
365 raise DevServerUtilError('thread attempting release is not locking %s' %
366 build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800367
368
369def FindMatchingBoards(static_dir, board):
370 """Returns a list of boards given a partial board name.
371
372 Args:
373 static_dir: Directory where builds are served from.
374 board: Partial board name for this build; e.g. x86-generic.
375
376 Returns:
377 Returns a list of boards given a partial board.
378 """
379 return [brd for brd in os.listdir(static_dir) if board in brd]
380
381
382def FindMatchingBuilds(static_dir, board, build):
383 """Returns a list of matching builds given a board and partial build.
384
385 Args:
386 static_dir: Directory where builds are served from.
387 board: Partial board name for this build; e.g. x86-generic-release.
388 build: Partial build string to look for; e.g. R17-1234.
389
390 Returns:
391 Returns a list of (board, build) tuples given a partial board and build.
392 """
393 matches = []
394 for brd in FindMatchingBoards(static_dir, board):
395 a = [(brd, bld) for bld in
396 os.listdir(os.path.join(static_dir, brd)) if build in bld]
397 matches.extend(a)
398 return matches
399
400
Scott Zawalski16954532012-03-20 15:31:36 -0400401def GetLatestBuildVersion(static_dir, target, milestone=None):
Frank Farzan37761d12011-12-01 14:29:08 -0800402 """Retrieves the latest build version for a given board.
403
404 Args:
405 static_dir: Directory where builds are served from.
Scott Zawalski16954532012-03-20 15:31:36 -0400406 target: The build target, typically a combination of the board and the
407 type of build e.g. x86-mario-release.
408 milestone: For latest build set to None, for builds only in a specific
409 milestone set to a str of format Rxx (e.g. R16). Default: None.
Frank Farzan37761d12011-12-01 14:29:08 -0800410
411 Returns:
Scott Zawalski16954532012-03-20 15:31:36 -0400412 If latest found, a full build string is returned e.g. R17-1234.0.0-a1-b983.
413 If no latest is found for some reason or another a '' string is returned.
Frank Farzan37761d12011-12-01 14:29:08 -0800414
415 Raises:
Scott Zawalski16954532012-03-20 15:31:36 -0400416 DevServerUtilError: If for some reason the latest build cannot be
417 deteremined, this could be due to the dir not existing or no builds
418 being present after filtering on milestone.
Frank Farzan37761d12011-12-01 14:29:08 -0800419 """
Scott Zawalski16954532012-03-20 15:31:36 -0400420 target_path = os.path.join(static_dir, target)
421 if not os.path.isdir(target_path):
422 raise DevServerUtilError('Cannot find path %s' % target_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800423
Scott Zawalski16954532012-03-20 15:31:36 -0400424 builds = [distutils.version.LooseVersion(build) for build in
425 os.listdir(target_path)]
Frank Farzan37761d12011-12-01 14:29:08 -0800426
Scott Zawalski16954532012-03-20 15:31:36 -0400427 if milestone and builds:
428 # Check if milestone Rxx is in the string representation of the build.
429 builds = filter(lambda x: milestone.upper() in str(x), builds)
Frank Farzan37761d12011-12-01 14:29:08 -0800430
Scott Zawalski16954532012-03-20 15:31:36 -0400431 if not builds:
432 raise DevServerUtilError('Could not determine build for %s' % target)
Frank Farzan37761d12011-12-01 14:29:08 -0800433
Scott Zawalski16954532012-03-20 15:31:36 -0400434 return str(max(builds))
Frank Farzan37761d12011-12-01 14:29:08 -0800435
436
437def CloneBuild(static_dir, board, build, tag, force=False):
438 """Clone an official build into the developer sandbox.
439
440 Developer sandbox directory must already exist.
441
442 Args:
443 static_dir: Directory where builds are served from.
444 board: Fully qualified board name; e.g. x86-generic-release.
445 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
446 tag: Unique resource/task identifier. Use '/' for nested tags.
447 force: Force re-creation of build_dir even if it already exists.
448
449 Returns:
450 The path to the new build.
451 """
452 # Create the developer build directory.
453 dev_static_dir = os.path.join(static_dir, DEV_BUILD_PREFIX)
454 dev_build_dir = os.path.join(dev_static_dir, tag)
455 official_build_dir = os.path.join(static_dir, board, build)
456 cherrypy.log('Cloning %s -> %s' % (official_build_dir, dev_build_dir),
457 'DEVSERVER_UTIL')
458 dev_build_exists = False
459 try:
460 AcquireLock(dev_static_dir, tag)
461 except DevServerUtilError:
462 dev_build_exists = True
463 if force:
464 dev_build_exists = False
Gilad Arnold5174ca22012-09-12 10:49:09 -0700465 ReleaseLock(dev_static_dir, tag, destroy=True)
Frank Farzan37761d12011-12-01 14:29:08 -0800466 AcquireLock(dev_static_dir, tag)
467
468 # Make a copy of the official build, only take necessary files.
469 if not dev_build_exists:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700470 copy_list = [downloadable_artifact.TEST_IMAGE,
471 downloadable_artifact.ROOT_UPDATE,
472 downloadable_artifact.STATEFUL_UPDATE]
Frank Farzan37761d12011-12-01 14:29:08 -0800473 for f in copy_list:
474 shutil.copy(os.path.join(official_build_dir, f), dev_build_dir)
475
476 return dev_build_dir
477
Scott Zawalski84a39c92012-01-13 15:12:42 -0500478
479def GetControlFile(static_dir, build, control_path):
Frank Farzan37761d12011-12-01 14:29:08 -0800480 """Attempts to pull the requested control file from the Dev Server.
481
482 Args:
483 static_dir: Directory where builds are served from.
Frank Farzan37761d12011-12-01 14:29:08 -0800484 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
485 control_path: Path to control file on Dev Server relative to Autotest root.
486
487 Raises:
488 DevServerUtilError: If lock can't be acquired.
489
490 Returns:
491 Content of the requested control file.
492 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500493 # Be forgiving if the user passes in the control_path with a leading /
494 control_path = control_path.lstrip('/')
Scott Zawalski84a39c92012-01-13 15:12:42 -0500495 control_path = os.path.join(static_dir, build, 'autotest',
Scott Zawalski4647ce62012-01-03 17:17:28 -0500496 control_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800497 if not SafeSandboxAccess(static_dir, control_path):
498 raise DevServerUtilError('Invaid control file "%s".' % control_path)
499
Scott Zawalski84a39c92012-01-13 15:12:42 -0500500 if not os.path.exists(control_path):
501 # TODO(scottz): Come up with some sort of error mechanism.
502 # crosbug.com/25040
503 return 'Unknown control path %s' % control_path
504
Frank Farzan37761d12011-12-01 14:29:08 -0800505 with open(control_path, 'r') as control_file:
506 return control_file.read()
507
508
Scott Zawalski84a39c92012-01-13 15:12:42 -0500509def GetControlFileList(static_dir, build):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500510 """List all control|control. files in the specified board/build path.
511
512 Args:
513 static_dir: Directory where builds are served from.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500514 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
515
516 Raises:
517 DevServerUtilError: If path is outside of sandbox.
518
519 Returns:
520 String of each file separated by a newline.
521 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500522 autotest_dir = os.path.join(static_dir, build, 'autotest/')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500523 if not SafeSandboxAccess(static_dir, autotest_dir):
524 raise DevServerUtilError('Autotest dir not in sandbox "%s".' % autotest_dir)
525
526 control_files = set()
Scott Zawalski84a39c92012-01-13 15:12:42 -0500527 if not os.path.exists(autotest_dir):
528 # TODO(scottz): Come up with some sort of error mechanism.
529 # crosbug.com/25040
530 return 'Unknown build path %s' % autotest_dir
531
Scott Zawalski4647ce62012-01-03 17:17:28 -0500532 for entry in os.walk(autotest_dir):
533 dir_path, _, files = entry
534 for file_entry in files:
535 if file_entry.startswith('control.') or file_entry == 'control':
536 control_files.add(os.path.join(dir_path,
Chris Sosaea148d92012-03-06 16:22:04 -0800537 file_entry).replace(autotest_dir, ''))
Scott Zawalski4647ce62012-01-03 17:17:28 -0500538
539 return '\n'.join(control_files)
540
541
Frank Farzan37761d12011-12-01 14:29:08 -0800542def ListAutoupdateTargets(static_dir, board, build):
543 """Returns a list of autoupdate test targets for the given board, build.
544
545 Args:
546 static_dir: Directory where builds are served from.
547 board: Fully qualified board name; e.g. x86-generic-release.
548 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
549
550 Returns:
551 List of autoupdate test targets; e.g. ['0.14.747.0-r2bf8859c-b2927_nton']
552 """
553 return os.listdir(os.path.join(static_dir, board, build, AU_BASE))