blob: 11e8937c4d36925977ec33be9e035e0994adc5b3 [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
Gilad Arnold55a2a372012-10-02 09:46:32 -070044import json
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070045import optparse
rtc@google.comded22402009-10-26 22:36:21 +000046import os
Scott Zawalski4647ce62012-01-03 17:17:28 -050047import re
Simran Basi4baad082013-02-14 13:39:18 -080048import shutil
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -080049import socket
Chris Masone816e38c2012-05-02 12:22:36 -070050import subprocess
J. Richard Barnette3d977b82013-04-23 11:05:19 -070051import sys
Chris Masone816e38c2012-05-02 12:22:36 -070052import tempfile
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070053import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070054from logging import handlers
55
56import cherrypy
57import cherrypy._cplogging
rtc@google.comded22402009-10-26 22:36:21 +000058
Chris Sosa0356d3b2010-09-16 15:46:22 -070059import autoupdate
Gilad Arnoldc65330c2012-09-20 15:17:48 -070060import common_util
Chris Sosa47a7d4e2012-03-28 11:26:55 -070061import downloader
Gilad Arnoldc65330c2012-09-20 15:17:48 -070062import log_util
63
64
65# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080066def _Log(message, *args):
67 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 15:46:22 -070068
Frank Farzan40160872011-12-12 18:39:18 -080069
Chris Sosa417e55d2011-01-25 16:40:48 -080070CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080071
Simran Basi4baad082013-02-14 13:39:18 -080072TELEMETRY_FOLDER = 'telemetry_src'
73TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
74 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070075 'dep-chrome_test.tar.bz2',
76 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -080077
Chris Sosa0356d3b2010-09-16 15:46:22 -070078# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +000079updater = None
rtc@google.comded22402009-10-26 22:36:21 +000080
J. Richard Barnette3d977b82013-04-23 11:05:19 -070081# Log rotation parameters. These settings correspond to once a week
82# on Saturday, with about three months of old logs kept for backup.
83#
84# For more, see the documentation for
85# logging.handlers.TimedRotatingFileHandler
86_LOG_ROTATION_TIME = 'W5'
87_LOG_ROTATION_BACKUP = 13
88
Frank Farzan40160872011-12-12 18:39:18 -080089
Chris Sosa9164ca32012-03-28 11:04:50 -070090class DevServerError(Exception):
Chris Sosa47a7d4e2012-03-28 11:26:55 -070091 """Exception class used by this module."""
92 pass
93
94
Scott Zawalski4647ce62012-01-03 17:17:28 -050095def _LeadingWhiteSpaceCount(string):
96 """Count the amount of leading whitespace in a string.
97
98 Args:
99 string: The string to count leading whitespace in.
100 Returns:
101 number of white space chars before characters start.
102 """
103 matched = re.match('^\s+', string)
104 if matched:
105 return len(matched.group())
106
107 return 0
108
109
110def _PrintDocStringAsHTML(func):
111 """Make a functions docstring somewhat HTML style.
112
113 Args:
114 func: The function to return the docstring from.
115 Returns:
116 A string that is somewhat formated for a web browser.
117 """
118 # TODO(scottz): Make this parse Args/Returns in a prettier way.
119 # Arguments could be bolded and indented etc.
120 html_doc = []
121 for line in func.__doc__.splitlines():
122 leading_space = _LeadingWhiteSpaceCount(line)
123 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700124 line = '&nbsp;' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500125
126 html_doc.append('<BR>%s' % line)
127
128 return '\n'.join(html_doc)
129
130
Chris Sosa7c931362010-10-11 19:49:01 -0700131def _GetConfig(options):
132 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800133
134 # On a system with IPv6 not compiled into the kernel,
135 # AF_INET6 sockets will return a socket.error exception.
136 # On such systems, fall-back to IPv4.
137 socket_host = '::'
138 try:
139 socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
140 except socket.error:
141 socket_host = '0.0.0.0'
142
Chris Sosa7c931362010-10-11 19:49:01 -0700143 base_config = { 'global':
144 { 'server.log_request_headers': True,
145 'server.protocol_version': 'HTTP/1.1',
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800146 'server.socket_host': socket_host,
Chris Sosa7c931362010-10-11 19:49:01 -0700147 'server.socket_port': int(options.port),
Chris Sosa374c62d2010-10-14 09:13:54 -0700148 'response.timeout': 6000,
Chris Sosa6fe23942012-07-02 15:44:46 -0700149 'request.show_tracebacks': True,
Chris Sosa72333d12012-06-13 11:28:05 -0700150 'server.socket_timeout': 60,
Zdenek Behan1347a312011-02-10 03:59:17 +0100151 'tools.staticdir.root':
152 os.path.dirname(os.path.abspath(sys.argv[0])),
Chris Sosa7c931362010-10-11 19:49:01 -0700153 },
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700154 '/api':
155 {
156 # Gets rid of cherrypy parsing post file for args.
157 'request.process_request_body': False,
158 },
Chris Sosaa1ef0102010-10-21 16:22:35 -0700159 '/build':
160 {
161 'response.timeout': 100000,
162 },
Chris Sosa7c931362010-10-11 19:49:01 -0700163 '/update':
164 {
165 # Gets rid of cherrypy parsing post file for args.
166 'request.process_request_body': False,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700167 'response.timeout': 10000,
Chris Sosa7c931362010-10-11 19:49:01 -0700168 },
169 # Sets up the static dir for file hosting.
170 '/static':
171 { 'tools.staticdir.dir': 'static',
172 'tools.staticdir.on': True,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700173 'response.timeout': 10000,
Chris Sosa7c931362010-10-11 19:49:01 -0700174 },
175 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700176 if options.production:
Chris Sosad1ea86b2012-07-12 13:35:37 -0700177 base_config['global'].update({'server.thread_pool': 75})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500178
Chris Sosa7c931362010-10-11 19:49:01 -0700179 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000180
Darin Petkove17164a2010-08-11 13:24:41 -0700181
Zdenek Behan608f46c2011-02-19 00:47:16 +0100182def _PrepareToServeUpdatesOnly(image_dir, static_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700183 """Sets up symlink to image_dir for serving purposes."""
184 assert os.path.exists(image_dir), '%s must exist.' % image_dir
185 # If we're serving out of an archived build dir (e.g. a
186 # buildbot), prepare this webserver's magic 'static/' dir with a
187 # link to the build archive.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700188 _Log('Preparing autoupdate for "serve updates only" mode.')
Zdenek Behan608f46c2011-02-19 00:47:16 +0100189 if os.path.lexists('%s/archive' % static_dir):
190 if image_dir != os.readlink('%s/archive' % static_dir):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700191 _Log('removing stale symlink to %s' % image_dir)
Zdenek Behan608f46c2011-02-19 00:47:16 +0100192 os.unlink('%s/archive' % static_dir)
193 os.symlink(image_dir, '%s/archive' % static_dir)
Chris Sosacde6bf42012-05-31 18:36:39 -0700194
Chris Sosa0356d3b2010-09-16 15:46:22 -0700195 else:
Zdenek Behan608f46c2011-02-19 00:47:16 +0100196 os.symlink(image_dir, '%s/archive' % static_dir)
Chris Sosacde6bf42012-05-31 18:36:39 -0700197
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700198 _Log('archive dir: %s ready to be used to serve images.' % image_dir)
Chris Sosa7c931362010-10-11 19:49:01 -0700199
200
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700201def _GetRecursiveMemberObject(root, member_list):
202 """Returns an object corresponding to a nested member list.
203
204 Args:
205 root: the root object to search
206 member_list: list of nested members to search
207 Returns:
208 An object corresponding to the member name list; None otherwise.
209 """
210 for member in member_list:
211 next_root = root.__class__.__dict__.get(member)
212 if not next_root:
213 return None
214 root = next_root
215 return root
216
217
218def _IsExposed(name):
219 """Returns True iff |name| has an `exposed' attribute and it is set."""
220 return hasattr(name, 'exposed') and name.exposed
221
222
Gilad Arnold748c8322012-10-12 09:51:35 -0700223def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700224 """Returns a CherryPy-exposed method, if such exists.
225
226 Args:
227 root: the root object for searching
228 nested_member: a slash-joined path to the nested member
229 ignored: method paths to be ignored
230 Returns:
231 A function object corresponding to the path defined by |member_list| from
232 the |root| object, if the function is exposed and not ignored; None
233 otherwise.
234 """
Gilad Arnold748c8322012-10-12 09:51:35 -0700235 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700236 _GetRecursiveMemberObject(root, nested_member.split('/')))
237 if (method and type(method) == types.FunctionType and _IsExposed(method)):
238 return method
239
240
Gilad Arnold748c8322012-10-12 09:51:35 -0700241def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700242 """Finds exposed CherryPy methods.
243
244 Args:
245 root: the root object for searching
246 prefix: slash-joined chain of members leading to current object
247 unlisted: URLs to be excluded regardless of their exposed status
248 Returns:
249 List of exposed URLs that are not unlisted.
250 """
251 method_list = []
252 for member in sorted(root.__class__.__dict__.keys()):
253 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700254 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700255 continue
256 member_obj = root.__class__.__dict__[member]
257 if _IsExposed(member_obj):
258 if type(member_obj) == types.FunctionType:
259 method_list.append(prefixed_member)
260 else:
261 method_list += _FindExposedMethods(
262 member_obj, prefixed_member, unlisted)
263 return method_list
264
265
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700266class ApiRoot(object):
267 """RESTful API for Dev Server information."""
268 exposed = True
269
270 @cherrypy.expose
271 def hostinfo(self, ip):
272 """Returns a JSON dictionary containing information about the given ip.
273
Gilad Arnold1b908392012-10-05 11:36:27 -0700274 Args:
275 ip: address of host whose info is requested
276 Returns:
277 A JSON dictionary containing all or some of the following fields:
278 last_event_type (int): last update event type received
279 last_event_status (int): last update event status received
280 last_known_version (string): last known version reported in update ping
281 forced_update_label (string): update label to force next update ping to
282 use, set by setnextupdate
283 See the OmahaEvent class in update_engine/omaha_request_action.h for
284 event type and status code definitions. If the ip does not exist an empty
285 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700286
Gilad Arnold1b908392012-10-05 11:36:27 -0700287 Example URL:
288 http://myhost/api/hostinfo?ip=192.168.1.5
289 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700290 return updater.HandleHostInfoPing(ip)
291
292 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800293 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700294 """Returns a JSON object containing a log of host event.
295
296 Args:
297 ip: address of host whose event log is requested, or `all'
298 Returns:
299 A JSON encoded list (log) of dictionaries (events), each of which
300 containing a `timestamp' and other event fields, as described under
301 /api/hostinfo.
302
303 Example URL:
304 http://myhost/api/hostlog?ip=192.168.1.5
305 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800306 return updater.HandleHostLogPing(ip)
307
308 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700309 def setnextupdate(self, ip):
310 """Allows the response to the next update ping from a host to be set.
311
312 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 11:36:27 -0700313 /update command.
314 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700315 body_length = int(cherrypy.request.headers['Content-Length'])
316 label = cherrypy.request.rfile.read(body_length)
317
318 if label:
319 label = label.strip()
320 if label:
321 return updater.HandleSetUpdatePing(ip, label)
322 raise cherrypy.HTTPError(400, 'No label provided.')
323
324
Gilad Arnold55a2a372012-10-02 09:46:32 -0700325 @cherrypy.expose
326 def fileinfo(self, *path_args):
327 """Returns information about a given staged file.
328
329 Args:
330 path_args: path to the file inside the server's static staging directory
331 Returns:
332 A JSON encoded dictionary with information about the said file, which may
333 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700334 size (int): the file size in bytes
335 sha1 (string): a base64 encoded SHA1 hash
336 sha256 (string): a base64 encoded SHA256 hash
337
338 Example URL:
339 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700340 """
341 file_path = os.path.join(updater.static_dir, *path_args)
342 if not os.path.exists(file_path):
343 raise DevServerError('file not found: %s' % file_path)
344 try:
345 file_size = os.path.getsize(file_path)
346 file_sha1 = common_util.GetFileSha1(file_path)
347 file_sha256 = common_util.GetFileSha256(file_path)
348 except os.error, e:
349 raise DevServerError('failed to get info for file %s: %s' %
Gilad Arnolde74b3812013-04-22 11:27:38 -0700350 (file_path, e))
351
352 is_delta = autoupdate.Autoupdate.IsDeltaFormatFile(file_path)
353
354 return json.dumps({
355 autoupdate.Autoupdate.SIZE_ATTR: file_size,
356 autoupdate.Autoupdate.SHA1_ATTR: file_sha1,
357 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
358 autoupdate.Autoupdate.ISDELTA_ATTR: is_delta
359 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700360
Chris Sosa76e44b92013-01-31 12:11:38 -0800361
David Rochberg7c79a812011-01-19 14:24:45 -0500362class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700363 """The Root Class for the Dev Server.
364
365 CherryPy works as follows:
366 For each method in this class, cherrpy interprets root/path
367 as a call to an instance of DevServerRoot->method_name. For example,
368 a call to http://myhost/build will call build. CherryPy automatically
369 parses http args and places them as keyword arguments in each method.
370 For paths http://myhost/update/dir1/dir2, you can use *args so that
371 cherrypy uses the update method and puts the extra paths in args.
372 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700373 # Method names that should not be listed on the index page.
374 _UNLISTED_METHODS = ['index', 'doc']
375
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700376 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700377
David Rochberg7c79a812011-01-19 14:24:45 -0500378 def __init__(self):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700379 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800380 self._telemetry_lock_dict = common_util.LockDict()
David Rochberg7c79a812011-01-19 14:24:45 -0500381
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700382 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500383 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700384 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700385 import builder
386 if self._builder is None:
387 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500388 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700389
Chris Sosacde6bf42012-05-31 18:36:39 -0700390 @staticmethod
391 def _canonicalize_archive_url(archive_url):
392 """Canonicalizes archive_url strings.
393
394 Raises:
395 DevserverError: if archive_url is not set.
396 """
397 if archive_url:
Chris Sosa76e44b92013-01-31 12:11:38 -0800398 if not archive_url.startswith('gs://'):
399 raise DevServerError("Archive URL isn't from Google Storage.")
400
Chris Sosacde6bf42012-05-31 18:36:39 -0700401 return archive_url.rstrip('/')
402 else:
403 raise DevServerError("Must specify an archive_url in the request")
404
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700405 @cherrypy.expose
Frank Farzanbcb571e2012-01-03 11:48:17 -0800406 def download(self, **kwargs):
407 """Downloads and archives full/delta payloads from Google Storage.
408
Chris Sosa76e44b92013-01-31 12:11:38 -0800409 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700410 This methods downloads artifacts. It may download artifacts in the
411 background in which case a caller should call wait_for_status to get
412 the status of the background artifact downloads. They should use the same
413 args passed to download.
414
Frank Farzanbcb571e2012-01-03 11:48:17 -0800415 Args:
416 archive_url: Google Storage URL for the build.
417
418 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700419 http://myhost/download?archive_url=gs://chromeos-image-archive/
420 x86-generic/R17-1208.0.0-a1-b338
Frank Farzanbcb571e2012-01-03 11:48:17 -0800421 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800422 return self.stage(archive_url=kwargs.get('archive_url'),
423 artifacts='full_payload,test_suites,stateful')
424
425 @cherrypy.expose
426 def stage(self, **kwargs):
427 """Downloads and caches the artifacts from Google Storage URL.
428
429 Downloads and caches the artifacts Google Storage URL. Returns once these
430 have been downloaded on the devserver. A call to this will attempt to cache
431 non-specified artifacts in the background for the given from the given URL
432 following the principle of spatial locality. Spatial locality of different
433 artifacts is explicitly defined in the build_artifact module.
434
435 These artifacts will then be available from the static/ sub-directory of
436 the devserver.
437
438 Args:
439 archive_url: Google Storage URL for the build.
440 artifacts: Comma separated list of artifacts to download.
441
442 Example:
443 To download the autotest and test suites tarballs:
444 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
445 artifacts=autotest,test_suites
446 To download the full update payload:
447 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
448 artifacts=full_payload
449
450 For both these examples, one could find these artifacts at:
451 http://devserver_url:<port>/static/archive/<relative_path>*
452
453 Note for this example, relative path is the archive_url stripped of its
454 basename i.e. path/ in the examples above. Specific example:
455
456 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
457
458 Will get staged to:
459
460 http://devserver_url:<port>/static/archive/x86-mario-release/R26-3920.0.0
461 """
Chris Sosacde6bf42012-05-31 18:36:39 -0700462 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Chris Sosa76e44b92013-01-31 12:11:38 -0800463 artifacts = kwargs.get('artifacts', '')
464 if not artifacts:
465 raise DevServerError('No artifacts specified.')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700466
Chris Sosa76e44b92013-01-31 12:11:38 -0800467 downloader.Downloader(updater.static_dir, archive_url).Download(
468 artifacts.split(','))
469 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700470
471 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800472 def setup_telemetry(self, **kwargs):
473 """Extracts and sets up telemetry
474
475 This method goes through the telemetry deps packages, and stages them on
476 the devserver to be used by the drones and the telemetry tests.
477
478 Args:
479 archive_url: Google Storage URL for the build.
480
481 Returns:
482 Path to the source folder for the telemetry codebase once it is staged.
483 """
484 archive_url = kwargs.get('archive_url')
485 self.stage(archive_url=archive_url, artifacts='autotest')
486
487 build = '/'.join(downloader.Downloader.ParseUrl(archive_url))
488 build_path = os.path.join(updater.static_dir, build)
489 deps_path = os.path.join(build_path, 'autotest/packages')
490 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
491 src_folder = os.path.join(telemetry_path, 'src')
492
493 with self._telemetry_lock_dict.lock(telemetry_path):
494 if os.path.exists(src_folder):
495 # Telemetry is already fully stage return
496 return src_folder
497
498 common_util.MkDirP(telemetry_path)
499
500 # Copy over the required deps tar balls to the telemetry directory.
501 for dep in TELEMETRY_DEPS:
502 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700503 if not os.path.exists(dep_path):
504 # This dep does not exist (could be new), do not extract it.
505 continue
Simran Basi4baad082013-02-14 13:39:18 -0800506 try:
507 common_util.ExtractTarball(dep_path, telemetry_path)
508 except common_util.CommonUtilError as e:
509 shutil.rmtree(telemetry_path)
510 raise DevServerError(str(e))
511
512 # By default all the tarballs extract to test_src but some parts of
513 # the telemetry code specifically hardcoded to exist inside of 'src'.
514 test_src = os.path.join(telemetry_path, 'test_src')
515 try:
516 shutil.move(test_src, src_folder)
517 except shutil.Error:
518 # This can occur if src_folder already exists. Remove and retry move.
519 shutil.rmtree(src_folder)
520 raise DevServerError('Failure in telemetry setup for build %s. Appears'
521 ' that the test_src to src move failed.' % build)
522
523 return src_folder
524
525 @cherrypy.expose
Chris Sosacde6bf42012-05-31 18:36:39 -0700526 def wait_for_status(self, **kwargs):
527 """Waits for background artifacts to be downloaded from Google Storage.
528
Chris Sosa76e44b92013-01-31 12:11:38 -0800529 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosacde6bf42012-05-31 18:36:39 -0700530 Args:
531 archive_url: Google Storage URL for the build.
532
533 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700534 http://myhost/wait_for_status?archive_url=gs://chromeos-image-archive/
535 x86-generic/R17-1208.0.0-a1-b338
Chris Sosacde6bf42012-05-31 18:36:39 -0700536 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800537 return self.stage(archive_url=kwargs.get('archive_url'),
538 artifacts='full_payload,test_suites,autotest,stateful')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700539
540 @cherrypy.expose
Chris Masone816e38c2012-05-02 12:22:36 -0700541 def stage_debug(self, **kwargs):
542 """Downloads and stages debug symbol payloads from Google Storage.
543
Chris Sosa76e44b92013-01-31 12:11:38 -0800544 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
545 This methods downloads the debug symbol build artifact
546 synchronously, and then stages it for use by symbolicate_dump.
Chris Masone816e38c2012-05-02 12:22:36 -0700547
548 Args:
549 archive_url: Google Storage URL for the build.
550
551 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700552 http://myhost/stage_debug?archive_url=gs://chromeos-image-archive/
553 x86-generic/R17-1208.0.0-a1-b338
Chris Masone816e38c2012-05-02 12:22:36 -0700554 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800555 return self.stage(archive_url=kwargs.get('archive_url'),
556 artifacts='symbols')
Chris Masone816e38c2012-05-02 12:22:36 -0700557
558 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800559 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700560 """Symbolicates a minidump using pre-downloaded symbols, returns it.
561
562 Callers will need to POST to this URL with a body of MIME-type
563 "multipart/form-data".
564 The body should include a single argument, 'minidump', containing the
565 binary-formatted minidump to symbolicate.
566
Chris Masone816e38c2012-05-02 12:22:36 -0700567 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800568 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700569 minidump: The binary minidump file to symbolicate.
570 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800571 # Ensure the symbols have been staged.
572 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
573 if self.stage(archive_url=archive_url, artifacts='symbols') != 'Success':
574 raise DevServerError('Failed to stage symbols for %s' % archive_url)
575
Chris Masone816e38c2012-05-02 12:22:36 -0700576 to_return = ''
577 with tempfile.NamedTemporaryFile() as local:
578 while True:
579 data = minidump.file.read(8192)
580 if not data:
581 break
582 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800583
Chris Masone816e38c2012-05-02 12:22:36 -0700584 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800585
586 symbols_directory = os.path.join(downloader.Downloader.GetBuildDir(
587 updater.static_dir, archive_url), 'debug', 'breakpad')
588
589 stackwalk = subprocess.Popen(
590 ['minidump_stackwalk', local.name, symbols_directory],
591 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
592
Chris Masone816e38c2012-05-02 12:22:36 -0700593 to_return, error_text = stackwalk.communicate()
594 if stackwalk.returncode != 0:
595 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
596 error_text, stackwalk.returncode))
597
598 return to_return
599
600 @cherrypy.expose
Scott Zawalski16954532012-03-20 15:31:36 -0400601 def latestbuild(self, **params):
602 """Return a string representing the latest build for a given target.
603
604 Args:
605 target: The build target, typically a combination of the board and the
606 type of build e.g. x86-mario-release.
607 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
608 provided the latest RXX build will be returned.
609 Returns:
610 A string representation of the latest build if one exists, i.e.
611 R19-1993.0.0-a1-b1480.
612 An empty string if no latest could be found.
613 """
614 if not params:
615 return _PrintDocStringAsHTML(self.latestbuild)
616
617 if 'target' not in params:
618 raise cherrypy.HTTPError('500 Internal Server Error',
619 'Error: target= is required!')
620 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700621 return common_util.GetLatestBuildVersion(
Scott Zawalski16954532012-03-20 15:31:36 -0400622 updater.static_dir, params['target'],
623 milestone=params.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700624 except common_util.CommonUtilError as errmsg:
Scott Zawalski16954532012-03-20 15:31:36 -0400625 raise cherrypy.HTTPError('500 Internal Server Error', str(errmsg))
626
627 @cherrypy.expose
Scott Zawalski84a39c92012-01-13 15:12:42 -0500628 def controlfiles(self, **params):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500629 """Return a control file or a list of all known control files.
630
631 Example URL:
632 To List all control files:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500633 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500634 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500635 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 -0500636
637 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500638 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500639 control_path: If you want the contents of a control file set this
640 to the path. E.g. client/site_tests/sleeptest/control
641 Optional, if not provided return a list of control files is returned.
642 Returns:
643 Contents of a control file if control_path is provided.
644 A list of control files if no control_path is provided.
645 """
Scott Zawalski4647ce62012-01-03 17:17:28 -0500646 if not params:
647 return _PrintDocStringAsHTML(self.controlfiles)
648
Scott Zawalski84a39c92012-01-13 15:12:42 -0500649 if 'build' not in params:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500650 raise cherrypy.HTTPError('500 Internal Server Error',
Scott Zawalski84a39c92012-01-13 15:12:42 -0500651 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500652
653 if 'control_path' not in params:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700654 return common_util.GetControlFileList(
655 updater.static_dir, params['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500656 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700657 return common_util.GetControlFile(
658 updater.static_dir, params['build'], params['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800659
660 @cherrypy.expose
Gilad Arnold6f99b982012-09-12 10:49:40 -0700661 def stage_images(self, **kwargs):
662 """Downloads and stages a Chrome OS image from Google Storage.
663
Chris Sosa76e44b92013-01-31 12:11:38 -0800664 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700665 This method downloads a zipped archive from a specified GS location, then
666 extracts and stages the specified list of images and stages them under
Chris Sosa76e44b92013-01-31 12:11:38 -0800667 static/BOARD/BUILD/. Download is synchronous.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700668
669 Args:
670 archive_url: Google Storage URL for the build.
671 image_types: comma-separated list of images to download, may include
672 'test', 'recovery', and 'base'
673
674 Example URL:
675 http://myhost/stage_images?archive_url=gs://chromeos-image-archive/
676 x86-generic/R17-1208.0.0-a1-b338&image_types=test,base
677 """
Gilad Arnold6f99b982012-09-12 10:49:40 -0700678 image_types = kwargs.get('image_types').split(',')
Chris Sosa76e44b92013-01-31 12:11:38 -0800679 image_types_list = [image + '_image' for image in image_types]
680 self.stage(archive_url=kwargs.get('archive_url'), artifacts=','.join(
681 image_types_list))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700682
683 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700684 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700685 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700686 return ('Welcome to the Dev Server!<br>\n'
687 '<br>\n'
688 'Here are the available methods, click for documentation:<br>\n'
689 '<br>\n'
690 '%s' %
691 '<br>\n'.join(
692 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700693 for name in _FindExposedMethods(
694 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700695
696 @cherrypy.expose
697 def doc(self, *args):
698 """Shows the documentation for available methods / URLs.
699
700 Example:
701 http://myhost/doc/update
702 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700703 name = '/'.join(args)
704 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700705 if not method:
706 raise DevServerError("No exposed method named `%s'" % name)
707 if not method.__doc__:
708 raise DevServerError("No documentation for exposed method `%s'" % name)
709 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -0700710
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700711 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700712 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700713 """Handles an update check from a Chrome OS client.
714
715 The HTTP request should contain the standard Omaha-style XML blob. The URL
716 line may contain an additional intermediate path to the update payload.
717
718 Example:
719 http://myhost/update/optional/path/to/payload
720 """
Chris Sosa7c931362010-10-11 19:49:01 -0700721 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -0800722 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -0700723 data = cherrypy.request.rfile.read(body_length)
724 return updater.HandleUpdatePing(data, label)
725
Chris Sosa0356d3b2010-09-16 15:46:22 -0700726
Chris Sosadbc20082012-12-10 13:39:11 -0800727def _CleanCache(cache_dir, wipe):
728 """Wipes any excess cached items in the cache_dir.
729
730 Args:
731 cache_dir: the directory we are wiping from.
732 wipe: If True, wipe all the contents -- not just the excess.
733 """
734 if wipe:
735 # Clear the cache and exit on error.
736 cmd = 'rm -rf %s/*' % cache_dir
737 if os.system(cmd) != 0:
738 _Log('Failed to clear the cache with %s' % cmd)
739 sys.exit(1)
740 else:
741 # Clear all but the last N cached updates
742 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
743 (cache_dir, CACHED_ENTRIES))
744 if os.system(cmd) != 0:
745 _Log('Failed to clean up old delta cache files with %s' % cmd)
746 sys.exit(1)
747
748
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700749def _AddTestingOptions(parser):
750 group = optparse.OptionGroup(
751 parser, 'Advanced Testing Options', 'These are used by test scripts and '
752 'developers writing integration tests utilizing the devserver. They are '
753 'not intended to be really used outside the scope of someone '
754 'knowledgable about the test.')
755 group.add_option('--exit',
756 action='store_true',
757 help='do not start the server (yet pregenerate/clear cache)')
758 group.add_option('--host_log',
759 action='store_true', default=False,
760 help='record history of host update events (/api/hostlog)')
761 group.add_option('--max_updates',
762 metavar='NUM', default= -1, type='int',
763 help='maximum number of update checks handled positively '
764 '(default: unlimited)')
765 group.add_option('--private_key',
766 metavar='PATH', default=None,
767 help='path to the private key in pem format. If this is set '
768 'the devserver will generate update payloads that are '
769 'signed with this key.')
770 group.add_option('--proxy_port',
771 metavar='PORT', default=None, type='int',
772 help='port to have the client connect to -- basically the '
773 'devserver lies to the update to tell it to get the payload '
774 'from a different port that will proxy the request back to '
775 'the devserver. The proxy must be managed outside the '
776 'devserver.')
777 group.add_option('--remote_payload',
778 action='store_true', default=False,
779 help='Payload is being served from a remote machine')
780 group.add_option('-u', '--urlbase',
781 metavar='URL',
782 help='base URL for update images, other than the '
783 'devserver. Use in conjunction with remote_payload.')
784 parser.add_option_group(group)
785
786
787def _AddUpdateOptions(parser):
788 group = optparse.OptionGroup(
789 parser, 'Autoupdate Options', 'These options can be used to change '
790 'how the devserver either generates or serve update payloads. Please '
791 'note that all of these option affect how a payload is generated and so '
792 'do not work in archive-only mode.')
793 group.add_option('--board',
794 help='By default the devserver will create an update '
795 'payload from the latest image built for the board '
796 'a device that is requesting an update has. When we '
797 'pre-generate an update (see below) and we do not specify '
798 'another update_type option like image or payload, the '
799 'devserver needs to know the board to generate the latest '
800 'image for. This is that board.')
801 group.add_option('--critical_update',
802 action='store_true', default=False,
803 help='Present update payload as critical')
804 group.add_option('--for_vm',
805 dest='vm', action='store_true',
806 help='DEPRECATED: see no_patch_kernel.')
807 group.add_option('--image',
808 metavar='FILE',
809 help='Generate and serve an update using this image to any '
810 'device that requests an update.')
811 group.add_option('--no_patch_kernel',
812 dest='patch_kernel', action='store_false', default=True,
813 help='When generating an update payload, do not patch the '
814 'kernel with kernel verification blob from the stateful '
815 'partition.')
816 group.add_option('--payload',
817 metavar='PATH',
818 help='use the update payload from specified directory '
819 '(update.gz).')
820 group.add_option('-p', '--pregenerate_update',
821 action='store_true', default=False,
822 help='pre-generate the update payload before accepting '
823 'update requests. Useful to help debug payload generation '
824 'issues quickly. Also if an update payload will take a '
825 'long time to generate, a client may timeout if you do not'
826 'pregenerate the update.')
827 group.add_option('--src_image',
828 metavar='PATH', default='',
829 help='If specified, delta updates will be generated using '
830 'this image as the source image. Delta updates are when '
831 'you are updating from a "source image" to a another '
832 'image.')
833 parser.add_option_group(group)
834
835
836def _AddProductionOptions(parser):
837 group = optparse.OptionGroup(
838 parser, 'Advanced Server Options', 'These options can be used to changed '
839 'for advanced server behavior.')
840 # TODO(sosa): Clean up the fact we have archive_dir and data_dir. It's ugly.
841 # Should be --archive_mode + optional data_dir.
842 group.add_option('--archive_dir',
843 metavar='PATH',
844 help='Enables archive-only mode. This disables any '
845 'update or package generation related functionality. This '
846 'mode also works without a Chromium OS chroot.')
847 group.add_option('--clear_cache',
848 action='store_true', default=False,
849 help='At startup, removes all cached entries from the'
850 'devserver\'s cache.')
851 group.add_option('--logfile',
852 metavar='PATH',
853 help='log output to this file instead of stdout')
854 group.add_option('--production',
855 action='store_true', default=False,
856 help='have the devserver use production values when '
857 'starting up. This includes using more threads and '
858 'performing less logging.')
859 parser.add_option_group(group)
860
861
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700862def _MakeLogHandler(logfile):
863 """Create a LogHandler instance used to log all messages."""
864 hdlr_cls = handlers.TimedRotatingFileHandler
865 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
866 backupCount=_LOG_ROTATION_BACKUP)
867 # The cherrypy documentation says to use the _cplogging module for
868 # this, even though it's named as a private module.
869 # pylint: disable=W0212
870 hdlr.setFormatter(cherrypy._cplogging.logfmt)
871 return hdlr
872
873
Chris Sosacde6bf42012-05-31 18:36:39 -0700874def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700875 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -0800876 parser = optparse.OptionParser(usage=usage)
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700877 parser.add_option('--data_dir',
878 metavar='PATH',
879 default=os.path.dirname(os.path.abspath(sys.argv[0])),
880 help='writable directory where static lives')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700881 parser.add_option('--port',
882 default=8080, type='int',
883 help='port for the dev server to use (default: 8080)')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700884 parser.add_option('-t', '--test_image',
885 action='store_true',
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700886 help='If set, look for the chromiumos_test_image.bin file '
887 'when generating update payloads rather than the '
888 'chromiumos_image.bin which is the default.')
889 _AddProductionOptions(parser)
890 _AddUpdateOptions(parser)
891 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -0700892 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +0000893
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700894 # Handle options that must be set globally in cherrypy. Do this
895 # work up front, because calls to _Log() below depend on this
896 # initialization.
897 if options.production:
898 cherrypy.config.update({'environment': 'production'})
899 if not options.logfile:
900 cherrypy.config.update({'log.screen': True})
901 else:
902 cherrypy.config.update({'log.error_file': '',
903 'log.access_file': ''})
904 hdlr = _MakeLogHandler(options.logfile)
905 # Pylint can't seem to process these two calls properly
906 # pylint: disable=E1101
907 cherrypy.log.access_log.addHandler(hdlr)
908 cherrypy.log.error_log.addHandler(hdlr)
909 # pylint: enable=E1101
910
Chris Sosa7c931362010-10-11 19:49:01 -0700911 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
912 root_dir = os.path.realpath('%s/../..' % devserver_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700913 serve_only = False
914
Zdenek Behan608f46c2011-02-19 00:47:16 +0100915 static_dir = os.path.realpath('%s/static' % options.data_dir)
916 os.system('mkdir -p %s' % static_dir)
917
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700918 # TODO(sosa): Remove after deprecation.
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700919 if options.vm:
920 options.patch_kernel = False
921
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700922 if options.archive_dir:
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700923 # TODO(zbehan) Remove legacy support:
924 # archive_dir is the directory where static/archive will point.
925 # If this is an absolute path, all is fine. If someone calls this
926 # using a relative path, that is relative to src/platform/dev/.
927 # That use case is unmaintainable, but since applications use it
928 # with =./static, instead of a boolean flag, we'll make this
929 # relative to devserver_dir to keep these unbroken. For now.
Zdenek Behan608f46c2011-02-19 00:47:16 +0100930 archive_dir = options.archive_dir
931 if not os.path.isabs(archive_dir):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700932 archive_dir = os.path.realpath(os.path.join(devserver_dir, archive_dir))
Zdenek Behan608f46c2011-02-19 00:47:16 +0100933 _PrepareToServeUpdatesOnly(archive_dir, static_dir)
Zdenek Behan6d93e552011-03-02 22:35:49 +0100934 static_dir = os.path.realpath(archive_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700935 serve_only = True
Chris Sosa0356d3b2010-09-16 15:46:22 -0700936
Don Garrettf90edf02010-11-16 17:36:14 -0800937 cache_dir = os.path.join(static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700938 # If our devserver is only supposed to serve payloads, we shouldn't be
939 # mucking with the cache at all. If the devserver hadn't previously
940 # generated a cache and is expected, the caller is using it wrong.
Chris Sosadbc20082012-12-10 13:39:11 -0800941 if serve_only:
942 # Extra check to make sure we're not being called incorrectly.
943 if (options.clear_cache or options.exit or options.pregenerate_update or
944 options.board or options.image):
945 parser.error('Incompatible flags detected for serve_only mode.')
Chris Sosadbc20082012-12-10 13:39:11 -0800946 elif os.path.exists(cache_dir):
947 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -0800948 else:
949 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800950
Chris Sosadbc20082012-12-10 13:39:11 -0800951 _Log('Using cache directory %s' % cache_dir)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700952 _Log('Data dir is %s' % options.data_dir)
953 _Log('Source root is %s' % root_dir)
954 _Log('Serving from %s' % static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +0000955
Chris Sosa6a3697f2013-01-29 16:44:43 -0800956 # We allow global use here to share with cherrypy classes.
957 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -0700958 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -0700959 updater = autoupdate.Autoupdate(
960 root_dir=root_dir,
961 static_dir=static_dir,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700962 serve_only=serve_only,
Andrew de los Reyes52620802010-04-12 13:40:07 -0700963 urlbase=options.urlbase,
964 test_image=options.test_image,
Chris Sosa5d342a22010-09-28 16:54:41 -0700965 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700966 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -0800967 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -0700968 src_image=options.src_image,
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700969 patch_kernel=options.patch_kernel,
Chris Sosa08d55a22011-01-19 16:08:02 -0800970 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800971 copy_to_static_root=not options.exit,
972 private_key=options.private_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800973 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700974 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700975 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -0700976 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800977 )
Chris Sosa7c931362010-10-11 19:49:01 -0700978
Chris Sosa6a3697f2013-01-29 16:44:43 -0800979 if options.pregenerate_update:
980 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700981
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700982 if options.exit:
983 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -0700984
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700985 cherrypy.quickstart(DevServerRoot(), config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -0700986
987
988if __name__ == '__main__':
989 main()