blob: e425b79137a577e961519305401bc166ad24ca40 [file] [log] [blame]
Chris Sosa7c931362010-10-12 02:49:011#!/usr/bin/python
2
Chris Sosa781ba6d2012-04-11 19:44:433# Copyright (c) 2009-2012 The Chromium OS Authors. All rights reserved.
[email protected]ded22402009-10-26 22:36:214# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Chris Sosa3ae4dc12013-03-29 18:47:007"""Chromium OS development server that can be used for all forms of update.
8
9This devserver can be used to perform system-wide autoupdate and update
10of specific portage packages on devices running Chromium OS derived operating
11systems. It mainly operates in two modes:
12
131) archive mode: In this mode, the devserver is configured to stage and
14serve artifacts from Google Storage using the credentials provided to it before
15it is run. The easiest way to understand this is that the devserver is
16functioning as a local cache for artifacts produced and uploaded by build
17servers. Users of this form of devserver can either download the artifacts
18from the devservers static directory OR use the update RPC to perform a
19system-wide autoupdate. Archive mode is always active.
20
212) artifact-generation mode: in this mode, the devserver will attempt to
22generate update payloads and build artifacts when requested. This mode only
23works in the Chromium OS chroot as it uses build tools only present in the
24chroot (emerge, cros_generate_update_payload, etc.). By default, when a device
25requests an update from this form of devserver, the devserver will attempt to
26discover if a more recent build of the board has been built by the developer
27and generate a payload that the requested system can autoupdate to. In addition,
28it accepts gmerge requests from devices that will stage the newest version of
joychen84d13772013-08-06 16:17:2329a particular package from a developer's chroot onto a requesting device.
Chris Sosa3ae4dc12013-03-29 18:47:0030
31For example:
32gmerge gmerge -d <devserver_url>
33
34devserver will see if a newer package of gmerge is available. If gmerge is
35cros_work'd on, it will re-build gmerge. After this, gmerge will install that
36version of gmerge that the devserver just created/found.
37
38For autoupdates, there are many more advanced options that can help specify
39how to update and which payload to give to a requester.
40"""
41
Chris Sosa7c931362010-10-12 02:49:0142
Gilad Arnold55a2a372012-10-02 16:46:3243import json
Sean O'Connor14b6a0a2010-03-21 06:23:4844import optparse
[email protected]ded22402009-10-26 22:36:2145import os
Scott Zawalski4647ce62012-01-03 22:17:2846import re
Simran Basi4baad082013-02-14 21:39:1847import shutil
Mandeep Singh Baines38dcdda2012-12-08 01:55:3348import socket
Chris Masone816e38c2012-05-02 19:22:3649import subprocess
J. Richard Barnette3d977b82013-04-23 18:05:1950import sys
Chris Masone816e38c2012-05-02 19:22:3651import tempfile
Dan Shi59ae7092013-06-04 21:37:2752import threading
Gilad Arnoldd5ebaaa2012-10-02 18:52:3853import types
J. Richard Barnette3d977b82013-04-23 18:05:1954from logging import handlers
55
56import cherrypy
57import cherrypy._cplogging
[email protected]ded22402009-10-26 22:36:2158
Chris Sosa0356d3b2010-09-16 22:46:2259import autoupdate
Gilad Arnoldc65330c2012-09-20 22:17:4860import common_util
Chris Sosa47a7d4e2012-03-28 18:26:5561import downloader
Gilad Arnoldc65330c2012-09-20 22:17:4862import log_util
joychen3cb228e2013-06-12 19:13:1363import xbuddy
Gilad Arnoldc65330c2012-09-20 22:17:4864
Gilad Arnoldc65330c2012-09-20 22:17:4865# Module-local log function.
Chris Sosa6a3697f2013-01-30 00:44:4366def _Log(message, *args):
67 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 22:46:2268
Frank Farzan40160872011-12-13 02:39:1869
Chris Sosa417e55d2011-01-26 00:40:4870CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-17 01:36:1471
Simran Basi4baad082013-02-14 21:39:1872TELEMETRY_FOLDER = 'telemetry_src'
73TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
74 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 23:40:0475 'dep-chrome_test.tar.bz2',
76 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 21:39:1877
Chris Sosa0356d3b2010-09-16 22:46:2278# Sets up global to share between classes.
[email protected]21a5ca32009-11-04 18:23:2379updater = None
[email protected]ded22402009-10-26 22:36:2180
J. Richard Barnette3d977b82013-04-23 18:05:1981# Log rotation parameters. These settings correspond to once a week
J. Richard Barnette6dfa5342013-06-04 18:48:5682# at midnight between Friday and Saturday, with about three months
83# of old logs kept for backup.
J. Richard Barnette3d977b82013-04-23 18:05:1984#
85# For more, see the documentation for
86# logging.handlers.TimedRotatingFileHandler
J. Richard Barnette6dfa5342013-06-04 18:48:5687_LOG_ROTATION_TIME = 'W4'
J. Richard Barnette3d977b82013-04-23 18:05:1988_LOG_ROTATION_BACKUP = 13
89
Frank Farzan40160872011-12-13 02:39:1890
Chris Sosa9164ca32012-03-28 18:04:5091class DevServerError(Exception):
Chris Sosa47a7d4e2012-03-28 18:26:5592 """Exception class used by this module."""
93 pass
94
95
Scott Zawalski4647ce62012-01-03 22:17:2896def _LeadingWhiteSpaceCount(string):
97 """Count the amount of leading whitespace in a string.
98
99 Args:
100 string: The string to count leading whitespace in.
101 Returns:
102 number of white space chars before characters start.
103 """
104 matched = re.match('^\s+', string)
105 if matched:
106 return len(matched.group())
107
108 return 0
109
110
111def _PrintDocStringAsHTML(func):
112 """Make a functions docstring somewhat HTML style.
113
114 Args:
115 func: The function to return the docstring from.
116 Returns:
117 A string that is somewhat formated for a web browser.
118 """
119 # TODO(scottz): Make this parse Args/Returns in a prettier way.
120 # Arguments could be bolded and indented etc.
121 html_doc = []
122 for line in func.__doc__.splitlines():
123 leading_space = _LeadingWhiteSpaceCount(line)
124 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 18:26:55125 line = '&nbsp;' * leading_space + line
Scott Zawalski4647ce62012-01-03 22:17:28126
127 html_doc.append('<BR>%s' % line)
128
129 return '\n'.join(html_doc)
130
131
Chris Sosa7c931362010-10-12 02:49:01132def _GetConfig(options):
133 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-08 01:55:33134
135 # On a system with IPv6 not compiled into the kernel,
136 # AF_INET6 sockets will return a socket.error exception.
137 # On such systems, fall-back to IPv4.
138 socket_host = '::'
139 try:
140 socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
141 except socket.error:
142 socket_host = '0.0.0.0'
143
Chris Sosa7c931362010-10-12 02:49:01144 base_config = { 'global':
145 { 'server.log_request_headers': True,
146 'server.protocol_version': 'HTTP/1.1',
Mandeep Singh Baines38dcdda2012-12-08 01:55:33147 'server.socket_host': socket_host,
Chris Sosa7c931362010-10-12 02:49:01148 'server.socket_port': int(options.port),
Chris Sosa374c62d2010-10-14 16:13:54149 'response.timeout': 6000,
Chris Sosa6fe23942012-07-02 22:44:46150 'request.show_tracebacks': True,
Chris Sosa72333d12012-06-13 18:28:05151 'server.socket_timeout': 60,
joychenecc02aa2013-07-18 01:27:35152 'server.thread_pool': 2,
Chris Sosa7c931362010-10-12 02:49:01153 },
Dale Curtisc9aaf3a2011-08-09 22:47:40154 '/api':
155 {
156 # Gets rid of cherrypy parsing post file for args.
157 'request.process_request_body': False,
158 },
Chris Sosaa1ef0102010-10-21 23:22:35159 '/build':
160 {
161 'response.timeout': 100000,
162 },
Chris Sosa7c931362010-10-12 02:49:01163 '/update':
164 {
165 # Gets rid of cherrypy parsing post file for args.
166 'request.process_request_body': False,
Chris Sosaf65f4b92010-10-21 22:57:51167 'response.timeout': 10000,
Chris Sosa7c931362010-10-12 02:49:01168 },
169 # Sets up the static dir for file hosting.
170 '/static':
joychened64b222013-06-21 23:39:34171 { 'tools.staticdir.dir': options.static_dir,
Chris Sosa7c931362010-10-12 02:49:01172 'tools.staticdir.on': True,
Chris Sosaf65f4b92010-10-21 22:57:51173 'response.timeout': 10000,
Chris Sosa7c931362010-10-12 02:49:01174 },
175 }
Chris Sosa5f118ef2012-07-12 18:37:50176 if options.production:
Alex Miller93beca52013-07-31 02:25:09177 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 18:12:52178
Chris Sosa7c931362010-10-12 02:49:01179 return base_config
[email protected]64244662009-11-12 00:52:08180
Darin Petkove17164a2010-08-11 20:24:41181
Gilad Arnoldd5ebaaa2012-10-02 18:52:38182def _GetRecursiveMemberObject(root, member_list):
183 """Returns an object corresponding to a nested member list.
184
185 Args:
186 root: the root object to search
187 member_list: list of nested members to search
188 Returns:
189 An object corresponding to the member name list; None otherwise.
190 """
191 for member in member_list:
192 next_root = root.__class__.__dict__.get(member)
193 if not next_root:
194 return None
195 root = next_root
196 return root
197
198
199def _IsExposed(name):
200 """Returns True iff |name| has an `exposed' attribute and it is set."""
201 return hasattr(name, 'exposed') and name.exposed
202
203
Gilad Arnold748c8322012-10-12 16:51:35204def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 18:52:38205 """Returns a CherryPy-exposed method, if such exists.
206
207 Args:
208 root: the root object for searching
209 nested_member: a slash-joined path to the nested member
210 ignored: method paths to be ignored
211 Returns:
212 A function object corresponding to the path defined by |member_list| from
213 the |root| object, if the function is exposed and not ignored; None
214 otherwise.
215 """
Gilad Arnold748c8322012-10-12 16:51:35216 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 18:52:38217 _GetRecursiveMemberObject(root, nested_member.split('/')))
218 if (method and type(method) == types.FunctionType and _IsExposed(method)):
219 return method
220
221
Gilad Arnold748c8322012-10-12 16:51:35222def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 18:52:38223 """Finds exposed CherryPy methods.
224
225 Args:
226 root: the root object for searching
227 prefix: slash-joined chain of members leading to current object
228 unlisted: URLs to be excluded regardless of their exposed status
229 Returns:
230 List of exposed URLs that are not unlisted.
231 """
232 method_list = []
233 for member in sorted(root.__class__.__dict__.keys()):
234 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 16:51:35235 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 18:52:38236 continue
237 member_obj = root.__class__.__dict__[member]
238 if _IsExposed(member_obj):
239 if type(member_obj) == types.FunctionType:
240 method_list.append(prefixed_member)
241 else:
242 method_list += _FindExposedMethods(
243 member_obj, prefixed_member, unlisted)
244 return method_list
245
246
Dale Curtisc9aaf3a2011-08-09 22:47:40247class ApiRoot(object):
248 """RESTful API for Dev Server information."""
249 exposed = True
250
251 @cherrypy.expose
252 def hostinfo(self, ip):
253 """Returns a JSON dictionary containing information about the given ip.
254
Gilad Arnold1b908392012-10-05 18:36:27255 Args:
256 ip: address of host whose info is requested
257 Returns:
258 A JSON dictionary containing all or some of the following fields:
259 last_event_type (int): last update event type received
260 last_event_status (int): last update event status received
261 last_known_version (string): last known version reported in update ping
262 forced_update_label (string): update label to force next update ping to
263 use, set by setnextupdate
264 See the OmahaEvent class in update_engine/omaha_request_action.h for
265 event type and status code definitions. If the ip does not exist an empty
266 string is returned.
Dale Curtisc9aaf3a2011-08-09 22:47:40267
Gilad Arnold1b908392012-10-05 18:36:27268 Example URL:
269 http://myhost/api/hostinfo?ip=192.168.1.5
270 """
Dale Curtisc9aaf3a2011-08-09 22:47:40271 return updater.HandleHostInfoPing(ip)
272
273 @cherrypy.expose
Gilad Arnold286a0062012-01-12 21:47:02274 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 18:36:27275 """Returns a JSON object containing a log of host event.
276
277 Args:
278 ip: address of host whose event log is requested, or `all'
279 Returns:
280 A JSON encoded list (log) of dictionaries (events), each of which
281 containing a `timestamp' and other event fields, as described under
282 /api/hostinfo.
283
284 Example URL:
285 http://myhost/api/hostlog?ip=192.168.1.5
286 """
Gilad Arnold286a0062012-01-12 21:47:02287 return updater.HandleHostLogPing(ip)
288
289 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 22:47:40290 def setnextupdate(self, ip):
291 """Allows the response to the next update ping from a host to be set.
292
293 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 18:36:27294 /update command.
295 """
Dale Curtisc9aaf3a2011-08-09 22:47:40296 body_length = int(cherrypy.request.headers['Content-Length'])
297 label = cherrypy.request.rfile.read(body_length)
298
299 if label:
300 label = label.strip()
301 if label:
302 return updater.HandleSetUpdatePing(ip, label)
303 raise cherrypy.HTTPError(400, 'No label provided.')
304
305
Gilad Arnold55a2a372012-10-02 16:46:32306 @cherrypy.expose
307 def fileinfo(self, *path_args):
308 """Returns information about a given staged file.
309
310 Args:
311 path_args: path to the file inside the server's static staging directory
312 Returns:
313 A JSON encoded dictionary with information about the said file, which may
314 contain the following keys/values:
Gilad Arnold1b908392012-10-05 18:36:27315 size (int): the file size in bytes
316 sha1 (string): a base64 encoded SHA1 hash
317 sha256 (string): a base64 encoded SHA256 hash
318
319 Example URL:
320 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 16:46:32321 """
322 file_path = os.path.join(updater.static_dir, *path_args)
323 if not os.path.exists(file_path):
324 raise DevServerError('file not found: %s' % file_path)
325 try:
326 file_size = os.path.getsize(file_path)
327 file_sha1 = common_util.GetFileSha1(file_path)
328 file_sha256 = common_util.GetFileSha256(file_path)
329 except os.error, e:
330 raise DevServerError('failed to get info for file %s: %s' %
Gilad Arnolde74b3812013-04-22 18:27:38331 (file_path, e))
332
333 is_delta = autoupdate.Autoupdate.IsDeltaFormatFile(file_path)
334
335 return json.dumps({
336 autoupdate.Autoupdate.SIZE_ATTR: file_size,
337 autoupdate.Autoupdate.SHA1_ATTR: file_sha1,
338 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
339 autoupdate.Autoupdate.ISDELTA_ATTR: is_delta
340 })
Gilad Arnold55a2a372012-10-02 16:46:32341
Chris Sosa76e44b92013-01-31 20:11:38342
David Rochberg7c79a812011-01-19 19:24:45343class DevServerRoot(object):
Chris Sosa7c931362010-10-12 02:49:01344 """The Root Class for the Dev Server.
345
346 CherryPy works as follows:
347 For each method in this class, cherrpy interprets root/path
348 as a call to an instance of DevServerRoot->method_name. For example,
349 a call to http://myhost/build will call build. CherryPy automatically
350 parses http args and places them as keyword arguments in each method.
351 For paths http://myhost/update/dir1/dir2, you can use *args so that
352 cherrypy uses the update method and puts the extra paths in args.
353 """
Gilad Arnoldf8f769f2012-09-24 15:43:01354 # Method names that should not be listed on the index page.
355 _UNLISTED_METHODS = ['index', 'doc']
356
Dale Curtisc9aaf3a2011-08-09 22:47:40357 api = ApiRoot()
Chris Sosa7c931362010-10-12 02:49:01358
Dan Shi59ae7092013-06-04 21:37:27359 # Number of threads that devserver is staging images.
360 _staging_thread_count = 0
361 # Lock used to lock increasing/decreasing count.
362 _staging_thread_count_lock = threading.Lock()
363
joychen3cb228e2013-06-12 19:13:13364 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 22:20:41365 self._builder = None
Simran Basi4baad082013-02-14 21:39:18366 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 19:13:13367 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 19:24:45368
Chris Sosa6b0c6172013-08-06 00:01:33369 @staticmethod
370 def _get_artifacts(kwargs):
371 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
372
373 Raises: DevserverError if no artifacts would be returned.
374 """
375 artifacts = kwargs.get('artifacts')
376 files = kwargs.get('files')
377 if not artifacts and not files:
378 raise DevServerError('No artifacts specified.')
379
380 return (artifacts.split(',') if artifacts else [],
381 files.split(',') if files else [])
382
Dale Curtisc9aaf3a2011-08-09 22:47:40383 @cherrypy.expose
David Rochberg7c79a812011-01-19 19:24:45384 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-12 02:49:01385 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 22:20:41386 import builder
387 if self._builder is None:
388 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 19:24:45389 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-12 02:49:01390
Chris Sosacde6bf42012-06-01 01:36:39391 @staticmethod
392 def _canonicalize_archive_url(archive_url):
393 """Canonicalizes archive_url strings.
394
395 Raises:
396 DevserverError: if archive_url is not set.
397 """
398 if archive_url:
Chris Sosa76e44b92013-01-31 20:11:38399 if not archive_url.startswith('gs://'):
400 raise DevServerError("Archive URL isn't from Google Storage.")
401
Chris Sosacde6bf42012-06-01 01:36:39402 return archive_url.rstrip('/')
403 else:
404 raise DevServerError("Must specify an archive_url in the request")
405
Dale Curtisc9aaf3a2011-08-09 22:47:40406 @cherrypy.expose
Frank Farzanbcb571e2012-01-03 19:48:17407 def download(self, **kwargs):
408 """Downloads and archives full/delta payloads from Google Storage.
409
Chris Sosa76e44b92013-01-31 20:11:38410 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Frank Farzanbcb571e2012-01-03 19:48:17411 """
Dan Shif8eb0d12013-08-02 00:52:06412 async = kwargs.get('async', False)
Chris Sosa76e44b92013-01-31 20:11:38413 return self.stage(archive_url=kwargs.get('archive_url'),
Dan Shiffaaf0c2013-08-07 04:22:31414 artifacts='full_payload,test_suites,stateful',
Dan Shif8eb0d12013-08-02 00:52:06415 async=async)
Chris Sosa76e44b92013-01-31 20:11:38416
Dan Shif8eb0d12013-08-02 00:52:06417 @cherrypy.expose
418 def is_staged(self, **kwargs):
419 """Check if artifacts have been downloaded.
420
Chris Sosa6b0c6172013-08-06 00:01:33421 async: True to return without waiting for download to complete.
422 artifacts: Comma separated list of named artifacts to download.
423 These are defined in artifact_info and have their implementation
424 in build_artifact.py.
425 files: Comma separated list of file artifacts to stage. These
426 will be available as is in the corresponding static directory with no
427 custom post-processing.
428
429 returns: True of all artifacts are staged.
Dan Shif8eb0d12013-08-02 00:52:06430
431 Example:
432 To check if autotest and test_suites are staged:
433 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
434 artifacts=autotest,test_suites
435 """
436 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Chris Sosa6b0c6172013-08-06 00:01:33437 artifacts, files = self._get_artifacts(kwargs)
Dan Shif8eb0d12013-08-02 00:52:06438 return str(downloader.Downloader(updater.static_dir, archive_url).IsStaged(
Chris Sosa6b0c6172013-08-06 00:01:33439 artifacts, files))
Dan Shi59ae7092013-06-04 21:37:27440
Chris Sosa76e44b92013-01-31 20:11:38441 @cherrypy.expose
442 def stage(self, **kwargs):
443 """Downloads and caches the artifacts from Google Storage URL.
444
445 Downloads and caches the artifacts Google Storage URL. Returns once these
446 have been downloaded on the devserver. A call to this will attempt to cache
447 non-specified artifacts in the background for the given from the given URL
448 following the principle of spatial locality. Spatial locality of different
449 artifacts is explicitly defined in the build_artifact module.
450
451 These artifacts will then be available from the static/ sub-directory of
452 the devserver.
453
454 Args:
455 archive_url: Google Storage URL for the build.
Dan Shif8eb0d12013-08-02 00:52:06456 async: True to return without waiting for download to complete.
Chris Sosa6b0c6172013-08-06 00:01:33457 artifacts: Comma separated list of named artifacts to download.
458 These are defined in artifact_info and have their implementation
459 in build_artifact.py.
460 files: Comma separated list of files to stage. These
461 will be available as is in the corresponding static directory with no
462 custom post-processing.
Chris Sosa76e44b92013-01-31 20:11:38463
464 Example:
465 To download the autotest and test suites tarballs:
466 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
467 artifacts=autotest,test_suites
468 To download the full update payload:
469 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
470 artifacts=full_payload
Chris Sosa6b0c6172013-08-06 00:01:33471 To download just a file called blah.bin:
472 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
473 files=blah.bin
Chris Sosa76e44b92013-01-31 20:11:38474
475 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 23:39:34476 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 20:11:38477
478 Note for this example, relative path is the archive_url stripped of its
479 basename i.e. path/ in the examples above. Specific example:
480
481 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
482
483 Will get staged to:
484
joychened64b222013-06-21 23:39:34485 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Chris Sosa76e44b92013-01-31 20:11:38486 """
Chris Sosacde6bf42012-06-01 01:36:39487 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Dan Shif8eb0d12013-08-02 00:52:06488 async = kwargs.get('async', False)
Chris Sosa6b0c6172013-08-06 00:01:33489 artifacts, files = self._get_artifacts(kwargs)
Dan Shi59ae7092013-06-04 21:37:27490 with DevServerRoot._staging_thread_count_lock:
491 DevServerRoot._staging_thread_count += 1
492 try:
Chris Sosa6b0c6172013-08-06 00:01:33493 downloader.Downloader(updater.static_dir, archive_url).Download(
494 artifacts, files, async=async)
Dan Shi59ae7092013-06-04 21:37:27495 finally:
496 with DevServerRoot._staging_thread_count_lock:
497 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 20:11:38498 return 'Success'
Chris Sosacde6bf42012-06-01 01:36:39499
500 @cherrypy.expose
Simran Basi4baad082013-02-14 21:39:18501 def setup_telemetry(self, **kwargs):
502 """Extracts and sets up telemetry
503
504 This method goes through the telemetry deps packages, and stages them on
505 the devserver to be used by the drones and the telemetry tests.
506
507 Args:
508 archive_url: Google Storage URL for the build.
509
510 Returns:
511 Path to the source folder for the telemetry codebase once it is staged.
512 """
513 archive_url = kwargs.get('archive_url')
514 self.stage(archive_url=archive_url, artifacts='autotest')
515
516 build = '/'.join(downloader.Downloader.ParseUrl(archive_url))
517 build_path = os.path.join(updater.static_dir, build)
518 deps_path = os.path.join(build_path, 'autotest/packages')
519 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
520 src_folder = os.path.join(telemetry_path, 'src')
521
522 with self._telemetry_lock_dict.lock(telemetry_path):
523 if os.path.exists(src_folder):
524 # Telemetry is already fully stage return
525 return src_folder
526
527 common_util.MkDirP(telemetry_path)
528
529 # Copy over the required deps tar balls to the telemetry directory.
530 for dep in TELEMETRY_DEPS:
531 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 23:40:04532 if not os.path.exists(dep_path):
533 # This dep does not exist (could be new), do not extract it.
534 continue
Simran Basi4baad082013-02-14 21:39:18535 try:
536 common_util.ExtractTarball(dep_path, telemetry_path)
537 except common_util.CommonUtilError as e:
538 shutil.rmtree(telemetry_path)
539 raise DevServerError(str(e))
540
541 # By default all the tarballs extract to test_src but some parts of
542 # the telemetry code specifically hardcoded to exist inside of 'src'.
543 test_src = os.path.join(telemetry_path, 'test_src')
544 try:
545 shutil.move(test_src, src_folder)
546 except shutil.Error:
547 # This can occur if src_folder already exists. Remove and retry move.
548 shutil.rmtree(src_folder)
549 raise DevServerError('Failure in telemetry setup for build %s. Appears'
550 ' that the test_src to src move failed.' % build)
551
552 return src_folder
553
554 @cherrypy.expose
Chris Sosacde6bf42012-06-01 01:36:39555 def wait_for_status(self, **kwargs):
556 """Waits for background artifacts to be downloaded from Google Storage.
557
Chris Sosa76e44b92013-01-31 20:11:38558 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosacde6bf42012-06-01 01:36:39559 """
Dan Shiffaaf0c2013-08-07 04:22:31560 async = kwargs.get('async', False)
Chris Sosa6b0c6172013-08-06 00:01:33561 return self.stage(
562 archive_url=kwargs.get('archive_url'),
563 artifacts='full_payload,test_suites,autotest,stateful',
564 async=async)
Chris Sosa47a7d4e2012-03-28 18:26:55565
566 @cherrypy.expose
Chris Masone816e38c2012-05-02 19:22:36567 def stage_debug(self, **kwargs):
568 """Downloads and stages debug symbol payloads from Google Storage.
569
Chris Sosa76e44b92013-01-31 20:11:38570 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Masone816e38c2012-05-02 19:22:36571 """
Chris Sosa76e44b92013-01-31 20:11:38572 return self.stage(archive_url=kwargs.get('archive_url'),
573 artifacts='symbols')
Chris Masone816e38c2012-05-02 19:22:36574
575 @cherrypy.expose
Chris Sosa76e44b92013-01-31 20:11:38576 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 19:22:36577 """Symbolicates a minidump using pre-downloaded symbols, returns it.
578
579 Callers will need to POST to this URL with a body of MIME-type
580 "multipart/form-data".
581 The body should include a single argument, 'minidump', containing the
582 binary-formatted minidump to symbolicate.
583
Chris Masone816e38c2012-05-02 19:22:36584 Args:
Chris Sosa76e44b92013-01-31 20:11:38585 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 19:22:36586 minidump: The binary minidump file to symbolicate.
587 """
Chris Sosa76e44b92013-01-31 20:11:38588 # Ensure the symbols have been staged.
589 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
590 if self.stage(archive_url=archive_url, artifacts='symbols') != 'Success':
591 raise DevServerError('Failed to stage symbols for %s' % archive_url)
592
Chris Masone816e38c2012-05-02 19:22:36593 to_return = ''
594 with tempfile.NamedTemporaryFile() as local:
595 while True:
596 data = minidump.file.read(8192)
597 if not data:
598 break
599 local.write(data)
Chris Sosa76e44b92013-01-31 20:11:38600
Chris Masone816e38c2012-05-02 19:22:36601 local.flush()
Chris Sosa76e44b92013-01-31 20:11:38602
603 symbols_directory = os.path.join(downloader.Downloader.GetBuildDir(
604 updater.static_dir, archive_url), 'debug', 'breakpad')
605
606 stackwalk = subprocess.Popen(
607 ['minidump_stackwalk', local.name, symbols_directory],
608 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
609
Chris Masone816e38c2012-05-02 19:22:36610 to_return, error_text = stackwalk.communicate()
611 if stackwalk.returncode != 0:
612 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
613 error_text, stackwalk.returncode))
614
615 return to_return
616
617 @cherrypy.expose
Scott Zawalski16954532012-03-20 19:31:36618 def latestbuild(self, **params):
619 """Return a string representing the latest build for a given target.
620
621 Args:
622 target: The build target, typically a combination of the board and the
623 type of build e.g. x86-mario-release.
624 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
625 provided the latest RXX build will be returned.
626 Returns:
627 A string representation of the latest build if one exists, i.e.
628 R19-1993.0.0-a1-b1480.
629 An empty string if no latest could be found.
630 """
631 if not params:
632 return _PrintDocStringAsHTML(self.latestbuild)
633
634 if 'target' not in params:
635 raise cherrypy.HTTPError('500 Internal Server Error',
636 'Error: target= is required!')
637 try:
Gilad Arnoldc65330c2012-09-20 22:17:48638 return common_util.GetLatestBuildVersion(
Scott Zawalski16954532012-03-20 19:31:36639 updater.static_dir, params['target'],
640 milestone=params.get('milestone'))
Gilad Arnold17fe03d2012-10-02 17:05:01641 except common_util.CommonUtilError as errmsg:
Scott Zawalski16954532012-03-20 19:31:36642 raise cherrypy.HTTPError('500 Internal Server Error', str(errmsg))
643
644 @cherrypy.expose
Scott Zawalski84a39c92012-01-13 20:12:42645 def controlfiles(self, **params):
Scott Zawalski4647ce62012-01-03 22:17:28646 """Return a control file or a list of all known control files.
647
648 Example URL:
649 To List all control files:
beepsbd337242013-07-10 05:44:06650 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
651 To List all control files for, say, the bvt suite:
652 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 22:17:28653 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 20:12:42654 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0&control_path=client/sleeptest/control
Scott Zawalski4647ce62012-01-03 22:17:28655
656 Args:
Scott Zawalski84a39c92012-01-13 20:12:42657 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 22:17:28658 control_path: If you want the contents of a control file set this
659 to the path. E.g. client/site_tests/sleeptest/control
660 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-10 05:44:06661 suite_name: If control_path is not specified but a suite_name is
662 specified, list the control files belonging to that suite instead of
663 all control files. The empty string for suite_name will list all control
664 files for the build.
Scott Zawalski4647ce62012-01-03 22:17:28665 Returns:
666 Contents of a control file if control_path is provided.
667 A list of control files if no control_path is provided.
668 """
Scott Zawalski4647ce62012-01-03 22:17:28669 if not params:
670 return _PrintDocStringAsHTML(self.controlfiles)
671
Scott Zawalski84a39c92012-01-13 20:12:42672 if 'build' not in params:
Scott Zawalski4647ce62012-01-03 22:17:28673 raise cherrypy.HTTPError('500 Internal Server Error',
Scott Zawalski84a39c92012-01-13 20:12:42674 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 22:17:28675
676 if 'control_path' not in params:
beepsbd337242013-07-10 05:44:06677 if 'suite_name' in params and params['suite_name']:
678 return common_util.GetControlFileListForSuite(
679 updater.static_dir, params['build'], params['suite_name'])
680 else:
681 return common_util.GetControlFileList(
682 updater.static_dir, params['build'])
Scott Zawalski4647ce62012-01-03 22:17:28683 else:
Gilad Arnoldc65330c2012-09-20 22:17:48684 return common_util.GetControlFile(
685 updater.static_dir, params['build'], params['control_path'])
Frank Farzan40160872011-12-13 02:39:18686
687 @cherrypy.expose
Gilad Arnold6f99b982012-09-12 17:49:40688 def stage_images(self, **kwargs):
689 """Downloads and stages a Chrome OS image from Google Storage.
690
Chris Sosa76e44b92013-01-31 20:11:38691 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Gilad Arnold6f99b982012-09-12 17:49:40692 """
Gilad Arnold6f99b982012-09-12 17:49:40693 image_types = kwargs.get('image_types').split(',')
Chris Sosa76e44b92013-01-31 20:11:38694 image_types_list = [image + '_image' for image in image_types]
695 self.stage(archive_url=kwargs.get('archive_url'), artifacts=','.join(
696 image_types_list))
Gilad Arnold6f99b982012-09-12 17:49:40697
698 @cherrypy.expose
joycheneaf4cfc2013-07-02 15:38:57699 def xbuddy(self, *args, **kwargs):
700 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 19:13:13701
702 Args:
joycheneaf4cfc2013-07-02 15:38:57703 path_parts: the path following xbuddy/ in the call url is split into the
704 components of the path.
705 The path can be understood as a build_id/artifact, build_id is
706 composed of "board/version"
707
708 path_parts[0], the board, is the familiar board name, optionally
709 suffixed.
710 path_parts[1], the version, can be the google storage version
711 number, and may also be any of a number of xBuddy defined version
712 aliases that will be translated into the latest built image that
713 fits the description. defaults to latest.
714 path_parts[2], the artifact, is one of a number of image or artifact
715 aliases used by xbuddy, defined in xbuddy:ALIASES. Defaults to test
716
717 Kwargs:
joychen3cb228e2013-06-12 19:13:13718 return_dir: {true|false}
719 if set to true, returns the url to the update.gz
720 instead.
721
722 Example URL:
joycheneaf4cfc2013-07-02 15:38:57723 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 19:13:13724 or
joycheneaf4cfc2013-07-02 15:38:57725 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 19:13:13726
727 Returns:
728 A redirect to the image or update file on the devserver.
729 e.g. http://host:port/static/archive/x86-generic-release/
730 R26-4000.0.0/chromium-test-image.bin
731 or if return_dir is True, return path to the folder where
732 image or update file is
733 http://host:port/static/x86-generic-release/R26-4000.0.0/
734 """
735 boolean_string = kwargs.get('return_dir')
736 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
joycheneaf4cfc2013-07-02 15:38:57737 return_url = self._xbuddy.Get(args,
joychen3cb228e2013-06-12 19:13:13738 return_dir)
739 if return_dir:
joycheneaf4cfc2013-07-02 15:38:57740 directory = os.path.join(cherrypy.request.base, return_url)
741 _Log("Directory requested, returning: %s", directory)
742 return directory
joychen3cb228e2013-06-12 19:13:13743 else:
joycheneaf4cfc2013-07-02 15:38:57744 return_url = '/' + return_url
745 _Log("Payload requested, returning: %s", return_url)
joychen3cb228e2013-06-12 19:13:13746 raise cherrypy.HTTPRedirect(return_url, 302)
747
748 @cherrypy.expose
749 def xbuddy_list(self):
750 """Lists the currently available images & time since last access.
751
752 @return: A string representation of a list of tuples
753 [(build_id, time since last access),...]
754 """
755 return self._xbuddy.List()
756
757 @cherrypy.expose
758 def xbuddy_capacity(self):
759 """Returns the number of images cached by xBuddy.
760
761 @return: Capacity of this devserver.
762 """
763 return self._xbuddy.Capacity()
764
765 @cherrypy.expose
Chris Sosa7c931362010-10-12 02:49:01766 def index(self):
Gilad Arnoldf8f769f2012-09-24 15:43:01767 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 15:43:01768 return ('Welcome to the Dev Server!<br>\n'
769 '<br>\n'
770 'Here are the available methods, click for documentation:<br>\n'
771 '<br>\n'
772 '%s' %
773 '<br>\n'.join(
774 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 18:52:38775 for name in _FindExposedMethods(
776 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 15:43:01777
778 @cherrypy.expose
779 def doc(self, *args):
780 """Shows the documentation for available methods / URLs.
781
782 Example:
783 http://myhost/doc/update
784 """
Gilad Arnoldd5ebaaa2012-10-02 18:52:38785 name = '/'.join(args)
786 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 15:43:01787 if not method:
788 raise DevServerError("No exposed method named `%s'" % name)
789 if not method.__doc__:
790 raise DevServerError("No documentation for exposed method `%s'" % name)
791 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-12 02:49:01792
Dale Curtisc9aaf3a2011-08-09 22:47:40793 @cherrypy.expose
Chris Sosa7c931362010-10-12 02:49:01794 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 15:43:01795 """Handles an update check from a Chrome OS client.
796
797 The HTTP request should contain the standard Omaha-style XML blob. The URL
798 line may contain an additional intermediate path to the update payload.
799
joychenb0dfe552013-07-30 17:02:06800 Paths that can be handled by xbuddy are formatted:
801 http://myhost/update/xbuddy/board/version
802
Gilad Arnoldf8f769f2012-09-24 15:43:01803 Example:
804 http://myhost/update/optional/path/to/payload
805 """
joychen346531c2013-07-24 23:55:56806 if len(args) > 0 and args[0] == 'xbuddy':
807 # Interpret the rest of the path as an xbuddy path
808 label, found = self._xbuddy.Translate(args[1:] + ('full_payload',))
809 if not found:
810 _Log("Update payload not found for %s, xBuddy looking it up.", label)
811 else:
812 label = '/'.join(args)
813
814 _Log('Update label: %s', label)
Gilad Arnold286a0062012-01-12 21:47:02815 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-12 02:49:01816 data = cherrypy.request.rfile.read(body_length)
817 return updater.HandleUpdatePing(data, label)
818
Chris Sosa0356d3b2010-09-16 22:46:22819
Dan Shif5ce2de2013-04-25 23:06:32820 @cherrypy.expose
821 def check_health(self):
822 """Collect the health status of devserver to see if it's ready for staging.
823
824 @return: A JSON dictionary containing all or some of the following fields:
Dan Shi59ae7092013-06-04 21:37:27825 free_disk (int): free disk space in GB
826 staging_thread_count (int): number of devserver threads currently
827 staging an image
Dan Shif5ce2de2013-04-25 23:06:32828 """
829 # Get free disk space.
830 stat = os.statvfs(updater.static_dir)
831 free_disk = stat.f_bsize * stat.f_bavail / 1000000000
832
833 return json.dumps({
834 'free_disk': free_disk,
Dan Shi59ae7092013-06-04 21:37:27835 'staging_thread_count': DevServerRoot._staging_thread_count,
Dan Shif5ce2de2013-04-25 23:06:32836 })
837
838
Chris Sosadbc20082012-12-10 21:39:11839def _CleanCache(cache_dir, wipe):
840 """Wipes any excess cached items in the cache_dir.
841
842 Args:
843 cache_dir: the directory we are wiping from.
844 wipe: If True, wipe all the contents -- not just the excess.
845 """
846 if wipe:
847 # Clear the cache and exit on error.
848 cmd = 'rm -rf %s/*' % cache_dir
849 if os.system(cmd) != 0:
850 _Log('Failed to clear the cache with %s' % cmd)
851 sys.exit(1)
852 else:
853 # Clear all but the last N cached updates
854 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
855 (cache_dir, CACHED_ENTRIES))
856 if os.system(cmd) != 0:
857 _Log('Failed to clean up old delta cache files with %s' % cmd)
858 sys.exit(1)
859
860
Chris Sosa3ae4dc12013-03-29 18:47:00861def _AddTestingOptions(parser):
862 group = optparse.OptionGroup(
863 parser, 'Advanced Testing Options', 'These are used by test scripts and '
864 'developers writing integration tests utilizing the devserver. They are '
865 'not intended to be really used outside the scope of someone '
866 'knowledgable about the test.')
867 group.add_option('--exit',
868 action='store_true',
869 help='do not start the server (yet pregenerate/clear cache)')
870 group.add_option('--host_log',
871 action='store_true', default=False,
872 help='record history of host update events (/api/hostlog)')
873 group.add_option('--max_updates',
874 metavar='NUM', default= -1, type='int',
875 help='maximum number of update checks handled positively '
876 '(default: unlimited)')
877 group.add_option('--private_key',
878 metavar='PATH', default=None,
879 help='path to the private key in pem format. If this is set '
880 'the devserver will generate update payloads that are '
881 'signed with this key.')
882 group.add_option('--proxy_port',
883 metavar='PORT', default=None, type='int',
884 help='port to have the client connect to -- basically the '
885 'devserver lies to the update to tell it to get the payload '
886 'from a different port that will proxy the request back to '
887 'the devserver. The proxy must be managed outside the '
888 'devserver.')
889 group.add_option('--remote_payload',
890 action='store_true', default=False,
891 help='Payload is being served from a remote machine')
892 group.add_option('-u', '--urlbase',
893 metavar='URL',
894 help='base URL for update images, other than the '
895 'devserver. Use in conjunction with remote_payload.')
896 parser.add_option_group(group)
897
898
899def _AddUpdateOptions(parser):
900 group = optparse.OptionGroup(
901 parser, 'Autoupdate Options', 'These options can be used to change '
902 'how the devserver either generates or serve update payloads. Please '
903 'note that all of these option affect how a payload is generated and so '
904 'do not work in archive-only mode.')
905 group.add_option('--board',
906 help='By default the devserver will create an update '
907 'payload from the latest image built for the board '
908 'a device that is requesting an update has. When we '
909 'pre-generate an update (see below) and we do not specify '
910 'another update_type option like image or payload, the '
911 'devserver needs to know the board to generate the latest '
912 'image for. This is that board.')
913 group.add_option('--critical_update',
914 action='store_true', default=False,
915 help='Present update payload as critical')
916 group.add_option('--for_vm',
917 dest='vm', action='store_true',
918 help='DEPRECATED: see no_patch_kernel.')
919 group.add_option('--image',
920 metavar='FILE',
921 help='Generate and serve an update using this image to any '
922 'device that requests an update.')
923 group.add_option('--no_patch_kernel',
924 dest='patch_kernel', action='store_false', default=True,
925 help='When generating an update payload, do not patch the '
926 'kernel with kernel verification blob from the stateful '
927 'partition.')
928 group.add_option('--payload',
929 metavar='PATH',
930 help='use the update payload from specified directory '
931 '(update.gz).')
932 group.add_option('-p', '--pregenerate_update',
933 action='store_true', default=False,
934 help='pre-generate the update payload before accepting '
935 'update requests. Useful to help debug payload generation '
936 'issues quickly. Also if an update payload will take a '
937 'long time to generate, a client may timeout if you do not'
938 'pregenerate the update.')
939 group.add_option('--src_image',
940 metavar='PATH', default='',
941 help='If specified, delta updates will be generated using '
942 'this image as the source image. Delta updates are when '
943 'you are updating from a "source image" to a another '
944 'image.')
945 parser.add_option_group(group)
946
947
948def _AddProductionOptions(parser):
949 group = optparse.OptionGroup(
950 parser, 'Advanced Server Options', 'These options can be used to changed '
951 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 18:47:00952 group.add_option('--clear_cache',
953 action='store_true', default=False,
954 help='At startup, removes all cached entries from the'
955 'devserver\'s cache.')
956 group.add_option('--logfile',
957 metavar='PATH',
958 help='log output to this file instead of stdout')
959 group.add_option('--production',
960 action='store_true', default=False,
961 help='have the devserver use production values when '
962 'starting up. This includes using more threads and '
963 'performing less logging.')
964 parser.add_option_group(group)
965
966
J. Richard Barnette3d977b82013-04-23 18:05:19967def _MakeLogHandler(logfile):
968 """Create a LogHandler instance used to log all messages."""
969 hdlr_cls = handlers.TimedRotatingFileHandler
970 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
971 backupCount=_LOG_ROTATION_BACKUP)
972 # The cherrypy documentation says to use the _cplogging module for
973 # this, even though it's named as a private module.
974 # pylint: disable=W0212
975 hdlr.setFormatter(cherrypy._cplogging.logfmt)
976 return hdlr
977
978
Chris Sosacde6bf42012-06-01 01:36:39979def main():
Chris Sosa3ae4dc12013-03-29 18:47:00980 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 21:47:02981 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 23:39:34982
983 # get directory that the devserver is run from
984 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 16:17:23985 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 23:39:34986 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 17:09:42987 metavar='PATH',
joychen84d13772013-08-06 16:17:23988 default=default_static_dir,
joychened64b222013-06-21 23:39:34989 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 17:09:42990 parser.add_option('--port',
991 default=8080, type='int',
992 help='port for the dev server to use (default: 8080)')
Gilad Arnold9714d9b2012-10-04 17:09:42993 parser.add_option('-t', '--test_image',
994 action='store_true',
Chris Sosa3ae4dc12013-03-29 18:47:00995 help='If set, look for the chromiumos_test_image.bin file '
996 'when generating update payloads rather than the '
997 'chromiumos_image.bin which is the default.')
joychen5260b9a2013-07-16 21:48:01998 parser.add_option('-x', '--xbuddy_manage_builds',
999 action='store_true',
1000 default=False,
1001 help='If set, allow xbuddy to manage images in'
1002 'build/images.')
Chris Sosa3ae4dc12013-03-29 18:47:001003 _AddProductionOptions(parser)
1004 _AddUpdateOptions(parser)
1005 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-12 02:49:011006 (options, _) = parser.parse_args()
[email protected]21a5ca32009-11-04 18:23:231007
J. Richard Barnette3d977b82013-04-23 18:05:191008 # Handle options that must be set globally in cherrypy. Do this
1009 # work up front, because calls to _Log() below depend on this
1010 # initialization.
1011 if options.production:
1012 cherrypy.config.update({'environment': 'production'})
1013 if not options.logfile:
1014 cherrypy.config.update({'log.screen': True})
1015 else:
1016 cherrypy.config.update({'log.error_file': '',
1017 'log.access_file': ''})
1018 hdlr = _MakeLogHandler(options.logfile)
1019 # Pylint can't seem to process these two calls properly
1020 # pylint: disable=E1101
1021 cherrypy.log.access_log.addHandler(hdlr)
1022 cherrypy.log.error_log.addHandler(hdlr)
1023 # pylint: enable=E1101
1024
Chris Sosa7c931362010-10-12 02:49:011025 root_dir = os.path.realpath('%s/../..' % devserver_dir)
Chris Sosa0356d3b2010-09-16 22:46:221026
J. Richard Barnette3d977b82013-04-23 18:05:191027 # TODO(sosa): Remove after deprecation.
Chris Sosa3ae4dc12013-03-29 18:47:001028 if options.vm:
1029 options.patch_kernel = False
1030
joychened64b222013-06-21 23:39:341031 # set static_dir, from which everything will be served
joychen84d13772013-08-06 16:17:231032 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 22:46:221033
joychened64b222013-06-21 23:39:341034 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 18:05:191035 # If our devserver is only supposed to serve payloads, we shouldn't be
1036 # mucking with the cache at all. If the devserver hadn't previously
1037 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 18:14:071038 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 21:39:111039 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 20:12:171040 else:
1041 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-17 01:36:141042
Chris Sosadbc20082012-12-10 21:39:111043 _Log('Using cache directory %s' % cache_dir)
Gilad Arnoldc65330c2012-09-20 22:17:481044 _Log('Source root is %s' % root_dir)
joychened64b222013-06-21 23:39:341045 _Log('Serving from %s' % options.static_dir)
[email protected]21a5ca32009-11-04 18:23:231046
Chris Sosa6a3697f2013-01-30 00:44:431047 # We allow global use here to share with cherrypy classes.
1048 # pylint: disable=W0603
Chris Sosacde6bf42012-06-01 01:36:391049 global updater
Andrew de los Reyes52620802010-04-12 20:40:071050 updater = autoupdate.Autoupdate(
1051 root_dir=root_dir,
joychened64b222013-06-21 23:39:341052 static_dir=options.static_dir,
Andrew de los Reyes52620802010-04-12 20:40:071053 urlbase=options.urlbase,
1054 test_image=options.test_image,
Chris Sosa5d342a22010-09-28 23:54:411055 forced_image=options.image,
Gilad Arnold0c9c8602012-10-03 06:58:581056 payload_path=options.payload,
Don Garrett0ad09372010-12-07 00:20:301057 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-29 06:42:371058 src_image=options.src_image,
Chris Sosa3ae4dc12013-03-29 18:47:001059 patch_kernel=options.patch_kernel,
Chris Sosa08d55a22011-01-20 00:08:021060 board=options.board,
Chris Sosa0f1ec842011-02-15 00:33:221061 copy_to_static_root=not options.exit,
1062 private_key=options.private_key,
Satoru Takabayashid733cbe2011-11-15 17:36:321063 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-03 06:58:581064 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 17:32:441065 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 19:52:231066 host_log=options.host_log,
Chris Sosa0f1ec842011-02-15 00:33:221067 )
Chris Sosa7c931362010-10-12 02:49:011068
Chris Sosa6a3697f2013-01-30 00:44:431069 if options.pregenerate_update:
1070 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 22:46:221071
J. Richard Barnette3d977b82013-04-23 18:05:191072 if options.exit:
1073 return
Chris Sosa2f1c41e2012-07-10 21:32:331074
joychen5260b9a2013-07-16 21:48:011075 _xbuddy = xbuddy.XBuddy(options.xbuddy_manage_builds,
joychenb0dfe552013-07-30 17:02:061076 options.board,
joychen5260b9a2013-07-16 21:48:011077 root_dir=root_dir,
joychenb0dfe552013-07-30 17:02:061078 static_dir=options.static_dir,
1079 )
joychen3cb228e2013-06-12 19:13:131080 dev_server = DevServerRoot(_xbuddy)
1081
1082 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-06-01 01:36:391083
1084
1085if __name__ == '__main__':
1086 main()