blob: e40ae7e91fe95241a7586a6944b6269a198bceb0 [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
10import os
11import shutil
Frank Farzan37761d12011-12-01 14:29:08 -080012
Chris Sosa47a7d4e2012-03-28 11:26:55 -070013import downloadable_artifact
14import gsutil_util
Frank Farzan37761d12011-12-01 14:29:08 -080015
16AU_BASE = 'au'
17NTON_DIR_SUFFIX = '_nton'
18MTON_DIR_SUFFIX = '_mton'
Frank Farzan37761d12011-12-01 14:29:08 -080019DEV_BUILD_PREFIX = 'dev'
20
21
22class DevServerUtilError(Exception):
23 """Exception classes used by this module."""
24 pass
25
26
27def ParsePayloadList(payload_list):
28 """Parse and return the full/delta payload URLs.
29
30 Args:
31 payload_list: A list of Google Storage URLs.
32
33 Returns:
34 Tuple of 3 payloads URLs: (full, nton, mton).
35
36 Raises:
37 DevServerUtilError: If payloads missing or invalid.
38 """
39 full_payload_url = None
40 mton_payload_url = None
41 nton_payload_url = None
42 for payload in payload_list:
43 if '_full_' in payload:
44 full_payload_url = payload
45 elif '_delta_' in payload:
46 # e.g. chromeos_{from_version}_{to_version}_x86-generic_delta_dev.bin
47 from_version, to_version = payload.rsplit('/', 1)[1].split('_')[1:3]
48 if from_version == to_version:
49 nton_payload_url = payload
50 else:
51 mton_payload_url = payload
52
Chris Sosa781ba6d2012-04-11 12:44:43 -070053 if not full_payload_url or not nton_payload_url:
Frank Farzan37761d12011-12-01 14:29:08 -080054 raise DevServerUtilError(
55 'Payloads are missing or have unexpected name formats.', payload_list)
56
57 return full_payload_url, nton_payload_url, mton_payload_url
58
59
Chris Masonec8209f42012-04-24 13:30:34 -070060def GatherArtifactDownloads(main_staging_dir, archive_url, build, build_dir,
61 static_dir):
Chris Sosa47a7d4e2012-03-28 11:26:55 -070062 """Generates artifacts that we mean to download and install for autotest.
Frank Farzan37761d12011-12-01 14:29:08 -080063
Chris Sosa47a7d4e2012-03-28 11:26:55 -070064 This method generates the list of artifacts we will need for autotest. These
Chris Masonec8209f42012-04-24 13:30:34 -070065 artifacts are instances of downloadable_artifact.DownloadableArtifact.
66
67 Note, these artifacts can be downloaded asynchronously iff
68 !artifact.Synchronous().
Scott Zawalski51ccf9e2012-03-28 08:16:01 -070069 """
Scott Zawalski51ccf9e2012-03-28 08:16:01 -070070 cmd = 'gsutil ls %s/*.bin' % archive_url
71 msg = 'Failed to get a list of payloads.'
Chris Sosa47a7d4e2012-03-28 11:26:55 -070072 payload_list = gsutil_util.GSUtilRun(cmd, msg).splitlines()
Scott Zawalski51ccf9e2012-03-28 08:16:01 -070073
Chris Sosa47a7d4e2012-03-28 11:26:55 -070074 # First we gather the urls/paths for the update payloads.
75 full_url, nton_url, mton_url = ParsePayloadList(payload_list)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -070076
Chris Sosa47a7d4e2012-03-28 11:26:55 -070077 full_payload = os.path.join(build_dir, downloadable_artifact.ROOT_UPDATE)
78 nton_payload = os.path.join(build_dir, AU_BASE, build + NTON_DIR_SUFFIX,
79 downloadable_artifact.ROOT_UPDATE)
Scott Zawalski51ccf9e2012-03-28 08:16:01 -070080
Chris Sosa47a7d4e2012-03-28 11:26:55 -070081 artifacts = []
82 artifacts.append(downloadable_artifact.DownloadableArtifact(full_url,
83 main_staging_dir, full_payload, synchronous=True))
84 artifacts.append(downloadable_artifact.AUTestPayload(nton_url,
85 main_staging_dir, nton_payload))
Chris Sosa781ba6d2012-04-11 12:44:43 -070086 if mton_url:
87 mton_payload = os.path.join(build_dir, AU_BASE, build + MTON_DIR_SUFFIX,
88 downloadable_artifact.ROOT_UPDATE)
89 artifacts.append(downloadable_artifact.AUTestPayload(
90 mton_url, main_staging_dir, mton_payload))
Chris Sosa47a7d4e2012-03-28 11:26:55 -070091
92 # Next we gather the miscellaneous payloads.
Chris Masonec8209f42012-04-24 13:30:34 -070093 debug_url = archive_url + '/' + downloadable_artifact.DEBUG_SYMBOLS
Chris Sosa47a7d4e2012-03-28 11:26:55 -070094 stateful_url = archive_url + '/' + downloadable_artifact.STATEFUL_UPDATE
95 autotest_url = archive_url + '/' + downloadable_artifact.AUTOTEST_PACKAGE
96 test_suites_url = (archive_url + '/' +
97 downloadable_artifact.TEST_SUITES_PACKAGE)
98
99 stateful_payload = os.path.join(build_dir,
100 downloadable_artifact.STATEFUL_UPDATE)
101
Chris Masonec8209f42012-04-24 13:30:34 -0700102 artifacts.append(downloadable_artifact.DebugTarball(
103 debug_url, main_staging_dir, static_dir))
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700104 artifacts.append(downloadable_artifact.DownloadableArtifact(
105 stateful_url, main_staging_dir, stateful_payload, synchronous=True))
106 artifacts.append(downloadable_artifact.AutotestTarball(
107 autotest_url, main_staging_dir, build_dir))
108 artifacts.append(downloadable_artifact.Tarball(
109 test_suites_url, main_staging_dir, build_dir, synchronous=True))
110 return artifacts
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700111
112
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700113def PrepareBuildDirectory(build_dir):
114 """Preliminary staging of installation directory for build.
Scott Zawalski51ccf9e2012-03-28 08:16:01 -0700115
116 Args:
Frank Farzan37761d12011-12-01 14:29:08 -0800117 build_dir: Directory to install build components into.
118 """
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700119 if not os.path.isdir(build_dir):
120 os.path.makedirs(build_dir)
Frank Farzan37761d12011-12-01 14:29:08 -0800121
122 # Create blank chromiumos_test_image.bin. Otherwise the Dev Server will
123 # try to rebuild it unnecessarily.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700124 test_image = os.path.join(build_dir, downloadable_artifact.TEST_IMAGE)
Frank Farzan37761d12011-12-01 14:29:08 -0800125 open(test_image, 'a').close()
126
Frank Farzan37761d12011-12-01 14:29:08 -0800127
128def SafeSandboxAccess(static_dir, path):
129 """Verify that the path is in static_dir.
130
131 Args:
132 static_dir: Directory where builds are served from.
133 path: Path to verify.
134
135 Returns:
136 True if path is in static_dir, False otherwise
137 """
138 static_dir = os.path.realpath(static_dir)
139 path = os.path.realpath(path)
140 return (path.startswith(static_dir) and path != static_dir)
141
142
143def AcquireLock(static_dir, tag):
144 """Acquires a lock for a given tag.
145
146 Creates a directory for the specified tag, telling other
147 components the resource/task represented by the tag is unavailable.
148
149 Args:
150 static_dir: Directory where builds are served from.
151 tag: Unique resource/task identifier. Use '/' for nested tags.
152
153 Returns:
154 Path to the created directory or None if creation failed.
155
156 Raises:
157 DevServerUtilError: If lock can't be acquired.
158 """
159 build_dir = os.path.join(static_dir, tag)
160 if not SafeSandboxAccess(static_dir, build_dir):
Chris Sosa9164ca32012-03-28 11:04:50 -0700161 raise DevServerUtilError('Invalid tag "%s".' % tag)
Frank Farzan37761d12011-12-01 14:29:08 -0800162
163 try:
164 os.makedirs(build_dir)
165 except OSError, e:
166 if e.errno == errno.EEXIST:
167 raise DevServerUtilError(str(e))
168 else:
169 raise
170
171 return build_dir
172
173
174def ReleaseLock(static_dir, tag):
175 """Releases the lock for a given tag. Removes lock directory content.
176
177 Args:
178 static_dir: Directory where builds are served from.
179 tag: Unique resource/task identifier. Use '/' for nested tags.
180
181 Raises:
182 DevServerUtilError: If lock can't be released.
183 """
184 build_dir = os.path.join(static_dir, tag)
185 if not SafeSandboxAccess(static_dir, build_dir):
186 raise DevServerUtilError('Invaid tag "%s".' % tag)
187
188 shutil.rmtree(build_dir)
189
190
191def FindMatchingBoards(static_dir, board):
192 """Returns a list of boards given a partial board name.
193
194 Args:
195 static_dir: Directory where builds are served from.
196 board: Partial board name for this build; e.g. x86-generic.
197
198 Returns:
199 Returns a list of boards given a partial board.
200 """
201 return [brd for brd in os.listdir(static_dir) if board in brd]
202
203
204def FindMatchingBuilds(static_dir, board, build):
205 """Returns a list of matching builds given a board and partial build.
206
207 Args:
208 static_dir: Directory where builds are served from.
209 board: Partial board name for this build; e.g. x86-generic-release.
210 build: Partial build string to look for; e.g. R17-1234.
211
212 Returns:
213 Returns a list of (board, build) tuples given a partial board and build.
214 """
215 matches = []
216 for brd in FindMatchingBoards(static_dir, board):
217 a = [(brd, bld) for bld in
218 os.listdir(os.path.join(static_dir, brd)) if build in bld]
219 matches.extend(a)
220 return matches
221
222
Scott Zawalski16954532012-03-20 15:31:36 -0400223def GetLatestBuildVersion(static_dir, target, milestone=None):
Frank Farzan37761d12011-12-01 14:29:08 -0800224 """Retrieves the latest build version for a given board.
225
226 Args:
227 static_dir: Directory where builds are served from.
Scott Zawalski16954532012-03-20 15:31:36 -0400228 target: The build target, typically a combination of the board and the
229 type of build e.g. x86-mario-release.
230 milestone: For latest build set to None, for builds only in a specific
231 milestone set to a str of format Rxx (e.g. R16). Default: None.
Frank Farzan37761d12011-12-01 14:29:08 -0800232
233 Returns:
Scott Zawalski16954532012-03-20 15:31:36 -0400234 If latest found, a full build string is returned e.g. R17-1234.0.0-a1-b983.
235 If no latest is found for some reason or another a '' string is returned.
Frank Farzan37761d12011-12-01 14:29:08 -0800236
237 Raises:
Scott Zawalski16954532012-03-20 15:31:36 -0400238 DevServerUtilError: If for some reason the latest build cannot be
239 deteremined, this could be due to the dir not existing or no builds
240 being present after filtering on milestone.
Frank Farzan37761d12011-12-01 14:29:08 -0800241 """
Scott Zawalski16954532012-03-20 15:31:36 -0400242 target_path = os.path.join(static_dir, target)
243 if not os.path.isdir(target_path):
244 raise DevServerUtilError('Cannot find path %s' % target_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800245
Scott Zawalski16954532012-03-20 15:31:36 -0400246 builds = [distutils.version.LooseVersion(build) for build in
247 os.listdir(target_path)]
Frank Farzan37761d12011-12-01 14:29:08 -0800248
Scott Zawalski16954532012-03-20 15:31:36 -0400249 if milestone and builds:
250 # Check if milestone Rxx is in the string representation of the build.
251 builds = filter(lambda x: milestone.upper() in str(x), builds)
Frank Farzan37761d12011-12-01 14:29:08 -0800252
Scott Zawalski16954532012-03-20 15:31:36 -0400253 if not builds:
254 raise DevServerUtilError('Could not determine build for %s' % target)
Frank Farzan37761d12011-12-01 14:29:08 -0800255
Scott Zawalski16954532012-03-20 15:31:36 -0400256 return str(max(builds))
Frank Farzan37761d12011-12-01 14:29:08 -0800257
258
259def CloneBuild(static_dir, board, build, tag, force=False):
260 """Clone an official build into the developer sandbox.
261
262 Developer sandbox directory must already exist.
263
264 Args:
265 static_dir: Directory where builds are served from.
266 board: Fully qualified board name; e.g. x86-generic-release.
267 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
268 tag: Unique resource/task identifier. Use '/' for nested tags.
269 force: Force re-creation of build_dir even if it already exists.
270
271 Returns:
272 The path to the new build.
273 """
274 # Create the developer build directory.
275 dev_static_dir = os.path.join(static_dir, DEV_BUILD_PREFIX)
276 dev_build_dir = os.path.join(dev_static_dir, tag)
277 official_build_dir = os.path.join(static_dir, board, build)
278 cherrypy.log('Cloning %s -> %s' % (official_build_dir, dev_build_dir),
279 'DEVSERVER_UTIL')
280 dev_build_exists = False
281 try:
282 AcquireLock(dev_static_dir, tag)
283 except DevServerUtilError:
284 dev_build_exists = True
285 if force:
286 dev_build_exists = False
287 ReleaseLock(dev_static_dir, tag)
288 AcquireLock(dev_static_dir, tag)
289
290 # Make a copy of the official build, only take necessary files.
291 if not dev_build_exists:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700292 copy_list = [downloadable_artifact.TEST_IMAGE,
293 downloadable_artifact.ROOT_UPDATE,
294 downloadable_artifact.STATEFUL_UPDATE]
Frank Farzan37761d12011-12-01 14:29:08 -0800295 for f in copy_list:
296 shutil.copy(os.path.join(official_build_dir, f), dev_build_dir)
297
298 return dev_build_dir
299
Scott Zawalski84a39c92012-01-13 15:12:42 -0500300
301def GetControlFile(static_dir, build, control_path):
Frank Farzan37761d12011-12-01 14:29:08 -0800302 """Attempts to pull the requested control file from the Dev Server.
303
304 Args:
305 static_dir: Directory where builds are served from.
Frank Farzan37761d12011-12-01 14:29:08 -0800306 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
307 control_path: Path to control file on Dev Server relative to Autotest root.
308
309 Raises:
310 DevServerUtilError: If lock can't be acquired.
311
312 Returns:
313 Content of the requested control file.
314 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500315 # Be forgiving if the user passes in the control_path with a leading /
316 control_path = control_path.lstrip('/')
Scott Zawalski84a39c92012-01-13 15:12:42 -0500317 control_path = os.path.join(static_dir, build, 'autotest',
Scott Zawalski4647ce62012-01-03 17:17:28 -0500318 control_path)
Frank Farzan37761d12011-12-01 14:29:08 -0800319 if not SafeSandboxAccess(static_dir, control_path):
320 raise DevServerUtilError('Invaid control file "%s".' % control_path)
321
Scott Zawalski84a39c92012-01-13 15:12:42 -0500322 if not os.path.exists(control_path):
323 # TODO(scottz): Come up with some sort of error mechanism.
324 # crosbug.com/25040
325 return 'Unknown control path %s' % control_path
326
Frank Farzan37761d12011-12-01 14:29:08 -0800327 with open(control_path, 'r') as control_file:
328 return control_file.read()
329
330
Scott Zawalski84a39c92012-01-13 15:12:42 -0500331def GetControlFileList(static_dir, build):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500332 """List all control|control. files in the specified board/build path.
333
334 Args:
335 static_dir: Directory where builds are served from.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500336 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
337
338 Raises:
339 DevServerUtilError: If path is outside of sandbox.
340
341 Returns:
342 String of each file separated by a newline.
343 """
Scott Zawalski1572d152012-01-16 14:36:02 -0500344 autotest_dir = os.path.join(static_dir, build, 'autotest/')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500345 if not SafeSandboxAccess(static_dir, autotest_dir):
346 raise DevServerUtilError('Autotest dir not in sandbox "%s".' % autotest_dir)
347
348 control_files = set()
Scott Zawalski84a39c92012-01-13 15:12:42 -0500349 if not os.path.exists(autotest_dir):
350 # TODO(scottz): Come up with some sort of error mechanism.
351 # crosbug.com/25040
352 return 'Unknown build path %s' % autotest_dir
353
Scott Zawalski4647ce62012-01-03 17:17:28 -0500354 for entry in os.walk(autotest_dir):
355 dir_path, _, files = entry
356 for file_entry in files:
357 if file_entry.startswith('control.') or file_entry == 'control':
358 control_files.add(os.path.join(dir_path,
Chris Sosaea148d92012-03-06 16:22:04 -0800359 file_entry).replace(autotest_dir, ''))
Scott Zawalski4647ce62012-01-03 17:17:28 -0500360
361 return '\n'.join(control_files)
362
363
Frank Farzan37761d12011-12-01 14:29:08 -0800364def ListAutoupdateTargets(static_dir, board, build):
365 """Returns a list of autoupdate test targets for the given board, build.
366
367 Args:
368 static_dir: Directory where builds are served from.
369 board: Fully qualified board name; e.g. x86-generic-release.
370 build: Fully qualified build string; e.g. R17-1234.0.0-a1-b983.
371
372 Returns:
373 List of autoupdate test targets; e.g. ['0.14.747.0-r2bf8859c-b2927_nton']
374 """
375 return os.listdir(os.path.join(static_dir, board, build, AU_BASE))