blob: 1badcde99c2d7ebff3e6cbf0fb7adfba4705b005 [file] [log] [blame]
Chris Sosa7c931362010-10-11 19:49:01 -07001#!/usr/bin/python
2
Chris Sosa781ba6d2012-04-11 12:44:43 -07003# Copyright (c) 2009-2012 The Chromium OS Authors. All rights reserved.
rtc@google.comded22402009-10-26 22:36:21 +00004# 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 11:47:00 -07007"""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-11 19:49:01 -070043
Chris Sosadbc20082012-12-10 13:39:11 -080044import cherrypy
Gilad Arnold55a2a372012-10-02 09:46:32 -070045import json
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070046import optparse
rtc@google.comded22402009-10-26 22:36:21 +000047import os
Scott Zawalski4647ce62012-01-03 17:17:28 -050048import re
Simran Basi4baad082013-02-14 13:39:18 -080049import shutil
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -080050import socket
chocobo@google.com4dc25812009-10-27 23:46:26 +000051import sys
Chris Masone816e38c2012-05-02 12:22:36 -070052import subprocess
53import tempfile
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070054import types
rtc@google.comded22402009-10-26 22:36:21 +000055
Chris Sosa0356d3b2010-09-16 15:46:22 -070056import autoupdate
Gilad Arnoldc65330c2012-09-20 15:17:48 -070057import common_util
Chris Sosa47a7d4e2012-03-28 11:26:55 -070058import downloader
Gilad Arnoldc65330c2012-09-20 15:17:48 -070059import log_util
60
61
62# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080063def _Log(message, *args):
64 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 15:46:22 -070065
Frank Farzan40160872011-12-12 18:39:18 -080066
Chris Sosa417e55d2011-01-25 16:40:48 -080067CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080068
Simran Basi4baad082013-02-14 13:39:18 -080069TELEMETRY_FOLDER = 'telemetry_src'
70TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
71 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070072 'dep-chrome_test.tar.bz2',
73 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -080074
Chris Sosa0356d3b2010-09-16 15:46:22 -070075# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +000076updater = None
rtc@google.comded22402009-10-26 22:36:21 +000077
Frank Farzan40160872011-12-12 18:39:18 -080078
Chris Sosa9164ca32012-03-28 11:04:50 -070079class DevServerError(Exception):
Chris Sosa47a7d4e2012-03-28 11:26:55 -070080 """Exception class used by this module."""
81 pass
82
83
Scott Zawalski4647ce62012-01-03 17:17:28 -050084def _LeadingWhiteSpaceCount(string):
85 """Count the amount of leading whitespace in a string.
86
87 Args:
88 string: The string to count leading whitespace in.
89 Returns:
90 number of white space chars before characters start.
91 """
92 matched = re.match('^\s+', string)
93 if matched:
94 return len(matched.group())
95
96 return 0
97
98
99def _PrintDocStringAsHTML(func):
100 """Make a functions docstring somewhat HTML style.
101
102 Args:
103 func: The function to return the docstring from.
104 Returns:
105 A string that is somewhat formated for a web browser.
106 """
107 # TODO(scottz): Make this parse Args/Returns in a prettier way.
108 # Arguments could be bolded and indented etc.
109 html_doc = []
110 for line in func.__doc__.splitlines():
111 leading_space = _LeadingWhiteSpaceCount(line)
112 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700113 line = '&nbsp;' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500114
115 html_doc.append('<BR>%s' % line)
116
117 return '\n'.join(html_doc)
118
119
Chris Sosa7c931362010-10-11 19:49:01 -0700120def _GetConfig(options):
121 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800122
123 # On a system with IPv6 not compiled into the kernel,
124 # AF_INET6 sockets will return a socket.error exception.
125 # On such systems, fall-back to IPv4.
126 socket_host = '::'
127 try:
128 socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
129 except socket.error:
130 socket_host = '0.0.0.0'
131
Chris Sosa7c931362010-10-11 19:49:01 -0700132 base_config = { 'global':
133 { 'server.log_request_headers': True,
134 'server.protocol_version': 'HTTP/1.1',
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800135 'server.socket_host': socket_host,
Chris Sosa7c931362010-10-11 19:49:01 -0700136 'server.socket_port': int(options.port),
Chris Sosa374c62d2010-10-14 09:13:54 -0700137 'response.timeout': 6000,
Chris Sosa6fe23942012-07-02 15:44:46 -0700138 'request.show_tracebacks': True,
Chris Sosa72333d12012-06-13 11:28:05 -0700139 'server.socket_timeout': 60,
Zdenek Behan1347a312011-02-10 03:59:17 +0100140 'tools.staticdir.root':
141 os.path.dirname(os.path.abspath(sys.argv[0])),
Chris Sosa7c931362010-10-11 19:49:01 -0700142 },
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700143 '/api':
144 {
145 # Gets rid of cherrypy parsing post file for args.
146 'request.process_request_body': False,
147 },
Chris Sosaa1ef0102010-10-21 16:22:35 -0700148 '/build':
149 {
150 'response.timeout': 100000,
151 },
Chris Sosa7c931362010-10-11 19:49:01 -0700152 '/update':
153 {
154 # Gets rid of cherrypy parsing post file for args.
155 'request.process_request_body': False,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700156 'response.timeout': 10000,
Chris Sosa7c931362010-10-11 19:49:01 -0700157 },
158 # Sets up the static dir for file hosting.
159 '/static':
160 { 'tools.staticdir.dir': 'static',
161 'tools.staticdir.on': True,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700162 'response.timeout': 10000,
Chris Sosa7c931362010-10-11 19:49:01 -0700163 },
164 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700165 if options.production:
Chris Sosad1ea86b2012-07-12 13:35:37 -0700166 base_config['global'].update({'server.thread_pool': 75})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500167
Chris Sosa7c931362010-10-11 19:49:01 -0700168 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000169
Darin Petkove17164a2010-08-11 13:24:41 -0700170
Zdenek Behan608f46c2011-02-19 00:47:16 +0100171def _PrepareToServeUpdatesOnly(image_dir, static_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700172 """Sets up symlink to image_dir for serving purposes."""
173 assert os.path.exists(image_dir), '%s must exist.' % image_dir
174 # If we're serving out of an archived build dir (e.g. a
175 # buildbot), prepare this webserver's magic 'static/' dir with a
176 # link to the build archive.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700177 _Log('Preparing autoupdate for "serve updates only" mode.')
Zdenek Behan608f46c2011-02-19 00:47:16 +0100178 if os.path.lexists('%s/archive' % static_dir):
179 if image_dir != os.readlink('%s/archive' % static_dir):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700180 _Log('removing stale symlink to %s' % image_dir)
Zdenek Behan608f46c2011-02-19 00:47:16 +0100181 os.unlink('%s/archive' % static_dir)
182 os.symlink(image_dir, '%s/archive' % static_dir)
Chris Sosacde6bf42012-05-31 18:36:39 -0700183
Chris Sosa0356d3b2010-09-16 15:46:22 -0700184 else:
Zdenek Behan608f46c2011-02-19 00:47:16 +0100185 os.symlink(image_dir, '%s/archive' % static_dir)
Chris Sosacde6bf42012-05-31 18:36:39 -0700186
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700187 _Log('archive dir: %s ready to be used to serve images.' % image_dir)
Chris Sosa7c931362010-10-11 19:49:01 -0700188
189
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700190def _GetRecursiveMemberObject(root, member_list):
191 """Returns an object corresponding to a nested member list.
192
193 Args:
194 root: the root object to search
195 member_list: list of nested members to search
196 Returns:
197 An object corresponding to the member name list; None otherwise.
198 """
199 for member in member_list:
200 next_root = root.__class__.__dict__.get(member)
201 if not next_root:
202 return None
203 root = next_root
204 return root
205
206
207def _IsExposed(name):
208 """Returns True iff |name| has an `exposed' attribute and it is set."""
209 return hasattr(name, 'exposed') and name.exposed
210
211
Gilad Arnold748c8322012-10-12 09:51:35 -0700212def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700213 """Returns a CherryPy-exposed method, if such exists.
214
215 Args:
216 root: the root object for searching
217 nested_member: a slash-joined path to the nested member
218 ignored: method paths to be ignored
219 Returns:
220 A function object corresponding to the path defined by |member_list| from
221 the |root| object, if the function is exposed and not ignored; None
222 otherwise.
223 """
Gilad Arnold748c8322012-10-12 09:51:35 -0700224 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700225 _GetRecursiveMemberObject(root, nested_member.split('/')))
226 if (method and type(method) == types.FunctionType and _IsExposed(method)):
227 return method
228
229
Gilad Arnold748c8322012-10-12 09:51:35 -0700230def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700231 """Finds exposed CherryPy methods.
232
233 Args:
234 root: the root object for searching
235 prefix: slash-joined chain of members leading to current object
236 unlisted: URLs to be excluded regardless of their exposed status
237 Returns:
238 List of exposed URLs that are not unlisted.
239 """
240 method_list = []
241 for member in sorted(root.__class__.__dict__.keys()):
242 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700243 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700244 continue
245 member_obj = root.__class__.__dict__[member]
246 if _IsExposed(member_obj):
247 if type(member_obj) == types.FunctionType:
248 method_list.append(prefixed_member)
249 else:
250 method_list += _FindExposedMethods(
251 member_obj, prefixed_member, unlisted)
252 return method_list
253
254
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700255class ApiRoot(object):
256 """RESTful API for Dev Server information."""
257 exposed = True
258
259 @cherrypy.expose
260 def hostinfo(self, ip):
261 """Returns a JSON dictionary containing information about the given ip.
262
Gilad Arnold1b908392012-10-05 11:36:27 -0700263 Args:
264 ip: address of host whose info is requested
265 Returns:
266 A JSON dictionary containing all or some of the following fields:
267 last_event_type (int): last update event type received
268 last_event_status (int): last update event status received
269 last_known_version (string): last known version reported in update ping
270 forced_update_label (string): update label to force next update ping to
271 use, set by setnextupdate
272 See the OmahaEvent class in update_engine/omaha_request_action.h for
273 event type and status code definitions. If the ip does not exist an empty
274 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700275
Gilad Arnold1b908392012-10-05 11:36:27 -0700276 Example URL:
277 http://myhost/api/hostinfo?ip=192.168.1.5
278 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700279 return updater.HandleHostInfoPing(ip)
280
281 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800282 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700283 """Returns a JSON object containing a log of host event.
284
285 Args:
286 ip: address of host whose event log is requested, or `all'
287 Returns:
288 A JSON encoded list (log) of dictionaries (events), each of which
289 containing a `timestamp' and other event fields, as described under
290 /api/hostinfo.
291
292 Example URL:
293 http://myhost/api/hostlog?ip=192.168.1.5
294 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800295 return updater.HandleHostLogPing(ip)
296
297 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700298 def setnextupdate(self, ip):
299 """Allows the response to the next update ping from a host to be set.
300
301 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 11:36:27 -0700302 /update command.
303 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700304 body_length = int(cherrypy.request.headers['Content-Length'])
305 label = cherrypy.request.rfile.read(body_length)
306
307 if label:
308 label = label.strip()
309 if label:
310 return updater.HandleSetUpdatePing(ip, label)
311 raise cherrypy.HTTPError(400, 'No label provided.')
312
313
Gilad Arnold55a2a372012-10-02 09:46:32 -0700314 @cherrypy.expose
315 def fileinfo(self, *path_args):
316 """Returns information about a given staged file.
317
318 Args:
319 path_args: path to the file inside the server's static staging directory
320 Returns:
321 A JSON encoded dictionary with information about the said file, which may
322 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700323 size (int): the file size in bytes
324 sha1 (string): a base64 encoded SHA1 hash
325 sha256 (string): a base64 encoded SHA256 hash
326
327 Example URL:
328 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700329 """
330 file_path = os.path.join(updater.static_dir, *path_args)
331 if not os.path.exists(file_path):
332 raise DevServerError('file not found: %s' % file_path)
333 try:
334 file_size = os.path.getsize(file_path)
335 file_sha1 = common_util.GetFileSha1(file_path)
336 file_sha256 = common_util.GetFileSha256(file_path)
337 except os.error, e:
338 raise DevServerError('failed to get info for file %s: %s' %
Simran Basi40836b22013-03-28 15:08:17 -0700339 (file_path, str(e)))
340 return json.dumps(
341 {'size': file_size, 'sha1': file_sha1, 'sha256': file_sha256})
Gilad Arnold55a2a372012-10-02 09:46:32 -0700342
Chris Sosa76e44b92013-01-31 12:11:38 -0800343
David Rochberg7c79a812011-01-19 14:24:45 -0500344class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700345 """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 08:43:01 -0700355 # Method names that should not be listed on the index page.
356 _UNLISTED_METHODS = ['index', 'doc']
357
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700358 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700359
David Rochberg7c79a812011-01-19 14:24:45 -0500360 def __init__(self):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700361 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800362 self._telemetry_lock_dict = common_util.LockDict()
David Rochberg7c79a812011-01-19 14:24:45 -0500363
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700364 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500365 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700366 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700367 import builder
368 if self._builder is None:
369 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500370 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700371
Chris Sosacde6bf42012-05-31 18:36:39 -0700372 @staticmethod
373 def _canonicalize_archive_url(archive_url):
374 """Canonicalizes archive_url strings.
375
376 Raises:
377 DevserverError: if archive_url is not set.
378 """
379 if archive_url:
Chris Sosa76e44b92013-01-31 12:11:38 -0800380 if not archive_url.startswith('gs://'):
381 raise DevServerError("Archive URL isn't from Google Storage.")
382
Chris Sosacde6bf42012-05-31 18:36:39 -0700383 return archive_url.rstrip('/')
384 else:
385 raise DevServerError("Must specify an archive_url in the request")
386
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700387 @cherrypy.expose
Frank Farzanbcb571e2012-01-03 11:48:17 -0800388 def download(self, **kwargs):
389 """Downloads and archives full/delta payloads from Google Storage.
390
Chris Sosa76e44b92013-01-31 12:11:38 -0800391 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700392 This methods downloads artifacts. It may download artifacts in the
393 background in which case a caller should call wait_for_status to get
394 the status of the background artifact downloads. They should use the same
395 args passed to download.
396
Frank Farzanbcb571e2012-01-03 11:48:17 -0800397 Args:
398 archive_url: Google Storage URL for the build.
399
400 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700401 http://myhost/download?archive_url=gs://chromeos-image-archive/
402 x86-generic/R17-1208.0.0-a1-b338
Frank Farzanbcb571e2012-01-03 11:48:17 -0800403 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800404 return self.stage(archive_url=kwargs.get('archive_url'),
405 artifacts='full_payload,test_suites,stateful')
406
407 @cherrypy.expose
408 def stage(self, **kwargs):
409 """Downloads and caches the artifacts from Google Storage URL.
410
411 Downloads and caches the artifacts Google Storage URL. Returns once these
412 have been downloaded on the devserver. A call to this will attempt to cache
413 non-specified artifacts in the background for the given from the given URL
414 following the principle of spatial locality. Spatial locality of different
415 artifacts is explicitly defined in the build_artifact module.
416
417 These artifacts will then be available from the static/ sub-directory of
418 the devserver.
419
420 Args:
421 archive_url: Google Storage URL for the build.
422 artifacts: Comma separated list of artifacts to download.
423
424 Example:
425 To download the autotest and test suites tarballs:
426 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
427 artifacts=autotest,test_suites
428 To download the full update payload:
429 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
430 artifacts=full_payload
431
432 For both these examples, one could find these artifacts at:
433 http://devserver_url:<port>/static/archive/<relative_path>*
434
435 Note for this example, relative path is the archive_url stripped of its
436 basename i.e. path/ in the examples above. Specific example:
437
438 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
439
440 Will get staged to:
441
442 http://devserver_url:<port>/static/archive/x86-mario-release/R26-3920.0.0
443 """
Chris Sosacde6bf42012-05-31 18:36:39 -0700444 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Chris Sosa76e44b92013-01-31 12:11:38 -0800445 artifacts = kwargs.get('artifacts', '')
446 if not artifacts:
447 raise DevServerError('No artifacts specified.')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700448
Chris Sosa76e44b92013-01-31 12:11:38 -0800449 downloader.Downloader(updater.static_dir, archive_url).Download(
450 artifacts.split(','))
451 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700452
453 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800454 def setup_telemetry(self, **kwargs):
455 """Extracts and sets up telemetry
456
457 This method goes through the telemetry deps packages, and stages them on
458 the devserver to be used by the drones and the telemetry tests.
459
460 Args:
461 archive_url: Google Storage URL for the build.
462
463 Returns:
464 Path to the source folder for the telemetry codebase once it is staged.
465 """
466 archive_url = kwargs.get('archive_url')
467 self.stage(archive_url=archive_url, artifacts='autotest')
468
469 build = '/'.join(downloader.Downloader.ParseUrl(archive_url))
470 build_path = os.path.join(updater.static_dir, build)
471 deps_path = os.path.join(build_path, 'autotest/packages')
472 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
473 src_folder = os.path.join(telemetry_path, 'src')
474
475 with self._telemetry_lock_dict.lock(telemetry_path):
476 if os.path.exists(src_folder):
477 # Telemetry is already fully stage return
478 return src_folder
479
480 common_util.MkDirP(telemetry_path)
481
482 # Copy over the required deps tar balls to the telemetry directory.
483 for dep in TELEMETRY_DEPS:
484 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700485 if not os.path.exists(dep_path):
486 # This dep does not exist (could be new), do not extract it.
487 continue
Simran Basi4baad082013-02-14 13:39:18 -0800488 try:
489 common_util.ExtractTarball(dep_path, telemetry_path)
490 except common_util.CommonUtilError as e:
491 shutil.rmtree(telemetry_path)
492 raise DevServerError(str(e))
493
494 # By default all the tarballs extract to test_src but some parts of
495 # the telemetry code specifically hardcoded to exist inside of 'src'.
496 test_src = os.path.join(telemetry_path, 'test_src')
497 try:
498 shutil.move(test_src, src_folder)
499 except shutil.Error:
500 # This can occur if src_folder already exists. Remove and retry move.
501 shutil.rmtree(src_folder)
502 raise DevServerError('Failure in telemetry setup for build %s. Appears'
503 ' that the test_src to src move failed.' % build)
504
505 return src_folder
506
507 @cherrypy.expose
Chris Sosacde6bf42012-05-31 18:36:39 -0700508 def wait_for_status(self, **kwargs):
509 """Waits for background artifacts to be downloaded from Google Storage.
510
Chris Sosa76e44b92013-01-31 12:11:38 -0800511 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosacde6bf42012-05-31 18:36:39 -0700512 Args:
513 archive_url: Google Storage URL for the build.
514
515 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700516 http://myhost/wait_for_status?archive_url=gs://chromeos-image-archive/
517 x86-generic/R17-1208.0.0-a1-b338
Chris Sosacde6bf42012-05-31 18:36:39 -0700518 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800519 return self.stage(archive_url=kwargs.get('archive_url'),
520 artifacts='full_payload,test_suites,autotest,stateful')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700521
522 @cherrypy.expose
Chris Masone816e38c2012-05-02 12:22:36 -0700523 def stage_debug(self, **kwargs):
524 """Downloads and stages debug symbol payloads from Google Storage.
525
Chris Sosa76e44b92013-01-31 12:11:38 -0800526 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
527 This methods downloads the debug symbol build artifact
528 synchronously, and then stages it for use by symbolicate_dump.
Chris Masone816e38c2012-05-02 12:22:36 -0700529
530 Args:
531 archive_url: Google Storage URL for the build.
532
533 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700534 http://myhost/stage_debug?archive_url=gs://chromeos-image-archive/
535 x86-generic/R17-1208.0.0-a1-b338
Chris Masone816e38c2012-05-02 12:22:36 -0700536 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800537 return self.stage(archive_url=kwargs.get('archive_url'),
538 artifacts='symbols')
Chris Masone816e38c2012-05-02 12:22:36 -0700539
540 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800541 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700542 """Symbolicates a minidump using pre-downloaded symbols, returns it.
543
544 Callers will need to POST to this URL with a body of MIME-type
545 "multipart/form-data".
546 The body should include a single argument, 'minidump', containing the
547 binary-formatted minidump to symbolicate.
548
Chris Masone816e38c2012-05-02 12:22:36 -0700549 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800550 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700551 minidump: The binary minidump file to symbolicate.
552 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800553 # Ensure the symbols have been staged.
554 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
555 if self.stage(archive_url=archive_url, artifacts='symbols') != 'Success':
556 raise DevServerError('Failed to stage symbols for %s' % archive_url)
557
Chris Masone816e38c2012-05-02 12:22:36 -0700558 to_return = ''
559 with tempfile.NamedTemporaryFile() as local:
560 while True:
561 data = minidump.file.read(8192)
562 if not data:
563 break
564 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800565
Chris Masone816e38c2012-05-02 12:22:36 -0700566 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800567
568 symbols_directory = os.path.join(downloader.Downloader.GetBuildDir(
569 updater.static_dir, archive_url), 'debug', 'breakpad')
570
571 stackwalk = subprocess.Popen(
572 ['minidump_stackwalk', local.name, symbols_directory],
573 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
574
Chris Masone816e38c2012-05-02 12:22:36 -0700575 to_return, error_text = stackwalk.communicate()
576 if stackwalk.returncode != 0:
577 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
578 error_text, stackwalk.returncode))
579
580 return to_return
581
582 @cherrypy.expose
Scott Zawalski16954532012-03-20 15:31:36 -0400583 def latestbuild(self, **params):
584 """Return a string representing the latest build for a given target.
585
586 Args:
587 target: The build target, typically a combination of the board and the
588 type of build e.g. x86-mario-release.
589 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
590 provided the latest RXX build will be returned.
591 Returns:
592 A string representation of the latest build if one exists, i.e.
593 R19-1993.0.0-a1-b1480.
594 An empty string if no latest could be found.
595 """
596 if not params:
597 return _PrintDocStringAsHTML(self.latestbuild)
598
599 if 'target' not in params:
600 raise cherrypy.HTTPError('500 Internal Server Error',
601 'Error: target= is required!')
602 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700603 return common_util.GetLatestBuildVersion(
Scott Zawalski16954532012-03-20 15:31:36 -0400604 updater.static_dir, params['target'],
605 milestone=params.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700606 except common_util.CommonUtilError as errmsg:
Scott Zawalski16954532012-03-20 15:31:36 -0400607 raise cherrypy.HTTPError('500 Internal Server Error', str(errmsg))
608
609 @cherrypy.expose
Scott Zawalski84a39c92012-01-13 15:12:42 -0500610 def controlfiles(self, **params):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500611 """Return a control file or a list of all known control files.
612
613 Example URL:
614 To List all control files:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500615 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500616 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500617 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0&control_path=client/sleeptest/control
Scott Zawalski4647ce62012-01-03 17:17:28 -0500618
619 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500620 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500621 control_path: If you want the contents of a control file set this
622 to the path. E.g. client/site_tests/sleeptest/control
623 Optional, if not provided return a list of control files is returned.
624 Returns:
625 Contents of a control file if control_path is provided.
626 A list of control files if no control_path is provided.
627 """
Scott Zawalski4647ce62012-01-03 17:17:28 -0500628 if not params:
629 return _PrintDocStringAsHTML(self.controlfiles)
630
Scott Zawalski84a39c92012-01-13 15:12:42 -0500631 if 'build' not in params:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500632 raise cherrypy.HTTPError('500 Internal Server Error',
Scott Zawalski84a39c92012-01-13 15:12:42 -0500633 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500634
635 if 'control_path' not in params:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700636 return common_util.GetControlFileList(
637 updater.static_dir, params['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500638 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700639 return common_util.GetControlFile(
640 updater.static_dir, params['build'], params['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800641
642 @cherrypy.expose
Gilad Arnold6f99b982012-09-12 10:49:40 -0700643 def stage_images(self, **kwargs):
644 """Downloads and stages a Chrome OS image from Google Storage.
645
Chris Sosa76e44b92013-01-31 12:11:38 -0800646 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700647 This method downloads a zipped archive from a specified GS location, then
648 extracts and stages the specified list of images and stages them under
Chris Sosa76e44b92013-01-31 12:11:38 -0800649 static/BOARD/BUILD/. Download is synchronous.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700650
651 Args:
652 archive_url: Google Storage URL for the build.
653 image_types: comma-separated list of images to download, may include
654 'test', 'recovery', and 'base'
655
656 Example URL:
657 http://myhost/stage_images?archive_url=gs://chromeos-image-archive/
658 x86-generic/R17-1208.0.0-a1-b338&image_types=test,base
659 """
Gilad Arnold6f99b982012-09-12 10:49:40 -0700660 image_types = kwargs.get('image_types').split(',')
Chris Sosa76e44b92013-01-31 12:11:38 -0800661 image_types_list = [image + '_image' for image in image_types]
662 self.stage(archive_url=kwargs.get('archive_url'), artifacts=','.join(
663 image_types_list))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700664
665 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700666 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700667 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700668 return ('Welcome to the Dev Server!<br>\n'
669 '<br>\n'
670 'Here are the available methods, click for documentation:<br>\n'
671 '<br>\n'
672 '%s' %
673 '<br>\n'.join(
674 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700675 for name in _FindExposedMethods(
676 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700677
678 @cherrypy.expose
679 def doc(self, *args):
680 """Shows the documentation for available methods / URLs.
681
682 Example:
683 http://myhost/doc/update
684 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700685 name = '/'.join(args)
686 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700687 if not method:
688 raise DevServerError("No exposed method named `%s'" % name)
689 if not method.__doc__:
690 raise DevServerError("No documentation for exposed method `%s'" % name)
691 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -0700692
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700693 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700694 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700695 """Handles an update check from a Chrome OS client.
696
697 The HTTP request should contain the standard Omaha-style XML blob. The URL
698 line may contain an additional intermediate path to the update payload.
699
700 Example:
701 http://myhost/update/optional/path/to/payload
702 """
Chris Sosa7c931362010-10-11 19:49:01 -0700703 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -0800704 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -0700705 data = cherrypy.request.rfile.read(body_length)
706 return updater.HandleUpdatePing(data, label)
707
Chris Sosa0356d3b2010-09-16 15:46:22 -0700708
Chris Sosadbc20082012-12-10 13:39:11 -0800709def _CleanCache(cache_dir, wipe):
710 """Wipes any excess cached items in the cache_dir.
711
712 Args:
713 cache_dir: the directory we are wiping from.
714 wipe: If True, wipe all the contents -- not just the excess.
715 """
716 if wipe:
717 # Clear the cache and exit on error.
718 cmd = 'rm -rf %s/*' % cache_dir
719 if os.system(cmd) != 0:
720 _Log('Failed to clear the cache with %s' % cmd)
721 sys.exit(1)
722 else:
723 # Clear all but the last N cached updates
724 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
725 (cache_dir, CACHED_ENTRIES))
726 if os.system(cmd) != 0:
727 _Log('Failed to clean up old delta cache files with %s' % cmd)
728 sys.exit(1)
729
730
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700731def _AddTestingOptions(parser):
732 group = optparse.OptionGroup(
733 parser, 'Advanced Testing Options', 'These are used by test scripts and '
734 'developers writing integration tests utilizing the devserver. They are '
735 'not intended to be really used outside the scope of someone '
736 'knowledgable about the test.')
737 group.add_option('--exit',
738 action='store_true',
739 help='do not start the server (yet pregenerate/clear cache)')
740 group.add_option('--host_log',
741 action='store_true', default=False,
742 help='record history of host update events (/api/hostlog)')
743 group.add_option('--max_updates',
744 metavar='NUM', default= -1, type='int',
745 help='maximum number of update checks handled positively '
746 '(default: unlimited)')
747 group.add_option('--private_key',
748 metavar='PATH', default=None,
749 help='path to the private key in pem format. If this is set '
750 'the devserver will generate update payloads that are '
751 'signed with this key.')
752 group.add_option('--proxy_port',
753 metavar='PORT', default=None, type='int',
754 help='port to have the client connect to -- basically the '
755 'devserver lies to the update to tell it to get the payload '
756 'from a different port that will proxy the request back to '
757 'the devserver. The proxy must be managed outside the '
758 'devserver.')
759 group.add_option('--remote_payload',
760 action='store_true', default=False,
761 help='Payload is being served from a remote machine')
762 group.add_option('-u', '--urlbase',
763 metavar='URL',
764 help='base URL for update images, other than the '
765 'devserver. Use in conjunction with remote_payload.')
766 parser.add_option_group(group)
767
768
769def _AddUpdateOptions(parser):
770 group = optparse.OptionGroup(
771 parser, 'Autoupdate Options', 'These options can be used to change '
772 'how the devserver either generates or serve update payloads. Please '
773 'note that all of these option affect how a payload is generated and so '
774 'do not work in archive-only mode.')
775 group.add_option('--board',
776 help='By default the devserver will create an update '
777 'payload from the latest image built for the board '
778 'a device that is requesting an update has. When we '
779 'pre-generate an update (see below) and we do not specify '
780 'another update_type option like image or payload, the '
781 'devserver needs to know the board to generate the latest '
782 'image for. This is that board.')
783 group.add_option('--critical_update',
784 action='store_true', default=False,
785 help='Present update payload as critical')
786 group.add_option('--for_vm',
787 dest='vm', action='store_true',
788 help='DEPRECATED: see no_patch_kernel.')
789 group.add_option('--image',
790 metavar='FILE',
791 help='Generate and serve an update using this image to any '
792 'device that requests an update.')
793 group.add_option('--no_patch_kernel',
794 dest='patch_kernel', action='store_false', default=True,
795 help='When generating an update payload, do not patch the '
796 'kernel with kernel verification blob from the stateful '
797 'partition.')
798 group.add_option('--payload',
799 metavar='PATH',
800 help='use the update payload from specified directory '
801 '(update.gz).')
802 group.add_option('-p', '--pregenerate_update',
803 action='store_true', default=False,
804 help='pre-generate the update payload before accepting '
805 'update requests. Useful to help debug payload generation '
806 'issues quickly. Also if an update payload will take a '
807 'long time to generate, a client may timeout if you do not'
808 'pregenerate the update.')
809 group.add_option('--src_image',
810 metavar='PATH', default='',
811 help='If specified, delta updates will be generated using '
812 'this image as the source image. Delta updates are when '
813 'you are updating from a "source image" to a another '
814 'image.')
815 parser.add_option_group(group)
816
817
818def _AddProductionOptions(parser):
819 group = optparse.OptionGroup(
820 parser, 'Advanced Server Options', 'These options can be used to changed '
821 'for advanced server behavior.')
822 # TODO(sosa): Clean up the fact we have archive_dir and data_dir. It's ugly.
823 # Should be --archive_mode + optional data_dir.
824 group.add_option('--archive_dir',
825 metavar='PATH',
826 help='Enables archive-only mode. This disables any '
827 'update or package generation related functionality. This '
828 'mode also works without a Chromium OS chroot.')
829 group.add_option('--clear_cache',
830 action='store_true', default=False,
831 help='At startup, removes all cached entries from the'
832 'devserver\'s cache.')
833 group.add_option('--logfile',
834 metavar='PATH',
835 help='log output to this file instead of stdout')
836 group.add_option('--production',
837 action='store_true', default=False,
838 help='have the devserver use production values when '
839 'starting up. This includes using more threads and '
840 'performing less logging.')
841 parser.add_option_group(group)
842
843
Chris Sosacde6bf42012-05-31 18:36:39 -0700844def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700845 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -0800846 parser = optparse.OptionParser(usage=usage)
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700847 parser.add_option('--data_dir',
848 metavar='PATH',
849 default=os.path.dirname(os.path.abspath(sys.argv[0])),
850 help='writable directory where static lives')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700851 parser.add_option('--port',
852 default=8080, type='int',
853 help='port for the dev server to use (default: 8080)')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700854 parser.add_option('-t', '--test_image',
855 action='store_true',
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700856 help='If set, look for the chromiumos_test_image.bin file '
857 'when generating update payloads rather than the '
858 'chromiumos_image.bin which is the default.')
859 _AddProductionOptions(parser)
860 _AddUpdateOptions(parser)
861 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -0700862 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +0000863
Chris Sosa7c931362010-10-11 19:49:01 -0700864 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
865 root_dir = os.path.realpath('%s/../..' % devserver_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700866 serve_only = False
867
Zdenek Behan608f46c2011-02-19 00:47:16 +0100868 static_dir = os.path.realpath('%s/static' % options.data_dir)
869 os.system('mkdir -p %s' % static_dir)
870
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700871 # TODO(sosa): Remove after depcrecation.
872 if options.vm:
873 options.patch_kernel = False
874
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700875 if options.archive_dir:
Zdenek Behan608f46c2011-02-19 00:47:16 +0100876 # TODO(zbehan) Remove legacy support:
877 # archive_dir is the directory where static/archive will point.
878 # If this is an absolute path, all is fine. If someone calls this
879 # using a relative path, that is relative to src/platform/dev/.
880 # That use case is unmaintainable, but since applications use it
881 # with =./static, instead of a boolean flag, we'll make this relative
882 # to devserver_dir to keep these unbroken. For now.
883 archive_dir = options.archive_dir
884 if not os.path.isabs(archive_dir):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700885 archive_dir = os.path.realpath(os.path.join(devserver_dir, archive_dir))
Zdenek Behan608f46c2011-02-19 00:47:16 +0100886 _PrepareToServeUpdatesOnly(archive_dir, static_dir)
Zdenek Behan6d93e552011-03-02 22:35:49 +0100887 static_dir = os.path.realpath(archive_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700888 serve_only = True
Chris Sosa0356d3b2010-09-16 15:46:22 -0700889
Don Garrettf90edf02010-11-16 17:36:14 -0800890 cache_dir = os.path.join(static_dir, 'cache')
Chris Sosadbc20082012-12-10 13:39:11 -0800891 # If our devserver is only supposed to serve payloads, we shouldn't be mucking
892 # with the cache at all. If the devserver hadn't previously generated a cache
893 # and is expected, the caller is using it wrong.
894 if serve_only:
895 # Extra check to make sure we're not being called incorrectly.
896 if (options.clear_cache or options.exit or options.pregenerate_update or
897 options.board or options.image):
898 parser.error('Incompatible flags detected for serve_only mode.')
Don Garrettf90edf02010-11-16 17:36:14 -0800899
Chris Sosadbc20082012-12-10 13:39:11 -0800900 elif os.path.exists(cache_dir):
901 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -0800902 else:
903 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800904
Chris Sosadbc20082012-12-10 13:39:11 -0800905 _Log('Using cache directory %s' % cache_dir)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700906 _Log('Data dir is %s' % options.data_dir)
907 _Log('Source root is %s' % root_dir)
908 _Log('Serving from %s' % static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +0000909
Chris Sosa6a3697f2013-01-29 16:44:43 -0800910 # We allow global use here to share with cherrypy classes.
911 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -0700912 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -0700913 updater = autoupdate.Autoupdate(
914 root_dir=root_dir,
915 static_dir=static_dir,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700916 serve_only=serve_only,
Andrew de los Reyes52620802010-04-12 13:40:07 -0700917 urlbase=options.urlbase,
918 test_image=options.test_image,
Chris Sosa5d342a22010-09-28 16:54:41 -0700919 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700920 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -0800921 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -0700922 src_image=options.src_image,
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700923 patch_kernel=options.patch_kernel,
Chris Sosa08d55a22011-01-19 16:08:02 -0800924 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800925 copy_to_static_root=not options.exit,
926 private_key=options.private_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800927 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700928 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700929 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -0700930 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800931 )
Chris Sosa7c931362010-10-11 19:49:01 -0700932
Chris Sosa6a3697f2013-01-29 16:44:43 -0800933 if options.pregenerate_update:
934 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700935
Don Garrett0c880e22010-11-17 18:13:37 -0800936 # If the command line requested after setup, it's time to do it.
937 if not options.exit:
Chris Sosa66e2d9c2012-07-11 14:14:14 -0700938 # Handle options that must be set globally in cherrypy.
Chris Sosa2f1c41e2012-07-10 14:32:33 -0700939 if options.production:
Chris Sosa66e2d9c2012-07-11 14:14:14 -0700940 cherrypy.config.update({'environment': 'production'})
941 if not options.logfile:
942 cherrypy.config.update({'log.screen': True})
943 else:
944 cherrypy.config.update({'log.error_file': options.logfile,
945 'log.access_file': options.logfile})
Chris Sosa2f1c41e2012-07-10 14:32:33 -0700946
Don Garrett0c880e22010-11-17 18:13:37 -0800947 cherrypy.quickstart(DevServerRoot(), config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -0700948
949
950if __name__ == '__main__':
951 main()