blob: ec74816f9485a46bc27c9f9e7c3df7bff2792c4c [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' %
Gilad Arnolde74b3812013-04-22 11:27:38 -0700339 (file_path, e))
340
341 is_delta = autoupdate.Autoupdate.IsDeltaFormatFile(file_path)
342
343 return json.dumps({
344 autoupdate.Autoupdate.SIZE_ATTR: file_size,
345 autoupdate.Autoupdate.SHA1_ATTR: file_sha1,
346 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
347 autoupdate.Autoupdate.ISDELTA_ATTR: is_delta
348 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700349
Chris Sosa76e44b92013-01-31 12:11:38 -0800350
David Rochberg7c79a812011-01-19 14:24:45 -0500351class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700352 """The Root Class for the Dev Server.
353
354 CherryPy works as follows:
355 For each method in this class, cherrpy interprets root/path
356 as a call to an instance of DevServerRoot->method_name. For example,
357 a call to http://myhost/build will call build. CherryPy automatically
358 parses http args and places them as keyword arguments in each method.
359 For paths http://myhost/update/dir1/dir2, you can use *args so that
360 cherrypy uses the update method and puts the extra paths in args.
361 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700362 # Method names that should not be listed on the index page.
363 _UNLISTED_METHODS = ['index', 'doc']
364
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700365 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700366
David Rochberg7c79a812011-01-19 14:24:45 -0500367 def __init__(self):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700368 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800369 self._telemetry_lock_dict = common_util.LockDict()
David Rochberg7c79a812011-01-19 14:24:45 -0500370
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700371 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500372 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700373 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700374 import builder
375 if self._builder is None:
376 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500377 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700378
Chris Sosacde6bf42012-05-31 18:36:39 -0700379 @staticmethod
380 def _canonicalize_archive_url(archive_url):
381 """Canonicalizes archive_url strings.
382
383 Raises:
384 DevserverError: if archive_url is not set.
385 """
386 if archive_url:
Chris Sosa76e44b92013-01-31 12:11:38 -0800387 if not archive_url.startswith('gs://'):
388 raise DevServerError("Archive URL isn't from Google Storage.")
389
Chris Sosacde6bf42012-05-31 18:36:39 -0700390 return archive_url.rstrip('/')
391 else:
392 raise DevServerError("Must specify an archive_url in the request")
393
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700394 @cherrypy.expose
Frank Farzanbcb571e2012-01-03 11:48:17 -0800395 def download(self, **kwargs):
396 """Downloads and archives full/delta payloads from Google Storage.
397
Chris Sosa76e44b92013-01-31 12:11:38 -0800398 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700399 This methods downloads artifacts. It may download artifacts in the
400 background in which case a caller should call wait_for_status to get
401 the status of the background artifact downloads. They should use the same
402 args passed to download.
403
Frank Farzanbcb571e2012-01-03 11:48:17 -0800404 Args:
405 archive_url: Google Storage URL for the build.
406
407 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700408 http://myhost/download?archive_url=gs://chromeos-image-archive/
409 x86-generic/R17-1208.0.0-a1-b338
Frank Farzanbcb571e2012-01-03 11:48:17 -0800410 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800411 return self.stage(archive_url=kwargs.get('archive_url'),
412 artifacts='full_payload,test_suites,stateful')
413
414 @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:
440 http://devserver_url:<port>/static/archive/<relative_path>*
441
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
449 http://devserver_url:<port>/static/archive/x86-mario-release/R26-3920.0.0
450 """
Chris Sosacde6bf42012-05-31 18:36:39 -0700451 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Chris Sosa76e44b92013-01-31 12:11:38 -0800452 artifacts = kwargs.get('artifacts', '')
453 if not artifacts:
454 raise DevServerError('No artifacts specified.')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700455
Chris Sosa76e44b92013-01-31 12:11:38 -0800456 downloader.Downloader(updater.static_dir, archive_url).Download(
457 artifacts.split(','))
458 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700459
460 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800461 def setup_telemetry(self, **kwargs):
462 """Extracts and sets up telemetry
463
464 This method goes through the telemetry deps packages, and stages them on
465 the devserver to be used by the drones and the telemetry tests.
466
467 Args:
468 archive_url: Google Storage URL for the build.
469
470 Returns:
471 Path to the source folder for the telemetry codebase once it is staged.
472 """
473 archive_url = kwargs.get('archive_url')
474 self.stage(archive_url=archive_url, artifacts='autotest')
475
476 build = '/'.join(downloader.Downloader.ParseUrl(archive_url))
477 build_path = os.path.join(updater.static_dir, build)
478 deps_path = os.path.join(build_path, 'autotest/packages')
479 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
480 src_folder = os.path.join(telemetry_path, 'src')
481
482 with self._telemetry_lock_dict.lock(telemetry_path):
483 if os.path.exists(src_folder):
484 # Telemetry is already fully stage return
485 return src_folder
486
487 common_util.MkDirP(telemetry_path)
488
489 # Copy over the required deps tar balls to the telemetry directory.
490 for dep in TELEMETRY_DEPS:
491 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700492 if not os.path.exists(dep_path):
493 # This dep does not exist (could be new), do not extract it.
494 continue
Simran Basi4baad082013-02-14 13:39:18 -0800495 try:
496 common_util.ExtractTarball(dep_path, telemetry_path)
497 except common_util.CommonUtilError as e:
498 shutil.rmtree(telemetry_path)
499 raise DevServerError(str(e))
500
501 # By default all the tarballs extract to test_src but some parts of
502 # the telemetry code specifically hardcoded to exist inside of 'src'.
503 test_src = os.path.join(telemetry_path, 'test_src')
504 try:
505 shutil.move(test_src, src_folder)
506 except shutil.Error:
507 # This can occur if src_folder already exists. Remove and retry move.
508 shutil.rmtree(src_folder)
509 raise DevServerError('Failure in telemetry setup for build %s. Appears'
510 ' that the test_src to src move failed.' % build)
511
512 return src_folder
513
514 @cherrypy.expose
Chris Sosacde6bf42012-05-31 18:36:39 -0700515 def wait_for_status(self, **kwargs):
516 """Waits for background artifacts to be downloaded from Google Storage.
517
Chris Sosa76e44b92013-01-31 12:11:38 -0800518 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosacde6bf42012-05-31 18:36:39 -0700519 Args:
520 archive_url: Google Storage URL for the build.
521
522 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700523 http://myhost/wait_for_status?archive_url=gs://chromeos-image-archive/
524 x86-generic/R17-1208.0.0-a1-b338
Chris Sosacde6bf42012-05-31 18:36:39 -0700525 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800526 return self.stage(archive_url=kwargs.get('archive_url'),
527 artifacts='full_payload,test_suites,autotest,stateful')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700528
529 @cherrypy.expose
Chris Masone816e38c2012-05-02 12:22:36 -0700530 def stage_debug(self, **kwargs):
531 """Downloads and stages debug symbol payloads from Google Storage.
532
Chris Sosa76e44b92013-01-31 12:11:38 -0800533 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
534 This methods downloads the debug symbol build artifact
535 synchronously, and then stages it for use by symbolicate_dump.
Chris Masone816e38c2012-05-02 12:22:36 -0700536
537 Args:
538 archive_url: Google Storage URL for the build.
539
540 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700541 http://myhost/stage_debug?archive_url=gs://chromeos-image-archive/
542 x86-generic/R17-1208.0.0-a1-b338
Chris Masone816e38c2012-05-02 12:22:36 -0700543 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800544 return self.stage(archive_url=kwargs.get('archive_url'),
545 artifacts='symbols')
Chris Masone816e38c2012-05-02 12:22:36 -0700546
547 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800548 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700549 """Symbolicates a minidump using pre-downloaded symbols, returns it.
550
551 Callers will need to POST to this URL with a body of MIME-type
552 "multipart/form-data".
553 The body should include a single argument, 'minidump', containing the
554 binary-formatted minidump to symbolicate.
555
Chris Masone816e38c2012-05-02 12:22:36 -0700556 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800557 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700558 minidump: The binary minidump file to symbolicate.
559 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800560 # Ensure the symbols have been staged.
561 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
562 if self.stage(archive_url=archive_url, artifacts='symbols') != 'Success':
563 raise DevServerError('Failed to stage symbols for %s' % archive_url)
564
Chris Masone816e38c2012-05-02 12:22:36 -0700565 to_return = ''
566 with tempfile.NamedTemporaryFile() as local:
567 while True:
568 data = minidump.file.read(8192)
569 if not data:
570 break
571 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800572
Chris Masone816e38c2012-05-02 12:22:36 -0700573 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800574
575 symbols_directory = os.path.join(downloader.Downloader.GetBuildDir(
576 updater.static_dir, archive_url), 'debug', 'breakpad')
577
578 stackwalk = subprocess.Popen(
579 ['minidump_stackwalk', local.name, symbols_directory],
580 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
581
Chris Masone816e38c2012-05-02 12:22:36 -0700582 to_return, error_text = stackwalk.communicate()
583 if stackwalk.returncode != 0:
584 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
585 error_text, stackwalk.returncode))
586
587 return to_return
588
589 @cherrypy.expose
Scott Zawalski16954532012-03-20 15:31:36 -0400590 def latestbuild(self, **params):
591 """Return a string representing the latest build for a given target.
592
593 Args:
594 target: The build target, typically a combination of the board and the
595 type of build e.g. x86-mario-release.
596 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
597 provided the latest RXX build will be returned.
598 Returns:
599 A string representation of the latest build if one exists, i.e.
600 R19-1993.0.0-a1-b1480.
601 An empty string if no latest could be found.
602 """
603 if not params:
604 return _PrintDocStringAsHTML(self.latestbuild)
605
606 if 'target' not in params:
607 raise cherrypy.HTTPError('500 Internal Server Error',
608 'Error: target= is required!')
609 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700610 return common_util.GetLatestBuildVersion(
Scott Zawalski16954532012-03-20 15:31:36 -0400611 updater.static_dir, params['target'],
612 milestone=params.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700613 except common_util.CommonUtilError as errmsg:
Scott Zawalski16954532012-03-20 15:31:36 -0400614 raise cherrypy.HTTPError('500 Internal Server Error', str(errmsg))
615
616 @cherrypy.expose
Scott Zawalski84a39c92012-01-13 15:12:42 -0500617 def controlfiles(self, **params):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500618 """Return a control file or a list of all known control files.
619
620 Example URL:
621 To List all control files:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500622 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500623 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500624 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 -0500625
626 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500627 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500628 control_path: If you want the contents of a control file set this
629 to the path. E.g. client/site_tests/sleeptest/control
630 Optional, if not provided return a list of control files is returned.
631 Returns:
632 Contents of a control file if control_path is provided.
633 A list of control files if no control_path is provided.
634 """
Scott Zawalski4647ce62012-01-03 17:17:28 -0500635 if not params:
636 return _PrintDocStringAsHTML(self.controlfiles)
637
Scott Zawalski84a39c92012-01-13 15:12:42 -0500638 if 'build' not in params:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500639 raise cherrypy.HTTPError('500 Internal Server Error',
Scott Zawalski84a39c92012-01-13 15:12:42 -0500640 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500641
642 if 'control_path' not in params:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700643 return common_util.GetControlFileList(
644 updater.static_dir, params['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500645 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700646 return common_util.GetControlFile(
647 updater.static_dir, params['build'], params['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800648
649 @cherrypy.expose
Gilad Arnold6f99b982012-09-12 10:49:40 -0700650 def stage_images(self, **kwargs):
651 """Downloads and stages a Chrome OS image from Google Storage.
652
Chris Sosa76e44b92013-01-31 12:11:38 -0800653 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700654 This method downloads a zipped archive from a specified GS location, then
655 extracts and stages the specified list of images and stages them under
Chris Sosa76e44b92013-01-31 12:11:38 -0800656 static/BOARD/BUILD/. Download is synchronous.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700657
658 Args:
659 archive_url: Google Storage URL for the build.
660 image_types: comma-separated list of images to download, may include
661 'test', 'recovery', and 'base'
662
663 Example URL:
664 http://myhost/stage_images?archive_url=gs://chromeos-image-archive/
665 x86-generic/R17-1208.0.0-a1-b338&image_types=test,base
666 """
Gilad Arnold6f99b982012-09-12 10:49:40 -0700667 image_types = kwargs.get('image_types').split(',')
Chris Sosa76e44b92013-01-31 12:11:38 -0800668 image_types_list = [image + '_image' for image in image_types]
669 self.stage(archive_url=kwargs.get('archive_url'), artifacts=','.join(
670 image_types_list))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700671
672 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700673 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700674 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700675 return ('Welcome to the Dev Server!<br>\n'
676 '<br>\n'
677 'Here are the available methods, click for documentation:<br>\n'
678 '<br>\n'
679 '%s' %
680 '<br>\n'.join(
681 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700682 for name in _FindExposedMethods(
683 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700684
685 @cherrypy.expose
686 def doc(self, *args):
687 """Shows the documentation for available methods / URLs.
688
689 Example:
690 http://myhost/doc/update
691 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700692 name = '/'.join(args)
693 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700694 if not method:
695 raise DevServerError("No exposed method named `%s'" % name)
696 if not method.__doc__:
697 raise DevServerError("No documentation for exposed method `%s'" % name)
698 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -0700699
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700700 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700701 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700702 """Handles an update check from a Chrome OS client.
703
704 The HTTP request should contain the standard Omaha-style XML blob. The URL
705 line may contain an additional intermediate path to the update payload.
706
707 Example:
708 http://myhost/update/optional/path/to/payload
709 """
Chris Sosa7c931362010-10-11 19:49:01 -0700710 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -0800711 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -0700712 data = cherrypy.request.rfile.read(body_length)
713 return updater.HandleUpdatePing(data, label)
714
Chris Sosa0356d3b2010-09-16 15:46:22 -0700715
Chris Sosadbc20082012-12-10 13:39:11 -0800716def _CleanCache(cache_dir, wipe):
717 """Wipes any excess cached items in the cache_dir.
718
719 Args:
720 cache_dir: the directory we are wiping from.
721 wipe: If True, wipe all the contents -- not just the excess.
722 """
723 if wipe:
724 # Clear the cache and exit on error.
725 cmd = 'rm -rf %s/*' % cache_dir
726 if os.system(cmd) != 0:
727 _Log('Failed to clear the cache with %s' % cmd)
728 sys.exit(1)
729 else:
730 # Clear all but the last N cached updates
731 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
732 (cache_dir, CACHED_ENTRIES))
733 if os.system(cmd) != 0:
734 _Log('Failed to clean up old delta cache files with %s' % cmd)
735 sys.exit(1)
736
737
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700738def _AddTestingOptions(parser):
739 group = optparse.OptionGroup(
740 parser, 'Advanced Testing Options', 'These are used by test scripts and '
741 'developers writing integration tests utilizing the devserver. They are '
742 'not intended to be really used outside the scope of someone '
743 'knowledgable about the test.')
744 group.add_option('--exit',
745 action='store_true',
746 help='do not start the server (yet pregenerate/clear cache)')
747 group.add_option('--host_log',
748 action='store_true', default=False,
749 help='record history of host update events (/api/hostlog)')
750 group.add_option('--max_updates',
751 metavar='NUM', default= -1, type='int',
752 help='maximum number of update checks handled positively '
753 '(default: unlimited)')
754 group.add_option('--private_key',
755 metavar='PATH', default=None,
756 help='path to the private key in pem format. If this is set '
757 'the devserver will generate update payloads that are '
758 'signed with this key.')
759 group.add_option('--proxy_port',
760 metavar='PORT', default=None, type='int',
761 help='port to have the client connect to -- basically the '
762 'devserver lies to the update to tell it to get the payload '
763 'from a different port that will proxy the request back to '
764 'the devserver. The proxy must be managed outside the '
765 'devserver.')
766 group.add_option('--remote_payload',
767 action='store_true', default=False,
768 help='Payload is being served from a remote machine')
769 group.add_option('-u', '--urlbase',
770 metavar='URL',
771 help='base URL for update images, other than the '
772 'devserver. Use in conjunction with remote_payload.')
773 parser.add_option_group(group)
774
775
776def _AddUpdateOptions(parser):
777 group = optparse.OptionGroup(
778 parser, 'Autoupdate Options', 'These options can be used to change '
779 'how the devserver either generates or serve update payloads. Please '
780 'note that all of these option affect how a payload is generated and so '
781 'do not work in archive-only mode.')
782 group.add_option('--board',
783 help='By default the devserver will create an update '
784 'payload from the latest image built for the board '
785 'a device that is requesting an update has. When we '
786 'pre-generate an update (see below) and we do not specify '
787 'another update_type option like image or payload, the '
788 'devserver needs to know the board to generate the latest '
789 'image for. This is that board.')
790 group.add_option('--critical_update',
791 action='store_true', default=False,
792 help='Present update payload as critical')
793 group.add_option('--for_vm',
794 dest='vm', action='store_true',
795 help='DEPRECATED: see no_patch_kernel.')
796 group.add_option('--image',
797 metavar='FILE',
798 help='Generate and serve an update using this image to any '
799 'device that requests an update.')
800 group.add_option('--no_patch_kernel',
801 dest='patch_kernel', action='store_false', default=True,
802 help='When generating an update payload, do not patch the '
803 'kernel with kernel verification blob from the stateful '
804 'partition.')
805 group.add_option('--payload',
806 metavar='PATH',
807 help='use the update payload from specified directory '
808 '(update.gz).')
809 group.add_option('-p', '--pregenerate_update',
810 action='store_true', default=False,
811 help='pre-generate the update payload before accepting '
812 'update requests. Useful to help debug payload generation '
813 'issues quickly. Also if an update payload will take a '
814 'long time to generate, a client may timeout if you do not'
815 'pregenerate the update.')
816 group.add_option('--src_image',
817 metavar='PATH', default='',
818 help='If specified, delta updates will be generated using '
819 'this image as the source image. Delta updates are when '
820 'you are updating from a "source image" to a another '
821 'image.')
822 parser.add_option_group(group)
823
824
825def _AddProductionOptions(parser):
826 group = optparse.OptionGroup(
827 parser, 'Advanced Server Options', 'These options can be used to changed '
828 'for advanced server behavior.')
829 # TODO(sosa): Clean up the fact we have archive_dir and data_dir. It's ugly.
830 # Should be --archive_mode + optional data_dir.
831 group.add_option('--archive_dir',
832 metavar='PATH',
833 help='Enables archive-only mode. This disables any '
834 'update or package generation related functionality. This '
835 'mode also works without a Chromium OS chroot.')
836 group.add_option('--clear_cache',
837 action='store_true', default=False,
838 help='At startup, removes all cached entries from the'
839 'devserver\'s cache.')
840 group.add_option('--logfile',
841 metavar='PATH',
842 help='log output to this file instead of stdout')
843 group.add_option('--production',
844 action='store_true', default=False,
845 help='have the devserver use production values when '
846 'starting up. This includes using more threads and '
847 'performing less logging.')
848 parser.add_option_group(group)
849
850
Chris Sosacde6bf42012-05-31 18:36:39 -0700851def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700852 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -0800853 parser = optparse.OptionParser(usage=usage)
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700854 parser.add_option('--data_dir',
855 metavar='PATH',
856 default=os.path.dirname(os.path.abspath(sys.argv[0])),
857 help='writable directory where static lives')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700858 parser.add_option('--port',
859 default=8080, type='int',
860 help='port for the dev server to use (default: 8080)')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700861 parser.add_option('-t', '--test_image',
862 action='store_true',
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700863 help='If set, look for the chromiumos_test_image.bin file '
864 'when generating update payloads rather than the '
865 'chromiumos_image.bin which is the default.')
866 _AddProductionOptions(parser)
867 _AddUpdateOptions(parser)
868 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -0700869 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +0000870
Chris Sosa7c931362010-10-11 19:49:01 -0700871 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
872 root_dir = os.path.realpath('%s/../..' % devserver_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700873 serve_only = False
874
Zdenek Behan608f46c2011-02-19 00:47:16 +0100875 static_dir = os.path.realpath('%s/static' % options.data_dir)
876 os.system('mkdir -p %s' % static_dir)
877
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700878 # TODO(sosa): Remove after depcrecation.
879 if options.vm:
880 options.patch_kernel = False
881
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700882 if options.archive_dir:
Zdenek Behan608f46c2011-02-19 00:47:16 +0100883 # TODO(zbehan) Remove legacy support:
884 # archive_dir is the directory where static/archive will point.
885 # If this is an absolute path, all is fine. If someone calls this
886 # using a relative path, that is relative to src/platform/dev/.
887 # That use case is unmaintainable, but since applications use it
888 # with =./static, instead of a boolean flag, we'll make this relative
889 # to devserver_dir to keep these unbroken. For now.
890 archive_dir = options.archive_dir
891 if not os.path.isabs(archive_dir):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700892 archive_dir = os.path.realpath(os.path.join(devserver_dir, archive_dir))
Zdenek Behan608f46c2011-02-19 00:47:16 +0100893 _PrepareToServeUpdatesOnly(archive_dir, static_dir)
Zdenek Behan6d93e552011-03-02 22:35:49 +0100894 static_dir = os.path.realpath(archive_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700895 serve_only = True
Chris Sosa0356d3b2010-09-16 15:46:22 -0700896
Don Garrettf90edf02010-11-16 17:36:14 -0800897 cache_dir = os.path.join(static_dir, 'cache')
Chris Sosadbc20082012-12-10 13:39:11 -0800898 # If our devserver is only supposed to serve payloads, we shouldn't be mucking
899 # with the cache at all. If the devserver hadn't previously generated a cache
900 # and is expected, the caller is using it wrong.
901 if serve_only:
902 # Extra check to make sure we're not being called incorrectly.
903 if (options.clear_cache or options.exit or options.pregenerate_update or
904 options.board or options.image):
905 parser.error('Incompatible flags detected for serve_only mode.')
Don Garrettf90edf02010-11-16 17:36:14 -0800906
Chris Sosadbc20082012-12-10 13:39:11 -0800907 elif os.path.exists(cache_dir):
908 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -0800909 else:
910 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800911
Chris Sosadbc20082012-12-10 13:39:11 -0800912 _Log('Using cache directory %s' % cache_dir)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700913 _Log('Data dir is %s' % options.data_dir)
914 _Log('Source root is %s' % root_dir)
915 _Log('Serving from %s' % static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +0000916
Chris Sosa6a3697f2013-01-29 16:44:43 -0800917 # We allow global use here to share with cherrypy classes.
918 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -0700919 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -0700920 updater = autoupdate.Autoupdate(
921 root_dir=root_dir,
922 static_dir=static_dir,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700923 serve_only=serve_only,
Andrew de los Reyes52620802010-04-12 13:40:07 -0700924 urlbase=options.urlbase,
925 test_image=options.test_image,
Chris Sosa5d342a22010-09-28 16:54:41 -0700926 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700927 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -0800928 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -0700929 src_image=options.src_image,
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700930 patch_kernel=options.patch_kernel,
Chris Sosa08d55a22011-01-19 16:08:02 -0800931 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800932 copy_to_static_root=not options.exit,
933 private_key=options.private_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800934 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700935 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700936 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -0700937 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800938 )
Chris Sosa7c931362010-10-11 19:49:01 -0700939
Chris Sosa6a3697f2013-01-29 16:44:43 -0800940 if options.pregenerate_update:
941 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700942
Don Garrett0c880e22010-11-17 18:13:37 -0800943 # If the command line requested after setup, it's time to do it.
944 if not options.exit:
Chris Sosa66e2d9c2012-07-11 14:14:14 -0700945 # Handle options that must be set globally in cherrypy.
Chris Sosa2f1c41e2012-07-10 14:32:33 -0700946 if options.production:
Chris Sosa66e2d9c2012-07-11 14:14:14 -0700947 cherrypy.config.update({'environment': 'production'})
948 if not options.logfile:
949 cherrypy.config.update({'log.screen': True})
950 else:
951 cherrypy.config.update({'log.error_file': options.logfile,
952 'log.access_file': options.logfile})
Chris Sosa2f1c41e2012-07-10 14:32:33 -0700953
Don Garrett0c880e22010-11-17 18:13:37 -0800954 cherrypy.quickstart(DevServerRoot(), config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -0700955
956
957if __name__ == '__main__':
958 main()