blob: a80046b6ac3382acea411e11fd2fca878edffacf [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
29a particular package from a developer's chroot onto a requesting device. Note
30if archive_dir is specified, this mode is disabled.
31
32For example:
33gmerge gmerge -d <devserver_url>
34
35devserver will see if a newer package of gmerge is available. If gmerge is
36cros_work'd on, it will re-build gmerge. After this, gmerge will install that
37version of gmerge that the devserver just created/found.
38
39For autoupdates, there are many more advanced options that can help specify
40how to update and which payload to give to a requester.
41"""
42
Chris Sosa7c931362010-10-12 02:49:0143
Gilad Arnold55a2a372012-10-02 16:46:3244import json
Sean O'Connor14b6a0a2010-03-21 06:23:4845import optparse
[email protected]ded22402009-10-26 22:36:2146import os
Scott Zawalski4647ce62012-01-03 22:17:2847import re
Simran Basi4baad082013-02-14 21:39:1848import shutil
Mandeep Singh Baines38dcdda2012-12-08 01:55:3349import socket
Chris Masone816e38c2012-05-02 19:22:3650import subprocess
J. Richard Barnette3d977b82013-04-23 18:05:1951import sys
Chris Masone816e38c2012-05-02 19:22:3652import tempfile
Dan Shi59ae7092013-06-04 21:37:2753import threading
Gilad Arnoldd5ebaaa2012-10-02 18:52:3854import types
J. Richard Barnette3d977b82013-04-23 18:05:1955from logging import handlers
56
57import cherrypy
58import cherrypy._cplogging
[email protected]ded22402009-10-26 22:36:2159
Chris Sosa0356d3b2010-09-16 22:46:2260import autoupdate
Gilad Arnoldc65330c2012-09-20 22:17:4861import common_util
Chris Sosa47a7d4e2012-03-28 18:26:5562import downloader
Gilad Arnoldc65330c2012-09-20 22:17:4863import log_util
joychen3cb228e2013-06-12 19:13:1364import xbuddy
Gilad Arnoldc65330c2012-09-20 22:17:4865
Gilad Arnoldc65330c2012-09-20 22:17:4866# Module-local log function.
Chris Sosa6a3697f2013-01-30 00:44:4367def _Log(message, *args):
68 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 22:46:2269
Frank Farzan40160872011-12-13 02:39:1870
Chris Sosa417e55d2011-01-26 00:40:4871CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-17 01:36:1472
Simran Basi4baad082013-02-14 21:39:1873TELEMETRY_FOLDER = 'telemetry_src'
74TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
75 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 23:40:0476 'dep-chrome_test.tar.bz2',
77 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 21:39:1878
Chris Sosa0356d3b2010-09-16 22:46:2279# Sets up global to share between classes.
[email protected]21a5ca32009-11-04 18:23:2380updater = None
[email protected]ded22402009-10-26 22:36:2181
J. Richard Barnette3d977b82013-04-23 18:05:1982# Log rotation parameters. These settings correspond to once a week
J. Richard Barnette6dfa5342013-06-04 18:48:5683# at midnight between Friday and Saturday, with about three months
84# of old logs kept for backup.
J. Richard Barnette3d977b82013-04-23 18:05:1985#
86# For more, see the documentation for
87# logging.handlers.TimedRotatingFileHandler
J. Richard Barnette6dfa5342013-06-04 18:48:5688_LOG_ROTATION_TIME = 'W4'
J. Richard Barnette3d977b82013-04-23 18:05:1989_LOG_ROTATION_BACKUP = 13
90
Frank Farzan40160872011-12-13 02:39:1891
Chris Sosa9164ca32012-03-28 18:04:5092class DevServerError(Exception):
Chris Sosa47a7d4e2012-03-28 18:26:5593 """Exception class used by this module."""
94 pass
95
96
Scott Zawalski4647ce62012-01-03 22:17:2897def _LeadingWhiteSpaceCount(string):
98 """Count the amount of leading whitespace in a string.
99
100 Args:
101 string: The string to count leading whitespace in.
102 Returns:
103 number of white space chars before characters start.
104 """
beepsbd337242013-07-10 05:44:06105 # pylint: disable=W1401
Scott Zawalski4647ce62012-01-03 22:17:28106 matched = re.match('^\s+', string)
107 if matched:
108 return len(matched.group())
109
110 return 0
111
112
113def _PrintDocStringAsHTML(func):
114 """Make a functions docstring somewhat HTML style.
115
116 Args:
117 func: The function to return the docstring from.
118 Returns:
119 A string that is somewhat formated for a web browser.
120 """
121 # TODO(scottz): Make this parse Args/Returns in a prettier way.
122 # Arguments could be bolded and indented etc.
123 html_doc = []
124 for line in func.__doc__.splitlines():
125 leading_space = _LeadingWhiteSpaceCount(line)
126 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 18:26:55127 line = '&nbsp;' * leading_space + line
Scott Zawalski4647ce62012-01-03 22:17:28128
129 html_doc.append('<BR>%s' % line)
130
131 return '\n'.join(html_doc)
132
133
Chris Sosa7c931362010-10-12 02:49:01134def _GetConfig(options):
135 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-08 01:55:33136
137 # On a system with IPv6 not compiled into the kernel,
138 # AF_INET6 sockets will return a socket.error exception.
139 # On such systems, fall-back to IPv4.
140 socket_host = '::'
141 try:
142 socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
143 except socket.error:
144 socket_host = '0.0.0.0'
145
Chris Sosa7c931362010-10-12 02:49:01146 base_config = { 'global':
147 { 'server.log_request_headers': True,
148 'server.protocol_version': 'HTTP/1.1',
Mandeep Singh Baines38dcdda2012-12-08 01:55:33149 'server.socket_host': socket_host,
Chris Sosa7c931362010-10-12 02:49:01150 'server.socket_port': int(options.port),
Chris Sosa374c62d2010-10-14 16:13:54151 'response.timeout': 6000,
Chris Sosa6fe23942012-07-02 22:44:46152 'request.show_tracebacks': True,
Chris Sosa72333d12012-06-13 18:28:05153 'server.socket_timeout': 60,
Chris Sosa7c931362010-10-12 02:49:01154 },
Dale Curtisc9aaf3a2011-08-09 22:47:40155 '/api':
156 {
157 # Gets rid of cherrypy parsing post file for args.
158 'request.process_request_body': False,
159 },
Chris Sosaa1ef0102010-10-21 23:22:35160 '/build':
161 {
162 'response.timeout': 100000,
163 },
Chris Sosa7c931362010-10-12 02:49:01164 '/update':
165 {
166 # Gets rid of cherrypy parsing post file for args.
167 'request.process_request_body': False,
Chris Sosaf65f4b92010-10-21 22:57:51168 'response.timeout': 10000,
Chris Sosa7c931362010-10-12 02:49:01169 },
170 # Sets up the static dir for file hosting.
171 '/static':
joychened64b222013-06-21 23:39:34172 { 'tools.staticdir.dir': options.static_dir,
Chris Sosa7c931362010-10-12 02:49:01173 'tools.staticdir.on': True,
Chris Sosaf65f4b92010-10-21 22:57:51174 'response.timeout': 10000,
Chris Sosa7c931362010-10-12 02:49:01175 },
176 }
Chris Sosa5f118ef2012-07-12 18:37:50177 if options.production:
Chris Sosad1ea86b2012-07-12 20:35:37178 base_config['global'].update({'server.thread_pool': 75})
Scott Zawalski1c5e7cd2012-02-27 18:12:52179
Chris Sosa7c931362010-10-12 02:49:01180 return base_config
[email protected]64244662009-11-12 00:52:08181
Darin Petkove17164a2010-08-11 20:24:41182
Gilad Arnoldd5ebaaa2012-10-02 18:52:38183def _GetRecursiveMemberObject(root, member_list):
184 """Returns an object corresponding to a nested member list.
185
186 Args:
187 root: the root object to search
188 member_list: list of nested members to search
189 Returns:
190 An object corresponding to the member name list; None otherwise.
191 """
192 for member in member_list:
193 next_root = root.__class__.__dict__.get(member)
194 if not next_root:
195 return None
196 root = next_root
197 return root
198
199
200def _IsExposed(name):
201 """Returns True iff |name| has an `exposed' attribute and it is set."""
202 return hasattr(name, 'exposed') and name.exposed
203
204
Gilad Arnold748c8322012-10-12 16:51:35205def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 18:52:38206 """Returns a CherryPy-exposed method, if such exists.
207
208 Args:
209 root: the root object for searching
210 nested_member: a slash-joined path to the nested member
211 ignored: method paths to be ignored
212 Returns:
213 A function object corresponding to the path defined by |member_list| from
214 the |root| object, if the function is exposed and not ignored; None
215 otherwise.
216 """
Gilad Arnold748c8322012-10-12 16:51:35217 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 18:52:38218 _GetRecursiveMemberObject(root, nested_member.split('/')))
219 if (method and type(method) == types.FunctionType and _IsExposed(method)):
220 return method
221
222
Gilad Arnold748c8322012-10-12 16:51:35223def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 18:52:38224 """Finds exposed CherryPy methods.
225
226 Args:
227 root: the root object for searching
228 prefix: slash-joined chain of members leading to current object
229 unlisted: URLs to be excluded regardless of their exposed status
230 Returns:
231 List of exposed URLs that are not unlisted.
232 """
233 method_list = []
234 for member in sorted(root.__class__.__dict__.keys()):
235 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 16:51:35236 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 18:52:38237 continue
238 member_obj = root.__class__.__dict__[member]
239 if _IsExposed(member_obj):
240 if type(member_obj) == types.FunctionType:
241 method_list.append(prefixed_member)
242 else:
243 method_list += _FindExposedMethods(
244 member_obj, prefixed_member, unlisted)
245 return method_list
246
247
Dale Curtisc9aaf3a2011-08-09 22:47:40248class ApiRoot(object):
249 """RESTful API for Dev Server information."""
250 exposed = True
251
252 @cherrypy.expose
253 def hostinfo(self, ip):
254 """Returns a JSON dictionary containing information about the given ip.
255
Gilad Arnold1b908392012-10-05 18:36:27256 Args:
257 ip: address of host whose info is requested
258 Returns:
259 A JSON dictionary containing all or some of the following fields:
260 last_event_type (int): last update event type received
261 last_event_status (int): last update event status received
262 last_known_version (string): last known version reported in update ping
263 forced_update_label (string): update label to force next update ping to
264 use, set by setnextupdate
265 See the OmahaEvent class in update_engine/omaha_request_action.h for
266 event type and status code definitions. If the ip does not exist an empty
267 string is returned.
Dale Curtisc9aaf3a2011-08-09 22:47:40268
Gilad Arnold1b908392012-10-05 18:36:27269 Example URL:
270 http://myhost/api/hostinfo?ip=192.168.1.5
271 """
Dale Curtisc9aaf3a2011-08-09 22:47:40272 return updater.HandleHostInfoPing(ip)
273
274 @cherrypy.expose
Gilad Arnold286a0062012-01-12 21:47:02275 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 18:36:27276 """Returns a JSON object containing a log of host event.
277
278 Args:
279 ip: address of host whose event log is requested, or `all'
280 Returns:
281 A JSON encoded list (log) of dictionaries (events), each of which
282 containing a `timestamp' and other event fields, as described under
283 /api/hostinfo.
284
285 Example URL:
286 http://myhost/api/hostlog?ip=192.168.1.5
287 """
Gilad Arnold286a0062012-01-12 21:47:02288 return updater.HandleHostLogPing(ip)
289
290 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 22:47:40291 def setnextupdate(self, ip):
292 """Allows the response to the next update ping from a host to be set.
293
294 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 18:36:27295 /update command.
296 """
Dale Curtisc9aaf3a2011-08-09 22:47:40297 body_length = int(cherrypy.request.headers['Content-Length'])
298 label = cherrypy.request.rfile.read(body_length)
299
300 if label:
301 label = label.strip()
302 if label:
303 return updater.HandleSetUpdatePing(ip, label)
304 raise cherrypy.HTTPError(400, 'No label provided.')
305
306
Gilad Arnold55a2a372012-10-02 16:46:32307 @cherrypy.expose
308 def fileinfo(self, *path_args):
309 """Returns information about a given staged file.
310
311 Args:
312 path_args: path to the file inside the server's static staging directory
313 Returns:
314 A JSON encoded dictionary with information about the said file, which may
315 contain the following keys/values:
Gilad Arnold1b908392012-10-05 18:36:27316 size (int): the file size in bytes
317 sha1 (string): a base64 encoded SHA1 hash
318 sha256 (string): a base64 encoded SHA256 hash
319
320 Example URL:
321 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 16:46:32322 """
323 file_path = os.path.join(updater.static_dir, *path_args)
324 if not os.path.exists(file_path):
325 raise DevServerError('file not found: %s' % file_path)
326 try:
327 file_size = os.path.getsize(file_path)
328 file_sha1 = common_util.GetFileSha1(file_path)
329 file_sha256 = common_util.GetFileSha256(file_path)
330 except os.error, e:
331 raise DevServerError('failed to get info for file %s: %s' %
Gilad Arnolde74b3812013-04-22 18:27:38332 (file_path, e))
333
334 is_delta = autoupdate.Autoupdate.IsDeltaFormatFile(file_path)
335
336 return json.dumps({
337 autoupdate.Autoupdate.SIZE_ATTR: file_size,
338 autoupdate.Autoupdate.SHA1_ATTR: file_sha1,
339 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
340 autoupdate.Autoupdate.ISDELTA_ATTR: is_delta
341 })
Gilad Arnold55a2a372012-10-02 16:46:32342
Chris Sosa76e44b92013-01-31 20:11:38343
David Rochberg7c79a812011-01-19 19:24:45344class DevServerRoot(object):
Chris Sosa7c931362010-10-12 02:49:01345 """The Root Class for the Dev Server.
346
347 CherryPy works as follows:
348 For each method in this class, cherrpy interprets root/path
349 as a call to an instance of DevServerRoot->method_name. For example,
350 a call to http://myhost/build will call build. CherryPy automatically
351 parses http args and places them as keyword arguments in each method.
352 For paths http://myhost/update/dir1/dir2, you can use *args so that
353 cherrypy uses the update method and puts the extra paths in args.
354 """
Gilad Arnoldf8f769f2012-09-24 15:43:01355 # Method names that should not be listed on the index page.
356 _UNLISTED_METHODS = ['index', 'doc']
357
Dale Curtisc9aaf3a2011-08-09 22:47:40358 api = ApiRoot()
Chris Sosa7c931362010-10-12 02:49:01359
Dan Shi59ae7092013-06-04 21:37:27360 # Number of threads that devserver is staging images.
361 _staging_thread_count = 0
362 # Lock used to lock increasing/decreasing count.
363 _staging_thread_count_lock = threading.Lock()
364
joychen3cb228e2013-06-12 19:13:13365 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 22:20:41366 self._builder = None
Simran Basi4baad082013-02-14 21:39:18367 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 19:13:13368 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 19:24:45369
Dale Curtisc9aaf3a2011-08-09 22:47:40370 @cherrypy.expose
David Rochberg7c79a812011-01-19 19:24:45371 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-12 02:49:01372 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 22:20:41373 import builder
374 if self._builder is None:
375 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 19:24:45376 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-12 02:49:01377
Chris Sosacde6bf42012-06-01 01:36:39378 @staticmethod
379 def _canonicalize_archive_url(archive_url):
380 """Canonicalizes archive_url strings.
381
382 Raises:
383 DevserverError: if archive_url is not set.
384 """
385 if archive_url:
Chris Sosa76e44b92013-01-31 20:11:38386 if not archive_url.startswith('gs://'):
387 raise DevServerError("Archive URL isn't from Google Storage.")
388
Chris Sosacde6bf42012-06-01 01:36:39389 return archive_url.rstrip('/')
390 else:
391 raise DevServerError("Must specify an archive_url in the request")
392
Dale Curtisc9aaf3a2011-08-09 22:47:40393 @cherrypy.expose
Frank Farzanbcb571e2012-01-03 19:48:17394 def download(self, **kwargs):
395 """Downloads and archives full/delta payloads from Google Storage.
396
Chris Sosa76e44b92013-01-31 20:11:38397 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosa47a7d4e2012-03-28 18:26:55398 This methods downloads artifacts. It may download artifacts in the
399 background in which case a caller should call wait_for_status to get
400 the status of the background artifact downloads. They should use the same
401 args passed to download.
402
Frank Farzanbcb571e2012-01-03 19:48:17403 Args:
404 archive_url: Google Storage URL for the build.
405
406 Example URL:
Gilad Arnoldf8f769f2012-09-24 15:43:01407 http://myhost/download?archive_url=gs://chromeos-image-archive/
408 x86-generic/R17-1208.0.0-a1-b338
Frank Farzanbcb571e2012-01-03 19:48:17409 """
Chris Sosa76e44b92013-01-31 20:11:38410 return self.stage(archive_url=kwargs.get('archive_url'),
411 artifacts='full_payload,test_suites,stateful')
412
Dan Shi59ae7092013-06-04 21:37:27413
Chris Sosa76e44b92013-01-31 20:11:38414 @cherrypy.expose
415 def stage(self, **kwargs):
416 """Downloads and caches the artifacts from Google Storage URL.
417
418 Downloads and caches the artifacts Google Storage URL. Returns once these
419 have been downloaded on the devserver. A call to this will attempt to cache
420 non-specified artifacts in the background for the given from the given URL
421 following the principle of spatial locality. Spatial locality of different
422 artifacts is explicitly defined in the build_artifact module.
423
424 These artifacts will then be available from the static/ sub-directory of
425 the devserver.
426
427 Args:
428 archive_url: Google Storage URL for the build.
429 artifacts: Comma separated list of artifacts to download.
430
431 Example:
432 To download the autotest and test suites tarballs:
433 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
434 artifacts=autotest,test_suites
435 To download the full update payload:
436 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
437 artifacts=full_payload
438
439 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 23:39:34440 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 20:11:38441
442 Note for this example, relative path is the archive_url stripped of its
443 basename i.e. path/ in the examples above. Specific example:
444
445 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
446
447 Will get staged to:
448
joychened64b222013-06-21 23:39:34449 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Chris Sosa76e44b92013-01-31 20:11:38450 """
Chris Sosacde6bf42012-06-01 01:36:39451 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Chris Sosa76e44b92013-01-31 20:11:38452 artifacts = kwargs.get('artifacts', '')
453 if not artifacts:
454 raise DevServerError('No artifacts specified.')
Chris Sosa47a7d4e2012-03-28 18:26:55455
Dan Shi59ae7092013-06-04 21:37:27456 with DevServerRoot._staging_thread_count_lock:
457 DevServerRoot._staging_thread_count += 1
458 try:
459 downloader.Downloader(updater.static_dir, archive_url).Download(
460 artifacts.split(','))
461 finally:
462 with DevServerRoot._staging_thread_count_lock:
463 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 20:11:38464 return 'Success'
Chris Sosacde6bf42012-06-01 01:36:39465
Dan Shi59ae7092013-06-04 21:37:27466
Chris Sosacde6bf42012-06-01 01:36:39467 @cherrypy.expose
Simran Basi4baad082013-02-14 21:39:18468 def setup_telemetry(self, **kwargs):
469 """Extracts and sets up telemetry
470
471 This method goes through the telemetry deps packages, and stages them on
472 the devserver to be used by the drones and the telemetry tests.
473
474 Args:
475 archive_url: Google Storage URL for the build.
476
477 Returns:
478 Path to the source folder for the telemetry codebase once it is staged.
479 """
480 archive_url = kwargs.get('archive_url')
481 self.stage(archive_url=archive_url, artifacts='autotest')
482
483 build = '/'.join(downloader.Downloader.ParseUrl(archive_url))
484 build_path = os.path.join(updater.static_dir, build)
485 deps_path = os.path.join(build_path, 'autotest/packages')
486 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
487 src_folder = os.path.join(telemetry_path, 'src')
488
489 with self._telemetry_lock_dict.lock(telemetry_path):
490 if os.path.exists(src_folder):
491 # Telemetry is already fully stage return
492 return src_folder
493
494 common_util.MkDirP(telemetry_path)
495
496 # Copy over the required deps tar balls to the telemetry directory.
497 for dep in TELEMETRY_DEPS:
498 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 23:40:04499 if not os.path.exists(dep_path):
500 # This dep does not exist (could be new), do not extract it.
501 continue
Simran Basi4baad082013-02-14 21:39:18502 try:
503 common_util.ExtractTarball(dep_path, telemetry_path)
504 except common_util.CommonUtilError as e:
505 shutil.rmtree(telemetry_path)
506 raise DevServerError(str(e))
507
508 # By default all the tarballs extract to test_src but some parts of
509 # the telemetry code specifically hardcoded to exist inside of 'src'.
510 test_src = os.path.join(telemetry_path, 'test_src')
511 try:
512 shutil.move(test_src, src_folder)
513 except shutil.Error:
514 # This can occur if src_folder already exists. Remove and retry move.
515 shutil.rmtree(src_folder)
516 raise DevServerError('Failure in telemetry setup for build %s. Appears'
517 ' that the test_src to src move failed.' % build)
518
519 return src_folder
520
521 @cherrypy.expose
Chris Sosacde6bf42012-06-01 01:36:39522 def wait_for_status(self, **kwargs):
523 """Waits for background artifacts to be downloaded from Google Storage.
524
Chris Sosa76e44b92013-01-31 20:11:38525 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosacde6bf42012-06-01 01:36:39526 Args:
527 archive_url: Google Storage URL for the build.
528
529 Example URL:
Gilad Arnoldf8f769f2012-09-24 15:43:01530 http://myhost/wait_for_status?archive_url=gs://chromeos-image-archive/
531 x86-generic/R17-1208.0.0-a1-b338
Chris Sosacde6bf42012-06-01 01:36:39532 """
Chris Sosa76e44b92013-01-31 20:11:38533 return self.stage(archive_url=kwargs.get('archive_url'),
534 artifacts='full_payload,test_suites,autotest,stateful')
Chris Sosa47a7d4e2012-03-28 18:26:55535
536 @cherrypy.expose
Chris Masone816e38c2012-05-02 19:22:36537 def stage_debug(self, **kwargs):
538 """Downloads and stages debug symbol payloads from Google Storage.
539
Chris Sosa76e44b92013-01-31 20:11:38540 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
541 This methods downloads the debug symbol build artifact
542 synchronously, and then stages it for use by symbolicate_dump.
Chris Masone816e38c2012-05-02 19:22:36543
544 Args:
545 archive_url: Google Storage URL for the build.
546
547 Example URL:
Gilad Arnoldf8f769f2012-09-24 15:43:01548 http://myhost/stage_debug?archive_url=gs://chromeos-image-archive/
549 x86-generic/R17-1208.0.0-a1-b338
Chris Masone816e38c2012-05-02 19:22:36550 """
Chris Sosa76e44b92013-01-31 20:11:38551 return self.stage(archive_url=kwargs.get('archive_url'),
552 artifacts='symbols')
Chris Masone816e38c2012-05-02 19:22:36553
554 @cherrypy.expose
Chris Sosa76e44b92013-01-31 20:11:38555 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 19:22:36556 """Symbolicates a minidump using pre-downloaded symbols, returns it.
557
558 Callers will need to POST to this URL with a body of MIME-type
559 "multipart/form-data".
560 The body should include a single argument, 'minidump', containing the
561 binary-formatted minidump to symbolicate.
562
Chris Masone816e38c2012-05-02 19:22:36563 Args:
Chris Sosa76e44b92013-01-31 20:11:38564 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 19:22:36565 minidump: The binary minidump file to symbolicate.
566 """
Chris Sosa76e44b92013-01-31 20:11:38567 # Ensure the symbols have been staged.
568 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
569 if self.stage(archive_url=archive_url, artifacts='symbols') != 'Success':
570 raise DevServerError('Failed to stage symbols for %s' % archive_url)
571
Chris Masone816e38c2012-05-02 19:22:36572 to_return = ''
573 with tempfile.NamedTemporaryFile() as local:
574 while True:
575 data = minidump.file.read(8192)
576 if not data:
577 break
578 local.write(data)
Chris Sosa76e44b92013-01-31 20:11:38579
Chris Masone816e38c2012-05-02 19:22:36580 local.flush()
Chris Sosa76e44b92013-01-31 20:11:38581
582 symbols_directory = os.path.join(downloader.Downloader.GetBuildDir(
583 updater.static_dir, archive_url), 'debug', 'breakpad')
584
585 stackwalk = subprocess.Popen(
586 ['minidump_stackwalk', local.name, symbols_directory],
587 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
588
Chris Masone816e38c2012-05-02 19:22:36589 to_return, error_text = stackwalk.communicate()
590 if stackwalk.returncode != 0:
591 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
592 error_text, stackwalk.returncode))
593
594 return to_return
595
596 @cherrypy.expose
Scott Zawalski16954532012-03-20 19:31:36597 def latestbuild(self, **params):
598 """Return a string representing the latest build for a given target.
599
600 Args:
601 target: The build target, typically a combination of the board and the
602 type of build e.g. x86-mario-release.
603 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
604 provided the latest RXX build will be returned.
605 Returns:
606 A string representation of the latest build if one exists, i.e.
607 R19-1993.0.0-a1-b1480.
608 An empty string if no latest could be found.
609 """
610 if not params:
611 return _PrintDocStringAsHTML(self.latestbuild)
612
613 if 'target' not in params:
614 raise cherrypy.HTTPError('500 Internal Server Error',
615 'Error: target= is required!')
616 try:
Gilad Arnoldc65330c2012-09-20 22:17:48617 return common_util.GetLatestBuildVersion(
Scott Zawalski16954532012-03-20 19:31:36618 updater.static_dir, params['target'],
619 milestone=params.get('milestone'))
Gilad Arnold17fe03d2012-10-02 17:05:01620 except common_util.CommonUtilError as errmsg:
Scott Zawalski16954532012-03-20 19:31:36621 raise cherrypy.HTTPError('500 Internal Server Error', str(errmsg))
622
623 @cherrypy.expose
Scott Zawalski84a39c92012-01-13 20:12:42624 def controlfiles(self, **params):
Scott Zawalski4647ce62012-01-03 22:17:28625 """Return a control file or a list of all known control files.
626
627 Example URL:
628 To List all control files:
beepsbd337242013-07-10 05:44:06629 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
630 To List all control files for, say, the bvt suite:
631 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 22:17:28632 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 20:12:42633 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0&control_path=client/sleeptest/control
Scott Zawalski4647ce62012-01-03 22:17:28634
635 Args:
Scott Zawalski84a39c92012-01-13 20:12:42636 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 22:17:28637 control_path: If you want the contents of a control file set this
638 to the path. E.g. client/site_tests/sleeptest/control
639 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-10 05:44:06640 suite_name: If control_path is not specified but a suite_name is
641 specified, list the control files belonging to that suite instead of
642 all control files. The empty string for suite_name will list all control
643 files for the build.
Scott Zawalski4647ce62012-01-03 22:17:28644 Returns:
645 Contents of a control file if control_path is provided.
646 A list of control files if no control_path is provided.
647 """
Scott Zawalski4647ce62012-01-03 22:17:28648 if not params:
649 return _PrintDocStringAsHTML(self.controlfiles)
650
Scott Zawalski84a39c92012-01-13 20:12:42651 if 'build' not in params:
Scott Zawalski4647ce62012-01-03 22:17:28652 raise cherrypy.HTTPError('500 Internal Server Error',
Scott Zawalski84a39c92012-01-13 20:12:42653 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 22:17:28654
655 if 'control_path' not in params:
beepsbd337242013-07-10 05:44:06656 if 'suite_name' in params and params['suite_name']:
657 return common_util.GetControlFileListForSuite(
658 updater.static_dir, params['build'], params['suite_name'])
659 else:
660 return common_util.GetControlFileList(
661 updater.static_dir, params['build'])
Scott Zawalski4647ce62012-01-03 22:17:28662 else:
Gilad Arnoldc65330c2012-09-20 22:17:48663 return common_util.GetControlFile(
664 updater.static_dir, params['build'], params['control_path'])
Frank Farzan40160872011-12-13 02:39:18665
666 @cherrypy.expose
Gilad Arnold6f99b982012-09-12 17:49:40667 def stage_images(self, **kwargs):
668 """Downloads and stages a Chrome OS image from Google Storage.
669
Chris Sosa76e44b92013-01-31 20:11:38670 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Gilad Arnold6f99b982012-09-12 17:49:40671 This method downloads a zipped archive from a specified GS location, then
672 extracts and stages the specified list of images and stages them under
Chris Sosa76e44b92013-01-31 20:11:38673 static/BOARD/BUILD/. Download is synchronous.
Gilad Arnold6f99b982012-09-12 17:49:40674
675 Args:
676 archive_url: Google Storage URL for the build.
677 image_types: comma-separated list of images to download, may include
678 'test', 'recovery', and 'base'
679
680 Example URL:
681 http://myhost/stage_images?archive_url=gs://chromeos-image-archive/
682 x86-generic/R17-1208.0.0-a1-b338&image_types=test,base
683 """
Gilad Arnold6f99b982012-09-12 17:49:40684 image_types = kwargs.get('image_types').split(',')
Chris Sosa76e44b92013-01-31 20:11:38685 image_types_list = [image + '_image' for image in image_types]
686 self.stage(archive_url=kwargs.get('archive_url'), artifacts=','.join(
687 image_types_list))
Gilad Arnold6f99b982012-09-12 17:49:40688
689 @cherrypy.expose
joycheneaf4cfc2013-07-02 15:38:57690 def xbuddy(self, *args, **kwargs):
691 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 19:13:13692
693 Args:
joycheneaf4cfc2013-07-02 15:38:57694 path_parts: the path following xbuddy/ in the call url is split into the
695 components of the path.
696 The path can be understood as a build_id/artifact, build_id is
697 composed of "board/version"
698
699 path_parts[0], the board, is the familiar board name, optionally
700 suffixed.
701 path_parts[1], the version, can be the google storage version
702 number, and may also be any of a number of xBuddy defined version
703 aliases that will be translated into the latest built image that
704 fits the description. defaults to latest.
705 path_parts[2], the artifact, is one of a number of image or artifact
706 aliases used by xbuddy, defined in xbuddy:ALIASES. Defaults to test
707
708 Kwargs:
joychen3cb228e2013-06-12 19:13:13709 return_dir: {true|false}
710 if set to true, returns the url to the update.gz
711 instead.
712
713 Example URL:
joycheneaf4cfc2013-07-02 15:38:57714 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 19:13:13715 or
joycheneaf4cfc2013-07-02 15:38:57716 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 19:13:13717
718 Returns:
719 A redirect to the image or update file on the devserver.
720 e.g. http://host:port/static/archive/x86-generic-release/
721 R26-4000.0.0/chromium-test-image.bin
722 or if return_dir is True, return path to the folder where
723 image or update file is
724 http://host:port/static/x86-generic-release/R26-4000.0.0/
725 """
726 boolean_string = kwargs.get('return_dir')
727 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
joycheneaf4cfc2013-07-02 15:38:57728 return_url = self._xbuddy.Get(args,
joychen3cb228e2013-06-12 19:13:13729 return_dir)
730 if return_dir:
joycheneaf4cfc2013-07-02 15:38:57731 directory = os.path.join(cherrypy.request.base, return_url)
732 _Log("Directory requested, returning: %s", directory)
733 return directory
joychen3cb228e2013-06-12 19:13:13734 else:
joycheneaf4cfc2013-07-02 15:38:57735 return_url = '/' + return_url
736 _Log("Payload requested, returning: %s", return_url)
joychen3cb228e2013-06-12 19:13:13737 raise cherrypy.HTTPRedirect(return_url, 302)
738
739 @cherrypy.expose
740 def xbuddy_list(self):
741 """Lists the currently available images & time since last access.
742
743 @return: A string representation of a list of tuples
744 [(build_id, time since last access),...]
745 """
746 return self._xbuddy.List()
747
748 @cherrypy.expose
749 def xbuddy_capacity(self):
750 """Returns the number of images cached by xBuddy.
751
752 @return: Capacity of this devserver.
753 """
754 return self._xbuddy.Capacity()
755
756 @cherrypy.expose
Chris Sosa7c931362010-10-12 02:49:01757 def index(self):
Gilad Arnoldf8f769f2012-09-24 15:43:01758 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 15:43:01759 return ('Welcome to the Dev Server!<br>\n'
760 '<br>\n'
761 'Here are the available methods, click for documentation:<br>\n'
762 '<br>\n'
763 '%s' %
764 '<br>\n'.join(
765 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 18:52:38766 for name in _FindExposedMethods(
767 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 15:43:01768
769 @cherrypy.expose
770 def doc(self, *args):
771 """Shows the documentation for available methods / URLs.
772
773 Example:
774 http://myhost/doc/update
775 """
Gilad Arnoldd5ebaaa2012-10-02 18:52:38776 name = '/'.join(args)
777 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 15:43:01778 if not method:
779 raise DevServerError("No exposed method named `%s'" % name)
780 if not method.__doc__:
781 raise DevServerError("No documentation for exposed method `%s'" % name)
782 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-12 02:49:01783
Dale Curtisc9aaf3a2011-08-09 22:47:40784 @cherrypy.expose
Chris Sosa7c931362010-10-12 02:49:01785 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 15:43:01786 """Handles an update check from a Chrome OS client.
787
788 The HTTP request should contain the standard Omaha-style XML blob. The URL
789 line may contain an additional intermediate path to the update payload.
790
791 Example:
792 http://myhost/update/optional/path/to/payload
793 """
Chris Sosa7c931362010-10-12 02:49:01794 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 21:47:02795 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-12 02:49:01796 data = cherrypy.request.rfile.read(body_length)
797 return updater.HandleUpdatePing(data, label)
798
Chris Sosa0356d3b2010-09-16 22:46:22799
Dan Shif5ce2de2013-04-25 23:06:32800 @cherrypy.expose
801 def check_health(self):
802 """Collect the health status of devserver to see if it's ready for staging.
803
804 @return: A JSON dictionary containing all or some of the following fields:
Dan Shi59ae7092013-06-04 21:37:27805 free_disk (int): free disk space in GB
806 staging_thread_count (int): number of devserver threads currently
807 staging an image
Dan Shif5ce2de2013-04-25 23:06:32808 """
809 # Get free disk space.
810 stat = os.statvfs(updater.static_dir)
811 free_disk = stat.f_bsize * stat.f_bavail / 1000000000
812
813 return json.dumps({
814 'free_disk': free_disk,
Dan Shi59ae7092013-06-04 21:37:27815 'staging_thread_count': DevServerRoot._staging_thread_count,
Dan Shif5ce2de2013-04-25 23:06:32816 })
817
818
Chris Sosadbc20082012-12-10 21:39:11819def _CleanCache(cache_dir, wipe):
820 """Wipes any excess cached items in the cache_dir.
821
822 Args:
823 cache_dir: the directory we are wiping from.
824 wipe: If True, wipe all the contents -- not just the excess.
825 """
826 if wipe:
827 # Clear the cache and exit on error.
828 cmd = 'rm -rf %s/*' % cache_dir
829 if os.system(cmd) != 0:
830 _Log('Failed to clear the cache with %s' % cmd)
831 sys.exit(1)
832 else:
833 # Clear all but the last N cached updates
834 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
835 (cache_dir, CACHED_ENTRIES))
836 if os.system(cmd) != 0:
837 _Log('Failed to clean up old delta cache files with %s' % cmd)
838 sys.exit(1)
839
840
Chris Sosa3ae4dc12013-03-29 18:47:00841def _AddTestingOptions(parser):
842 group = optparse.OptionGroup(
843 parser, 'Advanced Testing Options', 'These are used by test scripts and '
844 'developers writing integration tests utilizing the devserver. They are '
845 'not intended to be really used outside the scope of someone '
846 'knowledgable about the test.')
847 group.add_option('--exit',
848 action='store_true',
849 help='do not start the server (yet pregenerate/clear cache)')
850 group.add_option('--host_log',
851 action='store_true', default=False,
852 help='record history of host update events (/api/hostlog)')
853 group.add_option('--max_updates',
854 metavar='NUM', default= -1, type='int',
855 help='maximum number of update checks handled positively '
856 '(default: unlimited)')
857 group.add_option('--private_key',
858 metavar='PATH', default=None,
859 help='path to the private key in pem format. If this is set '
860 'the devserver will generate update payloads that are '
861 'signed with this key.')
862 group.add_option('--proxy_port',
863 metavar='PORT', default=None, type='int',
864 help='port to have the client connect to -- basically the '
865 'devserver lies to the update to tell it to get the payload '
866 'from a different port that will proxy the request back to '
867 'the devserver. The proxy must be managed outside the '
868 'devserver.')
869 group.add_option('--remote_payload',
870 action='store_true', default=False,
871 help='Payload is being served from a remote machine')
872 group.add_option('-u', '--urlbase',
873 metavar='URL',
874 help='base URL for update images, other than the '
875 'devserver. Use in conjunction with remote_payload.')
876 parser.add_option_group(group)
877
878
879def _AddUpdateOptions(parser):
880 group = optparse.OptionGroup(
881 parser, 'Autoupdate Options', 'These options can be used to change '
882 'how the devserver either generates or serve update payloads. Please '
883 'note that all of these option affect how a payload is generated and so '
884 'do not work in archive-only mode.')
885 group.add_option('--board',
886 help='By default the devserver will create an update '
887 'payload from the latest image built for the board '
888 'a device that is requesting an update has. When we '
889 'pre-generate an update (see below) and we do not specify '
890 'another update_type option like image or payload, the '
891 'devserver needs to know the board to generate the latest '
892 'image for. This is that board.')
893 group.add_option('--critical_update',
894 action='store_true', default=False,
895 help='Present update payload as critical')
896 group.add_option('--for_vm',
897 dest='vm', action='store_true',
898 help='DEPRECATED: see no_patch_kernel.')
899 group.add_option('--image',
900 metavar='FILE',
901 help='Generate and serve an update using this image to any '
902 'device that requests an update.')
903 group.add_option('--no_patch_kernel',
904 dest='patch_kernel', action='store_false', default=True,
905 help='When generating an update payload, do not patch the '
906 'kernel with kernel verification blob from the stateful '
907 'partition.')
908 group.add_option('--payload',
909 metavar='PATH',
910 help='use the update payload from specified directory '
911 '(update.gz).')
912 group.add_option('-p', '--pregenerate_update',
913 action='store_true', default=False,
914 help='pre-generate the update payload before accepting '
915 'update requests. Useful to help debug payload generation '
916 'issues quickly. Also if an update payload will take a '
917 'long time to generate, a client may timeout if you do not'
918 'pregenerate the update.')
919 group.add_option('--src_image',
920 metavar='PATH', default='',
921 help='If specified, delta updates will be generated using '
922 'this image as the source image. Delta updates are when '
923 'you are updating from a "source image" to a another '
924 'image.')
925 parser.add_option_group(group)
926
927
928def _AddProductionOptions(parser):
929 group = optparse.OptionGroup(
930 parser, 'Advanced Server Options', 'These options can be used to changed '
931 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 18:47:00932 group.add_option('--archive_dir',
933 metavar='PATH',
joychened64b222013-06-21 23:39:34934 help='To be deprecated.')
Chris Sosa3ae4dc12013-03-29 18:47:00935 group.add_option('--clear_cache',
936 action='store_true', default=False,
937 help='At startup, removes all cached entries from the'
938 'devserver\'s cache.')
939 group.add_option('--logfile',
940 metavar='PATH',
941 help='log output to this file instead of stdout')
942 group.add_option('--production',
943 action='store_true', default=False,
944 help='have the devserver use production values when '
945 'starting up. This includes using more threads and '
946 'performing less logging.')
947 parser.add_option_group(group)
948
949
J. Richard Barnette3d977b82013-04-23 18:05:19950def _MakeLogHandler(logfile):
951 """Create a LogHandler instance used to log all messages."""
952 hdlr_cls = handlers.TimedRotatingFileHandler
953 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
954 backupCount=_LOG_ROTATION_BACKUP)
955 # The cherrypy documentation says to use the _cplogging module for
956 # this, even though it's named as a private module.
957 # pylint: disable=W0212
958 hdlr.setFormatter(cherrypy._cplogging.logfmt)
959 return hdlr
960
961
Chris Sosacde6bf42012-06-01 01:36:39962def main():
Chris Sosa3ae4dc12013-03-29 18:47:00963 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 21:47:02964 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 23:39:34965
966 # get directory that the devserver is run from
967 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
968 default_archive_dir = '%s/static' % devserver_dir
969 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 17:09:42970 metavar='PATH',
joychened64b222013-06-21 23:39:34971 default=default_archive_dir,
972 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 17:09:42973 parser.add_option('--port',
974 default=8080, type='int',
975 help='port for the dev server to use (default: 8080)')
Gilad Arnold9714d9b2012-10-04 17:09:42976 parser.add_option('-t', '--test_image',
977 action='store_true',
Chris Sosa3ae4dc12013-03-29 18:47:00978 help='If set, look for the chromiumos_test_image.bin file '
979 'when generating update payloads rather than the '
980 'chromiumos_image.bin which is the default.')
joychen5260b9a2013-07-16 21:48:01981 parser.add_option('-x', '--xbuddy_manage_builds',
982 action='store_true',
983 default=False,
984 help='If set, allow xbuddy to manage images in'
985 'build/images.')
Chris Sosa3ae4dc12013-03-29 18:47:00986 _AddProductionOptions(parser)
987 _AddUpdateOptions(parser)
988 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-12 02:49:01989 (options, _) = parser.parse_args()
[email protected]21a5ca32009-11-04 18:23:23990
J. Richard Barnette3d977b82013-04-23 18:05:19991 # Handle options that must be set globally in cherrypy. Do this
992 # work up front, because calls to _Log() below depend on this
993 # initialization.
994 if options.production:
995 cherrypy.config.update({'environment': 'production'})
996 if not options.logfile:
997 cherrypy.config.update({'log.screen': True})
998 else:
999 cherrypy.config.update({'log.error_file': '',
1000 'log.access_file': ''})
1001 hdlr = _MakeLogHandler(options.logfile)
1002 # Pylint can't seem to process these two calls properly
1003 # pylint: disable=E1101
1004 cherrypy.log.access_log.addHandler(hdlr)
1005 cherrypy.log.error_log.addHandler(hdlr)
1006 # pylint: enable=E1101
1007
Chris Sosa7c931362010-10-12 02:49:011008 root_dir = os.path.realpath('%s/../..' % devserver_dir)
Chris Sosa0356d3b2010-09-16 22:46:221009 serve_only = False
1010
J. Richard Barnette3d977b82013-04-23 18:05:191011 # TODO(sosa): Remove after deprecation.
Chris Sosa3ae4dc12013-03-29 18:47:001012 if options.vm:
1013 options.patch_kernel = False
1014
joychened64b222013-06-21 23:39:341015 # set static_dir, from which everything will be served
Sean O'Connor14b6a0a2010-03-21 06:23:481016 if options.archive_dir:
joychened64b222013-06-21 23:39:341017 # TODO(joyc) To be deprecated
Zdenek Behan608f46c2011-02-18 23:47:161018 archive_dir = options.archive_dir
1019 if not os.path.isabs(archive_dir):
joychened64b222013-06-21 23:39:341020 archive_dir = os.path.join(devserver_dir, archive_dir)
1021 options.static_dir = os.path.realpath(archive_dir)
Chris Sosa0356d3b2010-09-16 22:46:221022 serve_only = True
joychened64b222013-06-21 23:39:341023 else:
1024 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 22:46:221025
joychened64b222013-06-21 23:39:341026 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 18:05:191027 # If our devserver is only supposed to serve payloads, we shouldn't be
1028 # mucking with the cache at all. If the devserver hadn't previously
1029 # generated a cache and is expected, the caller is using it wrong.
Chris Sosadbc20082012-12-10 21:39:111030 if serve_only:
1031 # Extra check to make sure we're not being called incorrectly.
1032 if (options.clear_cache or options.exit or options.pregenerate_update or
1033 options.board or options.image):
1034 parser.error('Incompatible flags detected for serve_only mode.')
Chris Sosadbc20082012-12-10 21:39:111035 elif os.path.exists(cache_dir):
1036 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 20:12:171037 else:
1038 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-17 01:36:141039
Chris Sosadbc20082012-12-10 21:39:111040 _Log('Using cache directory %s' % cache_dir)
Gilad Arnoldc65330c2012-09-20 22:17:481041 _Log('Source root is %s' % root_dir)
joychened64b222013-06-21 23:39:341042 _Log('Serving from %s' % options.static_dir)
[email protected]21a5ca32009-11-04 18:23:231043
Chris Sosa6a3697f2013-01-30 00:44:431044 # We allow global use here to share with cherrypy classes.
1045 # pylint: disable=W0603
Chris Sosacde6bf42012-06-01 01:36:391046 global updater
Andrew de los Reyes52620802010-04-12 20:40:071047 updater = autoupdate.Autoupdate(
1048 root_dir=root_dir,
joychened64b222013-06-21 23:39:341049 static_dir=options.static_dir,
Chris Sosa0356d3b2010-09-16 22:46:221050 serve_only=serve_only,
Andrew de los Reyes52620802010-04-12 20:40:071051 urlbase=options.urlbase,
1052 test_image=options.test_image,
Chris Sosa5d342a22010-09-28 23:54:411053 forced_image=options.image,
Gilad Arnold0c9c8602012-10-03 06:58:581054 payload_path=options.payload,
Don Garrett0ad09372010-12-07 00:20:301055 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-29 06:42:371056 src_image=options.src_image,
Chris Sosa3ae4dc12013-03-29 18:47:001057 patch_kernel=options.patch_kernel,
Chris Sosa08d55a22011-01-20 00:08:021058 board=options.board,
Chris Sosa0f1ec842011-02-15 00:33:221059 copy_to_static_root=not options.exit,
1060 private_key=options.private_key,
Satoru Takabayashid733cbe2011-11-15 17:36:321061 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-03 06:58:581062 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 17:32:441063 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 19:52:231064 host_log=options.host_log,
Chris Sosa0f1ec842011-02-15 00:33:221065 )
Chris Sosa7c931362010-10-12 02:49:011066
Chris Sosa6a3697f2013-01-30 00:44:431067 if options.pregenerate_update:
1068 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 22:46:221069
J. Richard Barnette3d977b82013-04-23 18:05:191070 if options.exit:
1071 return
Chris Sosa2f1c41e2012-07-10 21:32:331072
joychen5260b9a2013-07-16 21:48:011073 _xbuddy = xbuddy.XBuddy(options.xbuddy_manage_builds,
1074 root_dir=root_dir,
1075 static_dir=options.static_dir)
joychen3cb228e2013-06-12 19:13:131076 dev_server = DevServerRoot(_xbuddy)
1077
1078 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-06-01 01:36:391079
1080
1081if __name__ == '__main__':
1082 main()