blob: d4acccf7a39ab3de4427e577f029eee451dcf50c [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
Frank Farzan37761d12011-12-01 14:29:08 -08007import distutils.version
8import errno
9import os
Chris Masone816e38c2012-05-02 12:22:36 -070010import random
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070011import re
Frank Farzan37761d12011-12-01 14:29:08 -080012import shutil
Chris Masone816e38c2012-05-02 12:22:36 -070013import time
Frank Farzan37761d12011-12-01 14:29:08 -080014
Gilad Arnoldabb352e2012-09-23 01:24:27 -070015import lockfile
16
Gilad Arnoldc65330c2012-09-20 15:17:48 -070017import build_artifact
Chris Sosa47a7d4e2012-03-28 11:26:55 -070018import gsutil_util
Gilad Arnoldc65330c2012-09-20 15:17:48 -070019import log_util
20
21
22# Module-local log function.
23def _Log(message, *args, **kwargs):
24 return log_util.LogWithTag('UTIL', message, *args, **kwargs)
25
Frank Farzan37761d12011-12-01 14:29:08 -080026
27AU_BASE = 'au'
28NTON_DIR_SUFFIX = '_nton'
29MTON_DIR_SUFFIX = '_mton'
Frank Farzan37761d12011-12-01 14:29:08 -080030DEV_BUILD_PREFIX = 'dev'
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070031UPLOADED_LIST = 'UPLOADED'
Gilad Arnold5174ca22012-09-12 10:49:09 -070032DEVSERVER_LOCK_FILE = 'devserver'
Frank Farzan37761d12011-12-01 14:29:08 -080033
Gilad Arnold6f99b982012-09-12 10:49:40 -070034
35def CommaSeparatedList(value_list, is_quoted=False):
36 """Concatenates a list of strings.
37
38 This turns ['a', 'b', 'c'] into a single string 'a, b and c'. It optionally
39 adds quotes (`a') around each element. Used for logging.
40
41 """
42 if is_quoted:
43 value_list = ["`" + value + "'" for value in value_list]
44
45 if len(value_list) > 1:
46 return (', '.join(value_list[:-1]) + ' and ' + value_list[-1])
47 elif value_list:
48 return value_list[0]
49 else:
50 return ''
51
Frank Farzan37761d12011-12-01 14:29:08 -080052class DevServerUtilError(Exception):
53 """Exception classes used by this module."""
54 pass
55
56
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070057def ParsePayloadList(archive_url, payload_list):
Frank Farzan37761d12011-12-01 14:29:08 -080058 """Parse and return the full/delta payload URLs.
59
60 Args:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070061 archive_url: The URL of the Google Storage bucket.
62 payload_list: A list filenames.
Frank Farzan37761d12011-12-01 14:29:08 -080063
64 Returns:
65 Tuple of 3 payloads URLs: (full, nton, mton).
66
67 Raises:
68 DevServerUtilError: If payloads missing or invalid.
69 """
70 full_payload_url = None
71 mton_payload_url = None
72 nton_payload_url = None
73 for payload in payload_list:
74 if '_full_' in payload:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070075 full_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080076 elif '_delta_' in payload:
77 # e.g. chromeos_{from_version}_{to_version}_x86-generic_delta_dev.bin
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070078 from_version, to_version = payload.split('_')[1:3]
Frank Farzan37761d12011-12-01 14:29:08 -080079 if from_version == to_version:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070080 nton_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080081 else:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070082 mton_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080083
Chris Sosa1228a1a2012-05-22 17:12:13 -070084 if not full_payload_url:
Frank Farzan37761d12011-12-01 14:29:08 -080085 raise DevServerUtilError(
Chris Sosa1228a1a2012-05-22 17:12:13 -070086 'Full payload is missing or has unexpected name format.', payload_list)
Frank Farzan37761d12011-12-01 14:29:08 -080087
88 return full_payload_url, nton_payload_url, mton_payload_url
89
90
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070091def IsAvailable(pattern_list, uploaded_list):
92 """Checks whether the target artifacts we wait for are available.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -070093
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070094 This method searches the uploaded_list for a match for every pattern
95 in the pattern_list. It aborts and returns false if no filename
96 matches a given pattern.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -070097
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070098 Args:
99 pattern_list: List of regular expression patterns to identify
100 the target artifacts.
101 uploaded_list: List of all uploaded files.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700102
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700103 Returns:
104 True if there is a match for every pattern; false otherwise.
105 """
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700106
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700107 # Pre-compile the regular expression patterns
108 compiled_patterns = []
109 for p in pattern_list:
110 compiled_patterns.append(re.compile(p))
111
112 for pattern in compiled_patterns:
113 found = False
114 for filename in uploaded_list:
115 if re.search(pattern, filename):
116 found = True
117 break
118 if not found:
119 return False
120
121 return True
122
123
124def WaitUntilAvailable(to_wait_list, archive_url, err_str, timeout=600,
125 delay=10):
126 """Waits until all target artifacts are available in Google Storage or
127 until the request times out.
128
129 This method polls Google Storage until all target artifacts are
130 available or until the timeout occurs. Because we may not know the
131 exact name of the target artifacts, the method accepts to_wait_list, a
132 list of filename patterns, to identify whether an artifact whose name
133 matches the pattern exists (e.g. use pattern '_full_' to search for
134 the full payload 'chromeos_R17-1413.0.0-a1_x86-mario_full_dev.bin').
135
136 Args:
137 to_wait_list: List of regular expression patterns to identify
138 the target artifacts.
139 archive_url: URL of the Google Storage bucket.
140 err_str: String to display in the error message.
141
142 Returns:
143 The list of artifacts in the Google Storage bucket.
144
145 Raises:
146 DevServerUtilError: If timeout occurs.
147 """
148
149 cmd = 'gsutil cat %s/%s' % (archive_url, UPLOADED_LIST)
150 msg = 'Failed to get a list of uploaded files.'
151
152 deadline = time.time() + timeout
153 while time.time() < deadline:
Yu-Ju Hong92784052012-08-22 13:08:54 -0700154 uploaded_list = []
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700155 to_delay = delay + random.uniform(.5 * delay, 1.5 * delay)
Yu-Ju Hong92784052012-08-22 13:08:54 -0700156 try:
157 # Run "gsutil cat" to retrieve the list.
158 uploaded_list = gsutil_util.GSUtilRun(cmd, msg).splitlines()
159 except gsutil_util.GSUtilError:
160 # For backward compatibility, fallling back to use "gsutil ls"
161 # when the manifest file is not present.
162 cmd = 'gsutil ls %s/*' % archive_url
163 msg = 'Failed to list payloads.'
164 payload_list = gsutil_util.GSUtilRun(cmd, msg).splitlines()
165 for payload in payload_list:
166 uploaded_list.append(payload.rsplit('/', 1)[1])
167
168 # Check if all target artifacts are available.
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700169 if IsAvailable(to_wait_list, uploaded_list):
170 return uploaded_list
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700171 _Log('Retrying in %f seconds...%s' % (to_delay, err_str))
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700172 time.sleep(to_delay)
173
174 raise DevServerUtilError('Missing %s for %s.' % (err_str, archive_url))
175
176
Gilad Arnold6f99b982012-09-12 10:49:40 -0700177def GatherArtifactDownloads(main_staging_dir, archive_url, build_dir, build,
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700178 timeout=600, delay=10):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700179 """Generates artifacts that we mean to download and install for autotest.
Frank Farzan37761d12011-12-01 14:29:08 -0800180
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700181 This method generates the list of artifacts we will need for autotest. These
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700182 artifacts are instances of build_artifact.BuildArtifact.
Chris Masone816e38c2012-05-02 12:22:36 -0700183
184 Note, these artifacts can be downloaded asynchronously iff
185 !artifact.Synchronous().
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700186 """
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700187
188 # Wait up to 10 minutes for the full payload to be uploaded because we
189 # do not know the exact name of the full payload.
190
191 # We also wait for 'autotest.tar' because we do not know what type of
192 # autotest tarballs (tar or tar.bz2) is available
193 # (crosbug.com/32312). This dependency can be removed once all
194 # branches move to the new 'tar' format.
195 to_wait_list = ['_full_', 'autotest.tar']
196 err_str = 'full payload or autotest tarball'
197 uploaded_list = WaitUntilAvailable(to_wait_list, archive_url, err_str,
198 timeout=600)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700199
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700200 # First we gather the urls/paths for the update payloads.
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700201 full_url, nton_url, mton_url = ParsePayloadList(archive_url, uploaded_list)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700202
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700203 full_payload = os.path.join(build_dir, build_artifact.ROOT_UPDATE)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700204
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700205 artifacts = []
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700206 artifacts.append(build_artifact.BuildArtifact(
207 full_url, main_staging_dir, full_payload, synchronous=True))
Chris Sosa1228a1a2012-05-22 17:12:13 -0700208
209 if nton_url:
210 nton_payload = os.path.join(build_dir, AU_BASE, build + NTON_DIR_SUFFIX,
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700211 build_artifact.ROOT_UPDATE)
212 artifacts.append(build_artifact.AUTestPayloadBuildArtifact(
213 nton_url, main_staging_dir, nton_payload))
Chris Sosa1228a1a2012-05-22 17:12:13 -0700214
Chris Sosa781ba6d2012-04-11 12:44:43 -0700215 if mton_url:
216 mton_payload = os.path.join(build_dir, AU_BASE, build + MTON_DIR_SUFFIX,
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700217 build_artifact.ROOT_UPDATE)
218 artifacts.append(build_artifact.AUTestPayloadBuildArtifact(
Chris Sosa781ba6d2012-04-11 12:44:43 -0700219 mton_url, main_staging_dir, mton_payload))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700220
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700221
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700222 # Gather information about autotest tarballs. Use autotest.tar if available.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700223 if build_artifact.AUTOTEST_PACKAGE in uploaded_list:
224 autotest_url = '%s/%s' % (archive_url, build_artifact.AUTOTEST_PACKAGE)
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700225 else:
226 # Use autotest.tar.bz for backward compatibility. This can be
227 # removed once all branches start using "autotest.tar"
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700228 autotest_url = '%s/%s' % (
229 archive_url, build_artifact.AUTOTEST_ZIPPED_PACKAGE)
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700230
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700231 # Next we gather the miscellaneous payloads.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700232 stateful_url = archive_url + '/' + build_artifact.STATEFUL_UPDATE
233 test_suites_url = (archive_url + '/' + build_artifact.TEST_SUITES_PACKAGE)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700234
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700235 stateful_payload = os.path.join(build_dir, build_artifact.STATEFUL_UPDATE)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700236
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700237 artifacts.append(build_artifact.BuildArtifact(
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700238 stateful_url, main_staging_dir, stateful_payload, synchronous=True))
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700239 artifacts.append(build_artifact.AutotestTarballBuildArtifact(
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700240 autotest_url, main_staging_dir, build_dir))
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700241 artifacts.append(build_artifact.TarballBuildArtifact(
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700242 test_suites_url, main_staging_dir, build_dir, synchronous=True))
243 return artifacts
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700244
245
Chris Masone816e38c2012-05-02 12:22:36 -0700246def GatherSymbolArtifactDownloads(temp_download_dir, archive_url, staging_dir,
247 timeout=600, delay=10):
248 """Generates debug symbol artifacts that we mean to download and stage.
249
250 This method generates the list of artifacts we will need to
251 symbolicate crash dumps that occur during autotest runs. These
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700252 artifacts are instances of build_artifact.BuildArtifact.
Chris Masone816e38c2012-05-02 12:22:36 -0700253
254 This will poll google storage until the debug symbol artifact becomes
255 available, or until the 10 minute timeout is up.
256
257 @param temp_download_dir: the tempdir into which we're downloading artifacts
258 prior to staging them.
259 @param archive_url: the google storage url of the bucket where the debug
260 symbols for the desired build are stored.
261 @param staging_dir: the dir into which to stage the symbols
262
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700263 @return an iterable of one DebugTarballBuildArtifact pointing to the right
264 debug symbols. This is an iterable so that it's similar to
265 GatherArtifactDownloads. Also, it's possible that someday we might
266 have more than one.
Chris Masone816e38c2012-05-02 12:22:36 -0700267 """
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700268
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700269 artifact_name = build_artifact.DEBUG_SYMBOLS
Gilad Arnold6f99b982012-09-12 10:49:40 -0700270 WaitUntilAvailable([artifact_name], archive_url, 'debug symbols',
271 timeout=timeout, delay=delay)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700272 artifact = build_artifact.DebugTarballBuildArtifact(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700273 archive_url + '/' + artifact_name,
274 temp_download_dir,
275 staging_dir)
276 return [artifact]
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700277
Gilad Arnold6f99b982012-09-12 10:49:40 -0700278
279def GatherImageArchiveArtifactDownloads(temp_download_dir, archive_url,
280 staging_dir, image_file_list,
281 timeout=600, delay=10):
282 """Generates image archive artifact(s) for downloading / staging.
283
284 Generates the list of artifacts that are used for extracting Chrome OS images
285 from. Currently, it returns a single artifact, which is a zipfile configured
286 to extract a given list of images. It first polls Google Storage unti lthe
287 desired artifacts become available (or a timeout expires).
288
289 Args:
290 temp_download_dir: temporary directory, used for downloading artifacts
291 archive_url: URI to the bucket where the artifacts are stored
292 staging_dir: directory into which to stage the extracted files
293 image_file_list: list of image files to be extracted
294 Returns:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700295 list of downloadable artifacts (of type ZipfileBuildArtifact), currently
296 containing a single obejct
Gilad Arnold6f99b982012-09-12 10:49:40 -0700297 """
298
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700299 artifact_name = build_artifact.IMAGE_ARCHIVE
Gilad Arnold6f99b982012-09-12 10:49:40 -0700300 WaitUntilAvailable([artifact_name], archive_url, 'image archive',
301 timeout=timeout, delay=delay)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700302 artifact = build_artifact.ZipfileBuildArtifact(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700303 archive_url + '/' + artifact_name,
304 temp_download_dir, staging_dir,
305 unzip_file_list=image_file_list)
306 return [artifact]
Chris Masone816e38c2012-05-02 12:22:36 -0700307
308
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700309def PrepareBuildDirectory(build_dir):
310 """Preliminary staging of installation directory for build.
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700311
312 Args:
Frank Farzan37761d12011-12-01 14:29:08 -0800313 build_dir: Directory to install build components into.
314 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700315 if not os.path.isdir(build_dir):
316 os.path.makedirs(build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800317
318 # Create blank chromiumos_test_image.bin. Otherwise the Dev Server will
319 # try to rebuild it unnecessarily.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700320 test_image = os.path.join(build_dir, build_artifact.TEST_IMAGE)
Frank Farzan37761d12011-12-01 14:29:08 -0800321 open(test_image, 'a').close()
322
Frank Farzan37761d12011-12-01 14:29:08 -0800323
324def SafeSandboxAccess(static_dir, path):
325 """Verify that the path is in static_dir.
326
327 Args:
328 static_dir: Directory where builds are served from.
329 path: Path to verify.
330
331 Returns:
332 True if path is in static_dir, False otherwise
333 """
334 static_dir = os.path.realpath(static_dir)
335 path = os.path.realpath(path)
336 return (path.startswith(static_dir) and path != static_dir)
337
338
Gilad Arnold5174ca22012-09-12 10:49:09 -0700339def AcquireLock(static_dir, tag, create_once=True):
Frank Farzan37761d12011-12-01 14:29:08 -0800340 """Acquires a lock for a given tag.
341
Gilad Arnold5174ca22012-09-12 10:49:09 -0700342 Creates a directory for the specified tag, and atomically creates a lock file
343 in it. This tells other components the resource/task represented by the tag
344 is unavailable.
Frank Farzan37761d12011-12-01 14:29:08 -0800345
346 Args:
Gilad Arnold5174ca22012-09-12 10:49:09 -0700347 static_dir: Directory where builds are served from.
348 tag: Unique resource/task identifier. Use '/' for nested tags.
349 create_once: Determines whether the directory must be freshly created; this
350 preserves previous semantics of the lock acquisition.
Frank Farzan37761d12011-12-01 14:29:08 -0800351
352 Returns:
353 Path to the created directory or None if creation failed.
354
355 Raises:
356 DevServerUtilError: If lock can't be acquired.
357 """
358 build_dir = os.path.join(static_dir, tag)
359 if not SafeSandboxAccess(static_dir, build_dir):
Chris Sosa9164ca32012-03-28 11:04:50 -0700360 raise DevServerUtilError('Invalid tag "%s".' % tag)
Frank Farzan37761d12011-12-01 14:29:08 -0800361
Gilad Arnold5174ca22012-09-12 10:49:09 -0700362 # Create the directory.
363 is_created = False
Frank Farzan37761d12011-12-01 14:29:08 -0800364 try:
365 os.makedirs(build_dir)
Gilad Arnold5174ca22012-09-12 10:49:09 -0700366 is_created = True
Frank Farzan37761d12011-12-01 14:29:08 -0800367 except OSError, e:
368 if e.errno == errno.EEXIST:
Gilad Arnold5174ca22012-09-12 10:49:09 -0700369 if create_once:
370 raise DevServerUtilError(str(e))
Frank Farzan37761d12011-12-01 14:29:08 -0800371 else:
372 raise
373
Gilad Arnold5174ca22012-09-12 10:49:09 -0700374 # Lock the directory.
375 try:
376 lock = lockfile.FileLock(os.path.join(build_dir, DEVSERVER_LOCK_FILE))
377 lock.acquire(timeout=0)
378 except lockfile.AlreadyLocked, e:
379 raise DevServerUtilError(str(e))
380 except:
381 # In any other case, remove the directory if we actually created it, so
382 # that subsequent attempts won't fail to re-create it.
383 if is_created:
384 shutil.rmtree(build_dir)
385 raise
386
Frank Farzan37761d12011-12-01 14:29:08 -0800387 return build_dir
388
389
Gilad Arnold5174ca22012-09-12 10:49:09 -0700390def ReleaseLock(static_dir, tag, destroy=False):
391 """Releases the lock for a given tag.
392
393 Optionally, removes the locked directory entirely.
Frank Farzan37761d12011-12-01 14:29:08 -0800394
395 Args:
396 static_dir: Directory where builds are served from.
Gilad Arnold5174ca22012-09-12 10:49:09 -0700397 tag: Unique resource/task identifier. Use '/' for nested tags.
398 destroy: Determines whether the locked directory should be removed
399 entirely.
Frank Farzan37761d12011-12-01 14:29:08 -0800400
401 Raises:
402 DevServerUtilError: If lock can't be released.
403 """
404 build_dir = os.path.join(static_dir, tag)
405 if not SafeSandboxAccess(static_dir, build_dir):
406 raise DevServerUtilError('Invaid tag "%s".' % tag)
407
Gilad Arnold5174ca22012-09-12 10:49:09 -0700408 lock = lockfile.FileLock(os.path.join(build_dir, DEVSERVER_LOCK_FILE))
409 if lock.i_am_locking():
410 try:
411 lock.release()
412 if destroy:
413 shutil.rmtree(build_dir)
414 except Exception, e:
415 raise DevServerUtilError(str(e))
416 else:
417 raise DevServerUtilError('thread attempting release is not locking %s' %
418 build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800419
420
421def FindMatchingBoards(static_dir, board):
422 """Returns a list of boards given a partial board name.
423
424 Args:
425 static_dir: Directory where builds are served from.
426 board: Partial board name for this build; e.g. x86-generic.
427
428 Returns:
429 Returns a list of boards given a partial board.
430 """
431 return [brd for brd in os.listdir(static_dir) if board in brd]
432
433
434def FindMatchingBuilds(static_dir, board, build):
435 """Returns a list of matching builds given a board and partial build.
436
437 Args:
438 static_dir: Directory where builds are served from.
439 board: Partial board name for this build; e.g. x86-generic-release.
440 build: Partial build string to look for; e.g. R17-1234.
441
442 Returns:
443 Returns a list of (board, build) tuples given a partial board and build.
444 """
445 matches = []
446 for brd in FindMatchingBoards(static_dir, board):
447 a = [(brd, bld) for bld in
448 os.listdir(os.path.join(static_dir, brd)) if build in bld]
449 matches.extend(a)
450 return matches
451
452
Scott Zawalski16954532012-03-20 15:31:36 -0400453def GetLatestBuildVersion(static_dir, target, milestone=None):
Frank Farzan37761d12011-12-01 14:29:08 -0800454 """Retrieves the latest build version for a given board.
455
456 Args:
457 static_dir: Directory where builds are served from.
Scott Zawalski16954532012-03-20 15:31:36 -0400458 target: The build target, typically a combination of the board and the
459 type of build e.g. x86-mario-release.
460 milestone: For latest build set to None, for builds only in a specific
461 milestone set to a str of format Rxx (e.g. R16). Default: None.
Frank Farzan37761d12011-12-01 14:29:08 -0800462
463 Returns:
Scott Zawalski16954532012-03-20 15:31:36 -0400464 If latest found, a full build string is returned e.g. R17-1234.0.0-a1-b983.
465 If no latest is found for some reason or another a '' string is returned.
Frank Farzan37761d12011-12-01 14:29:08 -0800466
467 Raises:
Scott Zawalski16954532012-03-20 15:31:36 -0400468 DevServerUtilError: If for some reason the latest build cannot be
469 deteremined, this could be due to the dir not existing or no builds
470 being present after filtering on milestone.
Frank Farzan37761d12011-12-01 14:29:08 -0800471 """
Scott Zawalski16954532012-03-20 15:31:36 -0400472 target_path = os.path.join(static_dir, target)
473 if not os.path.isdir(target_path):
474 raise DevServerUtilError('Cannot find path %s' % target_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800475
Scott Zawalski16954532012-03-20 15:31:36 -0400476 builds = [distutils.version.LooseVersion(build) for build in
477 os.listdir(target_path)]
Frank Farzan37761d12011-12-01 14:29:08 -0800478
Scott Zawalski16954532012-03-20 15:31:36 -0400479 if milestone and builds:
480 # Check if milestone Rxx is in the string representation of the build.
481 builds = filter(lambda x: milestone.upper() in str(x), builds)
Frank Farzan37761d12011-12-01 14:29:08 -0800482
Scott Zawalski16954532012-03-20 15:31:36 -0400483 if not builds:
484 raise DevServerUtilError('Could not determine build for %s' % target)
Frank Farzan37761d12011-12-01 14:29:08 -0800485
Scott Zawalski16954532012-03-20 15:31:36 -0400486 return str(max(builds))
Frank Farzan37761d12011-12-01 14:29:08 -0800487
488
489def CloneBuild(static_dir, board, build, tag, force=False):
490 """Clone an official build into the developer sandbox.
491
492 Developer sandbox directory must already exist.
493
494 Args:
495 static_dir: Directory where builds are served from.
496 board: Fully qualified board name; e.g. x86-generic-release.
497 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
498 tag: Unique resource/task identifier. Use '/' for nested tags.
499 force: Force re-creation of build_dir even if it already exists.
500
501 Returns:
502 The path to the new build.
503 """
504 # Create the developer build directory.
505 dev_static_dir = os.path.join(static_dir, DEV_BUILD_PREFIX)
506 dev_build_dir = os.path.join(dev_static_dir, tag)
507 official_build_dir = os.path.join(static_dir, board, build)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700508 _Log('Cloning %s -> %s' % (official_build_dir, dev_build_dir))
Frank Farzan37761d12011-12-01 14:29:08 -0800509 dev_build_exists = False
510 try:
511 AcquireLock(dev_static_dir, tag)
512 except DevServerUtilError:
513 dev_build_exists = True
514 if force:
515 dev_build_exists = False
Gilad Arnold5174ca22012-09-12 10:49:09 -0700516 ReleaseLock(dev_static_dir, tag, destroy=True)
Frank Farzan37761d12011-12-01 14:29:08 -0800517 AcquireLock(dev_static_dir, tag)
518
519 # Make a copy of the official build, only take necessary files.
520 if not dev_build_exists:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700521 copy_list = [build_artifact.TEST_IMAGE,
522 build_artifact.ROOT_UPDATE,
523 build_artifact.STATEFUL_UPDATE]
Frank Farzan37761d12011-12-01 14:29:08 -0800524 for f in copy_list:
525 shutil.copy(os.path.join(official_build_dir, f), dev_build_dir)
526
527 return dev_build_dir
528
Scott Zawalski84a39c92012-01-13 15:12:42 -0500529
530def GetControlFile(static_dir, build, control_path):
Frank Farzan37761d12011-12-01 14:29:08 -0800531 """Attempts to pull the requested control file from the Dev Server.
532
533 Args:
534 static_dir: Directory where builds are served from.
Frank Farzan37761d12011-12-01 14:29:08 -0800535 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
536 control_path: Path to control file on Dev Server relative to Autotest root.
537
538 Raises:
539 DevServerUtilError: If lock can't be acquired.
540
541 Returns:
542 Content of the requested control file.
543 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500544 # Be forgiving if the user passes in the control_path with a leading /
545 control_path = control_path.lstrip('/')
Scott Zawalski84a39c92012-01-13 15:12:42 -0500546 control_path = os.path.join(static_dir, build, 'autotest',
Scott Zawalski4647ce62012-01-03 17:17:28 -0500547 control_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800548 if not SafeSandboxAccess(static_dir, control_path):
549 raise DevServerUtilError('Invaid control file "%s".' % control_path)
550
Scott Zawalski84a39c92012-01-13 15:12:42 -0500551 if not os.path.exists(control_path):
552 # TODO(scottz): Come up with some sort of error mechanism.
553 # crosbug.com/25040
554 return 'Unknown control path %s' % control_path
555
Frank Farzan37761d12011-12-01 14:29:08 -0800556 with open(control_path, 'r') as control_file:
557 return control_file.read()
558
559
Scott Zawalski84a39c92012-01-13 15:12:42 -0500560def GetControlFileList(static_dir, build):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500561 """List all control|control. files in the specified board/build path.
562
563 Args:
564 static_dir: Directory where builds are served from.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500565 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
566
567 Raises:
568 DevServerUtilError: If path is outside of sandbox.
569
570 Returns:
571 String of each file separated by a newline.
572 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500573 autotest_dir = os.path.join(static_dir, build, 'autotest/')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500574 if not SafeSandboxAccess(static_dir, autotest_dir):
575 raise DevServerUtilError('Autotest dir not in sandbox "%s".' % autotest_dir)
576
577 control_files = set()
Scott Zawalski84a39c92012-01-13 15:12:42 -0500578 if not os.path.exists(autotest_dir):
579 # TODO(scottz): Come up with some sort of error mechanism.
580 # crosbug.com/25040
581 return 'Unknown build path %s' % autotest_dir
582
Scott Zawalski4647ce62012-01-03 17:17:28 -0500583 for entry in os.walk(autotest_dir):
584 dir_path, _, files = entry
585 for file_entry in files:
586 if file_entry.startswith('control.') or file_entry == 'control':
587 control_files.add(os.path.join(dir_path,
Chris Sosaea148d92012-03-06 16:22:04 -0800588 file_entry).replace(autotest_dir, ''))
Scott Zawalski4647ce62012-01-03 17:17:28 -0500589
590 return '\n'.join(control_files)
591
592
Frank Farzan37761d12011-12-01 14:29:08 -0800593def ListAutoupdateTargets(static_dir, board, build):
594 """Returns a list of autoupdate test targets for the given board, build.
595
596 Args:
597 static_dir: Directory where builds are served from.
598 board: Fully qualified board name; e.g. x86-generic-release.
599 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
600
601 Returns:
602 List of autoupdate test targets; e.g. ['0.14.747.0-r2bf8859c-b2927_nton']
603 """
604 return os.listdir(os.path.join(static_dir, board, build, AU_BASE))