blob: 55a95ff0d6f69df7c42d01b7e9d7c7e53095947d [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
Gilad Arnold55a2a372012-10-02 09:46:32 -07007import base64
8import binascii
Frank Farzan37761d12011-12-01 14:29:08 -08009import distutils.version
10import errno
Gilad Arnold55a2a372012-10-02 09:46:32 -070011import hashlib
Frank Farzan37761d12011-12-01 14:29:08 -080012import os
Chris Masone816e38c2012-05-02 12:22:36 -070013import random
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070014import re
Frank Farzan37761d12011-12-01 14:29:08 -080015import shutil
Chris Masone816e38c2012-05-02 12:22:36 -070016import time
Frank Farzan37761d12011-12-01 14:29:08 -080017
Gilad Arnoldabb352e2012-09-23 01:24:27 -070018import lockfile
19
Gilad Arnoldc65330c2012-09-20 15:17:48 -070020import build_artifact
Chris Sosa47a7d4e2012-03-28 11:26:55 -070021import gsutil_util
Gilad Arnoldc65330c2012-09-20 15:17:48 -070022import log_util
23
24
25# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080026def _Log(message, *args):
27 return log_util.LogWithTag('UTIL', message, *args)
Gilad Arnoldc65330c2012-09-20 15:17:48 -070028
Frank Farzan37761d12011-12-01 14:29:08 -080029
30AU_BASE = 'au'
31NTON_DIR_SUFFIX = '_nton'
32MTON_DIR_SUFFIX = '_mton'
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070033UPLOADED_LIST = 'UPLOADED'
Gilad Arnold5174ca22012-09-12 10:49:09 -070034DEVSERVER_LOCK_FILE = 'devserver'
Frank Farzan37761d12011-12-01 14:29:08 -080035
Gilad Arnold55a2a372012-10-02 09:46:32 -070036_HASH_BLOCK_SIZE = 8192
37
Gilad Arnold6f99b982012-09-12 10:49:40 -070038
39def CommaSeparatedList(value_list, is_quoted=False):
40 """Concatenates a list of strings.
41
42 This turns ['a', 'b', 'c'] into a single string 'a, b and c'. It optionally
43 adds quotes (`a') around each element. Used for logging.
44
45 """
46 if is_quoted:
47 value_list = ["`" + value + "'" for value in value_list]
48
49 if len(value_list) > 1:
50 return (', '.join(value_list[:-1]) + ' and ' + value_list[-1])
51 elif value_list:
52 return value_list[0]
53 else:
54 return ''
55
Gilad Arnold17fe03d2012-10-02 10:05:01 -070056class CommonUtilError(Exception):
Frank Farzan37761d12011-12-01 14:29:08 -080057 """Exception classes used by this module."""
58 pass
59
60
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070061def ParsePayloadList(archive_url, payload_list):
Vadim Bendeburyd3698392012-12-27 18:21:32 -080062 """Parse and return the full, delta, and firmware payload URLs.
Frank Farzan37761d12011-12-01 14:29:08 -080063
64 Args:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070065 archive_url: The URL of the Google Storage bucket.
66 payload_list: A list filenames.
Frank Farzan37761d12011-12-01 14:29:08 -080067
68 Returns:
Vadim Bendeburyd3698392012-12-27 18:21:32 -080069 Tuple of 4 payload URLs: (full, nton, mton, firmware).
Frank Farzan37761d12011-12-01 14:29:08 -080070
71 Raises:
Vadim Bendeburyd3698392012-12-27 18:21:32 -080072 CommonUtilError: If full payload is missing or invalid.
Frank Farzan37761d12011-12-01 14:29:08 -080073 """
74 full_payload_url = None
75 mton_payload_url = None
76 nton_payload_url = None
Vadim Bendeburyd3698392012-12-27 18:21:32 -080077 firmware_payload_url = None
78
Frank Farzan37761d12011-12-01 14:29:08 -080079 for payload in payload_list:
80 if '_full_' in payload:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070081 full_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080082 elif '_delta_' in payload:
83 # e.g. chromeos_{from_version}_{to_version}_x86-generic_delta_dev.bin
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070084 from_version, to_version = payload.split('_')[1:3]
Frank Farzan37761d12011-12-01 14:29:08 -080085 if from_version == to_version:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070086 nton_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080087 else:
Yu-Ju Hong825ddc32012-08-13 18:47:10 -070088 mton_payload_url = '/'.join([archive_url, payload])
Vadim Bendeburyd3698392012-12-27 18:21:32 -080089 elif build_artifact.FIRMWARE_ARCHIVE in payload:
Chris Sosa6a3697f2013-01-29 16:44:43 -080090 firmware_payload_url = '/'.join([archive_url, payload])
Frank Farzan37761d12011-12-01 14:29:08 -080091
Chris Sosa1228a1a2012-05-22 17:12:13 -070092 if not full_payload_url:
Gilad Arnold17fe03d2012-10-02 10:05:01 -070093 raise CommonUtilError(
Chris Sosa1228a1a2012-05-22 17:12:13 -070094 'Full payload is missing or has unexpected name format.', payload_list)
Frank Farzan37761d12011-12-01 14:29:08 -080095
Vadim Bendeburyd3698392012-12-27 18:21:32 -080096 return (full_payload_url, nton_payload_url,
97 mton_payload_url, firmware_payload_url)
Frank Farzan37761d12011-12-01 14:29:08 -080098
99
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700100def IsAvailable(pattern_list, uploaded_list):
101 """Checks whether the target artifacts we wait for are available.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700102
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700103 This method searches the uploaded_list for a match for every pattern
104 in the pattern_list. It aborts and returns false if no filename
105 matches a given pattern.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700106
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700107 Args:
108 pattern_list: List of regular expression patterns to identify
109 the target artifacts.
110 uploaded_list: List of all uploaded files.
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700111
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700112 Returns:
113 True if there is a match for every pattern; false otherwise.
114 """
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700115
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700116 # Pre-compile the regular expression patterns
117 compiled_patterns = []
118 for p in pattern_list:
119 compiled_patterns.append(re.compile(p))
120
121 for pattern in compiled_patterns:
122 found = False
123 for filename in uploaded_list:
124 if re.search(pattern, filename):
125 found = True
126 break
127 if not found:
128 return False
129
130 return True
131
132
133def WaitUntilAvailable(to_wait_list, archive_url, err_str, timeout=600,
134 delay=10):
135 """Waits until all target artifacts are available in Google Storage or
136 until the request times out.
137
138 This method polls Google Storage until all target artifacts are
139 available or until the timeout occurs. Because we may not know the
140 exact name of the target artifacts, the method accepts to_wait_list, a
141 list of filename patterns, to identify whether an artifact whose name
142 matches the pattern exists (e.g. use pattern '_full_' to search for
143 the full payload 'chromeos_R17-1413.0.0-a1_x86-mario_full_dev.bin').
144
145 Args:
146 to_wait_list: List of regular expression patterns to identify
147 the target artifacts.
148 archive_url: URL of the Google Storage bucket.
149 err_str: String to display in the error message.
150
151 Returns:
152 The list of artifacts in the Google Storage bucket.
153
154 Raises:
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700155 CommonUtilError: If timeout occurs.
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700156 """
157
158 cmd = 'gsutil cat %s/%s' % (archive_url, UPLOADED_LIST)
159 msg = 'Failed to get a list of uploaded files.'
160
161 deadline = time.time() + timeout
162 while time.time() < deadline:
Yu-Ju Hong92784052012-08-22 13:08:54 -0700163 uploaded_list = []
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700164 to_delay = delay + random.uniform(.5 * delay, 1.5 * delay)
Yu-Ju Hong92784052012-08-22 13:08:54 -0700165 try:
166 # Run "gsutil cat" to retrieve the list.
167 uploaded_list = gsutil_util.GSUtilRun(cmd, msg).splitlines()
168 except gsutil_util.GSUtilError:
169 # For backward compatibility, fallling back to use "gsutil ls"
170 # when the manifest file is not present.
171 cmd = 'gsutil ls %s/*' % archive_url
172 msg = 'Failed to list payloads.'
173 payload_list = gsutil_util.GSUtilRun(cmd, msg).splitlines()
174 for payload in payload_list:
175 uploaded_list.append(payload.rsplit('/', 1)[1])
176
177 # Check if all target artifacts are available.
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700178 if IsAvailable(to_wait_list, uploaded_list):
179 return uploaded_list
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700180 _Log('Retrying in %f seconds...%s' % (to_delay, err_str))
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700181 time.sleep(to_delay)
182
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700183 raise CommonUtilError('Missing %s for %s.' % (err_str, archive_url))
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700184
185
Gilad Arnold6f99b982012-09-12 10:49:40 -0700186def GatherArtifactDownloads(main_staging_dir, archive_url, build_dir, build,
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700187 timeout=600, delay=10):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700188 """Generates artifacts that we mean to download and install for autotest.
Frank Farzan37761d12011-12-01 14:29:08 -0800189
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700190 This method generates the list of artifacts we will need for autotest. These
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700191 artifacts are instances of build_artifact.BuildArtifact.
Chris Masone816e38c2012-05-02 12:22:36 -0700192
193 Note, these artifacts can be downloaded asynchronously iff
194 !artifact.Synchronous().
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700195 """
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700196
197 # Wait up to 10 minutes for the full payload to be uploaded because we
198 # do not know the exact name of the full payload.
199
200 # We also wait for 'autotest.tar' because we do not know what type of
201 # autotest tarballs (tar or tar.bz2) is available
202 # (crosbug.com/32312). This dependency can be removed once all
203 # branches move to the new 'tar' format.
Vadim Bendeburyd3698392012-12-27 18:21:32 -0800204 to_wait_list = ['_full_', build_artifact.AUTOTEST_PACKAGE]
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700205 err_str = 'full payload or autotest tarball'
206 uploaded_list = WaitUntilAvailable(to_wait_list, archive_url, err_str,
Chris Sosa6a3697f2013-01-29 16:44:43 -0800207 timeout=timeout, delay=delay)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700208
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700209 # First we gather the urls/paths for the update payloads.
Vadim Bendeburyd3698392012-12-27 18:21:32 -0800210 full_url, nton_url, mton_url, fw_url = ParsePayloadList(
211 archive_url, uploaded_list)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700212
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700213 full_payload = os.path.join(build_dir, build_artifact.ROOT_UPDATE)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700214
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700215 artifacts = []
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700216 artifacts.append(build_artifact.BuildArtifact(
217 full_url, main_staging_dir, full_payload, synchronous=True))
Chris Sosa1228a1a2012-05-22 17:12:13 -0700218
219 if nton_url:
220 nton_payload = os.path.join(build_dir, AU_BASE, build + NTON_DIR_SUFFIX,
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700221 build_artifact.ROOT_UPDATE)
222 artifacts.append(build_artifact.AUTestPayloadBuildArtifact(
223 nton_url, main_staging_dir, nton_payload))
Chris Sosa1228a1a2012-05-22 17:12:13 -0700224
Chris Sosa781ba6d2012-04-11 12:44:43 -0700225 if mton_url:
226 mton_payload = os.path.join(build_dir, AU_BASE, build + MTON_DIR_SUFFIX,
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700227 build_artifact.ROOT_UPDATE)
228 artifacts.append(build_artifact.AUTestPayloadBuildArtifact(
Chris Sosa781ba6d2012-04-11 12:44:43 -0700229 mton_url, main_staging_dir, mton_payload))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700230
Vadim Bendeburyd3698392012-12-27 18:21:32 -0800231 if fw_url:
Chris Sosa6a3697f2013-01-29 16:44:43 -0800232 artifacts.append(build_artifact.BuildArtifact(
233 fw_url, main_staging_dir, build_dir))
Yu-Ju Honge61cbe92012-07-10 14:10:26 -0700234
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700235 # Gather information about autotest tarballs. Use autotest.tar if available.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700236 if build_artifact.AUTOTEST_PACKAGE in uploaded_list:
237 autotest_url = '%s/%s' % (archive_url, build_artifact.AUTOTEST_PACKAGE)
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700238 else:
239 # Use autotest.tar.bz for backward compatibility. This can be
240 # removed once all branches start using "autotest.tar"
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700241 autotest_url = '%s/%s' % (
242 archive_url, build_artifact.AUTOTEST_ZIPPED_PACKAGE)
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700243
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700244 # Next we gather the miscellaneous payloads.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700245 stateful_url = archive_url + '/' + build_artifact.STATEFUL_UPDATE
246 test_suites_url = (archive_url + '/' + build_artifact.TEST_SUITES_PACKAGE)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700247
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700248 stateful_payload = os.path.join(build_dir, build_artifact.STATEFUL_UPDATE)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700249
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700250 artifacts.append(build_artifact.BuildArtifact(
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700251 stateful_url, main_staging_dir, stateful_payload, synchronous=True))
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700252 artifacts.append(build_artifact.AutotestTarballBuildArtifact(
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700253 autotest_url, main_staging_dir, build_dir))
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700254 artifacts.append(build_artifact.TarballBuildArtifact(
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700255 test_suites_url, main_staging_dir, build_dir, synchronous=True))
256 return artifacts
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700257
258
Chris Masone816e38c2012-05-02 12:22:36 -0700259def GatherSymbolArtifactDownloads(temp_download_dir, archive_url, staging_dir,
260 timeout=600, delay=10):
261 """Generates debug symbol artifacts that we mean to download and stage.
262
263 This method generates the list of artifacts we will need to
264 symbolicate crash dumps that occur during autotest runs. These
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700265 artifacts are instances of build_artifact.BuildArtifact.
Chris Masone816e38c2012-05-02 12:22:36 -0700266
267 This will poll google storage until the debug symbol artifact becomes
268 available, or until the 10 minute timeout is up.
269
270 @param temp_download_dir: the tempdir into which we're downloading artifacts
271 prior to staging them.
272 @param archive_url: the google storage url of the bucket where the debug
273 symbols for the desired build are stored.
274 @param staging_dir: the dir into which to stage the symbols
275
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700276 @return an iterable of one DebugTarballBuildArtifact pointing to the right
277 debug symbols. This is an iterable so that it's similar to
278 GatherArtifactDownloads. Also, it's possible that someday we might
279 have more than one.
Chris Masone816e38c2012-05-02 12:22:36 -0700280 """
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700281
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700282 artifact_name = build_artifact.DEBUG_SYMBOLS
Gilad Arnold6f99b982012-09-12 10:49:40 -0700283 WaitUntilAvailable([artifact_name], archive_url, 'debug symbols',
284 timeout=timeout, delay=delay)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700285 artifact = build_artifact.DebugTarballBuildArtifact(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700286 archive_url + '/' + artifact_name,
287 temp_download_dir,
288 staging_dir)
289 return [artifact]
Yu-Ju Hong825ddc32012-08-13 18:47:10 -0700290
Gilad Arnold6f99b982012-09-12 10:49:40 -0700291
292def GatherImageArchiveArtifactDownloads(temp_download_dir, archive_url,
293 staging_dir, image_file_list,
294 timeout=600, delay=10):
295 """Generates image archive artifact(s) for downloading / staging.
296
297 Generates the list of artifacts that are used for extracting Chrome OS images
298 from. Currently, it returns a single artifact, which is a zipfile configured
299 to extract a given list of images. It first polls Google Storage unti lthe
300 desired artifacts become available (or a timeout expires).
301
302 Args:
303 temp_download_dir: temporary directory, used for downloading artifacts
304 archive_url: URI to the bucket where the artifacts are stored
305 staging_dir: directory into which to stage the extracted files
306 image_file_list: list of image files to be extracted
307 Returns:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700308 list of downloadable artifacts (of type ZipfileBuildArtifact), currently
309 containing a single obejct
Gilad Arnold6f99b982012-09-12 10:49:40 -0700310 """
311
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700312 artifact_name = build_artifact.IMAGE_ARCHIVE
Gilad Arnold6f99b982012-09-12 10:49:40 -0700313 WaitUntilAvailable([artifact_name], archive_url, 'image archive',
314 timeout=timeout, delay=delay)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700315 artifact = build_artifact.ZipfileBuildArtifact(
Gilad Arnold6f99b982012-09-12 10:49:40 -0700316 archive_url + '/' + artifact_name,
317 temp_download_dir, staging_dir,
318 unzip_file_list=image_file_list)
319 return [artifact]
Chris Masone816e38c2012-05-02 12:22:36 -0700320
321
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700322def PrepareBuildDirectory(build_dir):
323 """Preliminary staging of installation directory for build.
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700324
325 Args:
Frank Farzan37761d12011-12-01 14:29:08 -0800326 build_dir: Directory to install build components into.
327 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700328 if not os.path.isdir(build_dir):
329 os.path.makedirs(build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800330
331 # Create blank chromiumos_test_image.bin. Otherwise the Dev Server will
332 # try to rebuild it unnecessarily.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700333 test_image = os.path.join(build_dir, build_artifact.TEST_IMAGE)
Frank Farzan37761d12011-12-01 14:29:08 -0800334 open(test_image, 'a').close()
335
Frank Farzan37761d12011-12-01 14:29:08 -0800336
337def SafeSandboxAccess(static_dir, path):
338 """Verify that the path is in static_dir.
339
340 Args:
341 static_dir: Directory where builds are served from.
342 path: Path to verify.
343
344 Returns:
345 True if path is in static_dir, False otherwise
346 """
347 static_dir = os.path.realpath(static_dir)
348 path = os.path.realpath(path)
349 return (path.startswith(static_dir) and path != static_dir)
350
351
Gilad Arnold5174ca22012-09-12 10:49:09 -0700352def AcquireLock(static_dir, tag, create_once=True):
Frank Farzan37761d12011-12-01 14:29:08 -0800353 """Acquires a lock for a given tag.
354
Gilad Arnold5174ca22012-09-12 10:49:09 -0700355 Creates a directory for the specified tag, and atomically creates a lock file
356 in it. This tells other components the resource/task represented by the tag
357 is unavailable.
Frank Farzan37761d12011-12-01 14:29:08 -0800358
359 Args:
Gilad Arnold5174ca22012-09-12 10:49:09 -0700360 static_dir: Directory where builds are served from.
361 tag: Unique resource/task identifier. Use '/' for nested tags.
362 create_once: Determines whether the directory must be freshly created; this
363 preserves previous semantics of the lock acquisition.
Frank Farzan37761d12011-12-01 14:29:08 -0800364
365 Returns:
366 Path to the created directory or None if creation failed.
367
368 Raises:
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700369 CommonUtilError: If lock can't be acquired.
Frank Farzan37761d12011-12-01 14:29:08 -0800370 """
371 build_dir = os.path.join(static_dir, tag)
372 if not SafeSandboxAccess(static_dir, build_dir):
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700373 raise CommonUtilError('Invalid tag "%s".' % tag)
Frank Farzan37761d12011-12-01 14:29:08 -0800374
Gilad Arnold5174ca22012-09-12 10:49:09 -0700375 # Create the directory.
376 is_created = False
Frank Farzan37761d12011-12-01 14:29:08 -0800377 try:
378 os.makedirs(build_dir)
Gilad Arnold5174ca22012-09-12 10:49:09 -0700379 is_created = True
Frank Farzan37761d12011-12-01 14:29:08 -0800380 except OSError, e:
381 if e.errno == errno.EEXIST:
Gilad Arnold5174ca22012-09-12 10:49:09 -0700382 if create_once:
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700383 raise CommonUtilError(str(e))
Frank Farzan37761d12011-12-01 14:29:08 -0800384 else:
385 raise
386
Gilad Arnold5174ca22012-09-12 10:49:09 -0700387 # Lock the directory.
388 try:
389 lock = lockfile.FileLock(os.path.join(build_dir, DEVSERVER_LOCK_FILE))
390 lock.acquire(timeout=0)
391 except lockfile.AlreadyLocked, e:
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700392 raise CommonUtilError(str(e))
Gilad Arnold5174ca22012-09-12 10:49:09 -0700393 except:
394 # In any other case, remove the directory if we actually created it, so
395 # that subsequent attempts won't fail to re-create it.
396 if is_created:
397 shutil.rmtree(build_dir)
398 raise
399
Frank Farzan37761d12011-12-01 14:29:08 -0800400 return build_dir
401
402
Gilad Arnold5174ca22012-09-12 10:49:09 -0700403def ReleaseLock(static_dir, tag, destroy=False):
404 """Releases the lock for a given tag.
405
406 Optionally, removes the locked directory entirely.
Frank Farzan37761d12011-12-01 14:29:08 -0800407
408 Args:
409 static_dir: Directory where builds are served from.
Gilad Arnold5174ca22012-09-12 10:49:09 -0700410 tag: Unique resource/task identifier. Use '/' for nested tags.
411 destroy: Determines whether the locked directory should be removed
412 entirely.
Frank Farzan37761d12011-12-01 14:29:08 -0800413
414 Raises:
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700415 CommonUtilError: If lock can't be released.
Frank Farzan37761d12011-12-01 14:29:08 -0800416 """
417 build_dir = os.path.join(static_dir, tag)
418 if not SafeSandboxAccess(static_dir, build_dir):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700419 raise CommonUtilError('Invalid tag "%s".' % tag)
Frank Farzan37761d12011-12-01 14:29:08 -0800420
Gilad Arnold5174ca22012-09-12 10:49:09 -0700421 lock = lockfile.FileLock(os.path.join(build_dir, DEVSERVER_LOCK_FILE))
Gilad Arnoldea162aa2012-09-24 16:57:59 -0700422 try:
423 lock.break_lock()
424 if destroy:
425 shutil.rmtree(build_dir)
426 except Exception, e:
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700427 raise CommonUtilError(str(e))
Frank Farzan37761d12011-12-01 14:29:08 -0800428
429
Scott Zawalski16954532012-03-20 15:31:36 -0400430def GetLatestBuildVersion(static_dir, target, milestone=None):
Frank Farzan37761d12011-12-01 14:29:08 -0800431 """Retrieves the latest build version for a given board.
432
433 Args:
434 static_dir: Directory where builds are served from.
Scott Zawalski16954532012-03-20 15:31:36 -0400435 target: The build target, typically a combination of the board and the
436 type of build e.g. x86-mario-release.
437 milestone: For latest build set to None, for builds only in a specific
438 milestone set to a str of format Rxx (e.g. R16). Default: None.
Frank Farzan37761d12011-12-01 14:29:08 -0800439
440 Returns:
Scott Zawalski16954532012-03-20 15:31:36 -0400441 If latest found, a full build string is returned e.g. R17-1234.0.0-a1-b983.
442 If no latest is found for some reason or another a '' string is returned.
Frank Farzan37761d12011-12-01 14:29:08 -0800443
444 Raises:
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700445 CommonUtilError: If for some reason the latest build cannot be
Scott Zawalski16954532012-03-20 15:31:36 -0400446 deteremined, this could be due to the dir not existing or no builds
447 being present after filtering on milestone.
Frank Farzan37761d12011-12-01 14:29:08 -0800448 """
Scott Zawalski16954532012-03-20 15:31:36 -0400449 target_path = os.path.join(static_dir, target)
450 if not os.path.isdir(target_path):
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700451 raise CommonUtilError('Cannot find path %s' % target_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800452
Scott Zawalski16954532012-03-20 15:31:36 -0400453 builds = [distutils.version.LooseVersion(build) for build in
454 os.listdir(target_path)]
Frank Farzan37761d12011-12-01 14:29:08 -0800455
Scott Zawalski16954532012-03-20 15:31:36 -0400456 if milestone and builds:
457 # Check if milestone Rxx is in the string representation of the build.
458 builds = filter(lambda x: milestone.upper() in str(x), builds)
Frank Farzan37761d12011-12-01 14:29:08 -0800459
Scott Zawalski16954532012-03-20 15:31:36 -0400460 if not builds:
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700461 raise CommonUtilError('Could not determine build for %s' % target)
Frank Farzan37761d12011-12-01 14:29:08 -0800462
Scott Zawalski16954532012-03-20 15:31:36 -0400463 return str(max(builds))
Frank Farzan37761d12011-12-01 14:29:08 -0800464
465
Scott Zawalski84a39c92012-01-13 15:12:42 -0500466def GetControlFile(static_dir, build, control_path):
Frank Farzan37761d12011-12-01 14:29:08 -0800467 """Attempts to pull the requested control file from the Dev Server.
468
469 Args:
470 static_dir: Directory where builds are served from.
Frank Farzan37761d12011-12-01 14:29:08 -0800471 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
472 control_path: Path to control file on Dev Server relative to Autotest root.
473
474 Raises:
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700475 CommonUtilError: If lock can't be acquired.
Frank Farzan37761d12011-12-01 14:29:08 -0800476
477 Returns:
478 Content of the requested control file.
479 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500480 # Be forgiving if the user passes in the control_path with a leading /
481 control_path = control_path.lstrip('/')
Scott Zawalski84a39c92012-01-13 15:12:42 -0500482 control_path = os.path.join(static_dir, build, 'autotest',
Scott Zawalski4647ce62012-01-03 17:17:28 -0500483 control_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800484 if not SafeSandboxAccess(static_dir, control_path):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700485 raise CommonUtilError('Invalid control file "%s".' % control_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800486
Scott Zawalski84a39c92012-01-13 15:12:42 -0500487 if not os.path.exists(control_path):
488 # TODO(scottz): Come up with some sort of error mechanism.
489 # crosbug.com/25040
490 return 'Unknown control path %s' % control_path
491
Frank Farzan37761d12011-12-01 14:29:08 -0800492 with open(control_path, 'r') as control_file:
493 return control_file.read()
494
495
Scott Zawalski84a39c92012-01-13 15:12:42 -0500496def GetControlFileList(static_dir, build):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500497 """List all control|control. files in the specified board/build path.
498
499 Args:
500 static_dir: Directory where builds are served from.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500501 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
502
503 Raises:
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700504 CommonUtilError: If path is outside of sandbox.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500505
506 Returns:
507 String of each file separated by a newline.
508 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500509 autotest_dir = os.path.join(static_dir, build, 'autotest/')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500510 if not SafeSandboxAccess(static_dir, autotest_dir):
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700511 raise CommonUtilError('Autotest dir not in sandbox "%s".' % autotest_dir)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500512
513 control_files = set()
Scott Zawalski84a39c92012-01-13 15:12:42 -0500514 if not os.path.exists(autotest_dir):
515 # TODO(scottz): Come up with some sort of error mechanism.
516 # crosbug.com/25040
517 return 'Unknown build path %s' % autotest_dir
518
Scott Zawalski4647ce62012-01-03 17:17:28 -0500519 for entry in os.walk(autotest_dir):
520 dir_path, _, files = entry
521 for file_entry in files:
522 if file_entry.startswith('control.') or file_entry == 'control':
523 control_files.add(os.path.join(dir_path,
Chris Sosaea148d92012-03-06 16:22:04 -0800524 file_entry).replace(autotest_dir, ''))
Scott Zawalski4647ce62012-01-03 17:17:28 -0500525
526 return '\n'.join(control_files)
527
528
Gilad Arnold55a2a372012-10-02 09:46:32 -0700529def GetFileSize(file_path):
530 """Returns the size in bytes of the file given."""
531 return os.path.getsize(file_path)
532
533
Chris Sosa6a3697f2013-01-29 16:44:43 -0800534# Hashlib is strange and doesn't actually define these in a sane way that
535# pylint can find them. Disable checks for them.
536# pylint: disable=E1101,W0106
Gilad Arnold55a2a372012-10-02 09:46:32 -0700537def GetFileHashes(file_path, do_sha1=False, do_sha256=False, do_md5=False):
538 """Computes and returns a list of requested hashes.
539
540 Args:
541 file_path: path to file to be hashed
542 do_sha1: whether or not to compute a SHA1 hash
543 do_sha256: whether or not to compute a SHA256 hash
544 do_md5: whether or not to compute a MD5 hash
545 Returns:
546 A dictionary containing binary hash values, keyed by 'sha1', 'sha256' and
547 'md5', respectively.
548 """
549 hashes = {}
550 if (do_sha1 or do_sha256 or do_md5):
551 # Initialize hashers.
552 hasher_sha1 = hashlib.sha1() if do_sha1 else None
553 hasher_sha256 = hashlib.sha256() if do_sha256 else None
554 hasher_md5 = hashlib.md5() if do_md5 else None
555
556 # Read blocks from file, update hashes.
557 with open(file_path, 'rb') as fd:
558 while True:
559 block = fd.read(_HASH_BLOCK_SIZE)
560 if not block:
561 break
562 hasher_sha1 and hasher_sha1.update(block)
563 hasher_sha256 and hasher_sha256.update(block)
564 hasher_md5 and hasher_md5.update(block)
565
566 # Update return values.
567 if hasher_sha1:
568 hashes['sha1'] = hasher_sha1.digest()
569 if hasher_sha256:
570 hashes['sha256'] = hasher_sha256.digest()
571 if hasher_md5:
572 hashes['md5'] = hasher_md5.digest()
573
574 return hashes
575
576
577def GetFileSha1(file_path):
578 """Returns the SHA1 checksum of the file given (base64 encoded)."""
579 return base64.b64encode(GetFileHashes(file_path, do_sha1=True)['sha1'])
580
581
582def GetFileSha256(file_path):
583 """Returns the SHA256 checksum of the file given (base64 encoded)."""
584 return base64.b64encode(GetFileHashes(file_path, do_sha256=True)['sha256'])
585
586
587def GetFileMd5(file_path):
588 """Returns the MD5 checksum of the file given (hex encoded)."""
589 return binascii.hexlify(GetFileHashes(file_path, do_md5=True)['md5'])
590
591
592def CopyFile(source, dest):
593 """Copies a file from |source| to |dest|."""
594 _Log('Copy File %s -> %s' % (source, dest))
595 shutil.copy(source, dest)