blob: 238a65de9eb667b793ec5f144d00db0657020c08 [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
Dan Shif5ce2de2013-04-25 16:06:32 -0700727 @cherrypy.expose
728 def check_health(self):
729 """Collect the health status of devserver to see if it's ready for staging.
730
731 @return: A JSON dictionary containing all or some of the following fields:
732 free_disk (int): free disk space in GB
733 """
734 # Get free disk space.
735 stat = os.statvfs(updater.static_dir)
736 free_disk = stat.f_bsize * stat.f_bavail / 1000000000
737
738 return json.dumps({
739 'free_disk': free_disk,
740 })
741
742
Chris Sosadbc20082012-12-10 13:39:11 -0800743def _CleanCache(cache_dir, wipe):
744 """Wipes any excess cached items in the cache_dir.
745
746 Args:
747 cache_dir: the directory we are wiping from.
748 wipe: If True, wipe all the contents -- not just the excess.
749 """
750 if wipe:
751 # Clear the cache and exit on error.
752 cmd = 'rm -rf %s/*' % cache_dir
753 if os.system(cmd) != 0:
754 _Log('Failed to clear the cache with %s' % cmd)
755 sys.exit(1)
756 else:
757 # Clear all but the last N cached updates
758 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
759 (cache_dir, CACHED_ENTRIES))
760 if os.system(cmd) != 0:
761 _Log('Failed to clean up old delta cache files with %s' % cmd)
762 sys.exit(1)
763
764
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700765def _AddTestingOptions(parser):
766 group = optparse.OptionGroup(
767 parser, 'Advanced Testing Options', 'These are used by test scripts and '
768 'developers writing integration tests utilizing the devserver. They are '
769 'not intended to be really used outside the scope of someone '
770 'knowledgable about the test.')
771 group.add_option('--exit',
772 action='store_true',
773 help='do not start the server (yet pregenerate/clear cache)')
774 group.add_option('--host_log',
775 action='store_true', default=False,
776 help='record history of host update events (/api/hostlog)')
777 group.add_option('--max_updates',
778 metavar='NUM', default= -1, type='int',
779 help='maximum number of update checks handled positively '
780 '(default: unlimited)')
781 group.add_option('--private_key',
782 metavar='PATH', default=None,
783 help='path to the private key in pem format. If this is set '
784 'the devserver will generate update payloads that are '
785 'signed with this key.')
786 group.add_option('--proxy_port',
787 metavar='PORT', default=None, type='int',
788 help='port to have the client connect to -- basically the '
789 'devserver lies to the update to tell it to get the payload '
790 'from a different port that will proxy the request back to '
791 'the devserver. The proxy must be managed outside the '
792 'devserver.')
793 group.add_option('--remote_payload',
794 action='store_true', default=False,
795 help='Payload is being served from a remote machine')
796 group.add_option('-u', '--urlbase',
797 metavar='URL',
798 help='base URL for update images, other than the '
799 'devserver. Use in conjunction with remote_payload.')
800 parser.add_option_group(group)
801
802
803def _AddUpdateOptions(parser):
804 group = optparse.OptionGroup(
805 parser, 'Autoupdate Options', 'These options can be used to change '
806 'how the devserver either generates or serve update payloads. Please '
807 'note that all of these option affect how a payload is generated and so '
808 'do not work in archive-only mode.')
809 group.add_option('--board',
810 help='By default the devserver will create an update '
811 'payload from the latest image built for the board '
812 'a device that is requesting an update has. When we '
813 'pre-generate an update (see below) and we do not specify '
814 'another update_type option like image or payload, the '
815 'devserver needs to know the board to generate the latest '
816 'image for. This is that board.')
817 group.add_option('--critical_update',
818 action='store_true', default=False,
819 help='Present update payload as critical')
820 group.add_option('--for_vm',
821 dest='vm', action='store_true',
822 help='DEPRECATED: see no_patch_kernel.')
823 group.add_option('--image',
824 metavar='FILE',
825 help='Generate and serve an update using this image to any '
826 'device that requests an update.')
827 group.add_option('--no_patch_kernel',
828 dest='patch_kernel', action='store_false', default=True,
829 help='When generating an update payload, do not patch the '
830 'kernel with kernel verification blob from the stateful '
831 'partition.')
832 group.add_option('--payload',
833 metavar='PATH',
834 help='use the update payload from specified directory '
835 '(update.gz).')
836 group.add_option('-p', '--pregenerate_update',
837 action='store_true', default=False,
838 help='pre-generate the update payload before accepting '
839 'update requests. Useful to help debug payload generation '
840 'issues quickly. Also if an update payload will take a '
841 'long time to generate, a client may timeout if you do not'
842 'pregenerate the update.')
843 group.add_option('--src_image',
844 metavar='PATH', default='',
845 help='If specified, delta updates will be generated using '
846 'this image as the source image. Delta updates are when '
847 'you are updating from a "source image" to a another '
848 'image.')
849 parser.add_option_group(group)
850
851
852def _AddProductionOptions(parser):
853 group = optparse.OptionGroup(
854 parser, 'Advanced Server Options', 'These options can be used to changed '
855 'for advanced server behavior.')
856 # TODO(sosa): Clean up the fact we have archive_dir and data_dir. It's ugly.
857 # Should be --archive_mode + optional data_dir.
858 group.add_option('--archive_dir',
859 metavar='PATH',
860 help='Enables archive-only mode. This disables any '
861 'update or package generation related functionality. This '
862 'mode also works without a Chromium OS chroot.')
863 group.add_option('--clear_cache',
864 action='store_true', default=False,
865 help='At startup, removes all cached entries from the'
866 'devserver\'s cache.')
867 group.add_option('--logfile',
868 metavar='PATH',
869 help='log output to this file instead of stdout')
870 group.add_option('--production',
871 action='store_true', default=False,
872 help='have the devserver use production values when '
873 'starting up. This includes using more threads and '
874 'performing less logging.')
875 parser.add_option_group(group)
876
877
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700878def _MakeLogHandler(logfile):
879 """Create a LogHandler instance used to log all messages."""
880 hdlr_cls = handlers.TimedRotatingFileHandler
881 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
882 backupCount=_LOG_ROTATION_BACKUP)
883 # The cherrypy documentation says to use the _cplogging module for
884 # this, even though it's named as a private module.
885 # pylint: disable=W0212
886 hdlr.setFormatter(cherrypy._cplogging.logfmt)
887 return hdlr
888
889
Chris Sosacde6bf42012-05-31 18:36:39 -0700890def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700891 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -0800892 parser = optparse.OptionParser(usage=usage)
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700893 parser.add_option('--data_dir',
894 metavar='PATH',
895 default=os.path.dirname(os.path.abspath(sys.argv[0])),
896 help='writable directory where static lives')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700897 parser.add_option('--port',
898 default=8080, type='int',
899 help='port for the dev server to use (default: 8080)')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700900 parser.add_option('-t', '--test_image',
901 action='store_true',
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700902 help='If set, look for the chromiumos_test_image.bin file '
903 'when generating update payloads rather than the '
904 'chromiumos_image.bin which is the default.')
905 _AddProductionOptions(parser)
906 _AddUpdateOptions(parser)
907 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -0700908 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +0000909
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700910 # Handle options that must be set globally in cherrypy. Do this
911 # work up front, because calls to _Log() below depend on this
912 # initialization.
913 if options.production:
914 cherrypy.config.update({'environment': 'production'})
915 if not options.logfile:
916 cherrypy.config.update({'log.screen': True})
917 else:
918 cherrypy.config.update({'log.error_file': '',
919 'log.access_file': ''})
920 hdlr = _MakeLogHandler(options.logfile)
921 # Pylint can't seem to process these two calls properly
922 # pylint: disable=E1101
923 cherrypy.log.access_log.addHandler(hdlr)
924 cherrypy.log.error_log.addHandler(hdlr)
925 # pylint: enable=E1101
926
Chris Sosa7c931362010-10-11 19:49:01 -0700927 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
928 root_dir = os.path.realpath('%s/../..' % devserver_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700929 serve_only = False
930
Zdenek Behan608f46c2011-02-19 00:47:16 +0100931 static_dir = os.path.realpath('%s/static' % options.data_dir)
932 os.system('mkdir -p %s' % static_dir)
933
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700934 # TODO(sosa): Remove after deprecation.
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700935 if options.vm:
936 options.patch_kernel = False
937
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700938 if options.archive_dir:
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700939 # TODO(zbehan) Remove legacy support:
940 # archive_dir is the directory where static/archive will point.
941 # If this is an absolute path, all is fine. If someone calls this
942 # using a relative path, that is relative to src/platform/dev/.
943 # That use case is unmaintainable, but since applications use it
944 # with =./static, instead of a boolean flag, we'll make this
945 # relative to devserver_dir to keep these unbroken. For now.
Zdenek Behan608f46c2011-02-19 00:47:16 +0100946 archive_dir = options.archive_dir
947 if not os.path.isabs(archive_dir):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700948 archive_dir = os.path.realpath(os.path.join(devserver_dir, archive_dir))
Zdenek Behan608f46c2011-02-19 00:47:16 +0100949 _PrepareToServeUpdatesOnly(archive_dir, static_dir)
Zdenek Behan6d93e552011-03-02 22:35:49 +0100950 static_dir = os.path.realpath(archive_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700951 serve_only = True
Chris Sosa0356d3b2010-09-16 15:46:22 -0700952
Don Garrettf90edf02010-11-16 17:36:14 -0800953 cache_dir = os.path.join(static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700954 # If our devserver is only supposed to serve payloads, we shouldn't be
955 # mucking with the cache at all. If the devserver hadn't previously
956 # generated a cache and is expected, the caller is using it wrong.
Chris Sosadbc20082012-12-10 13:39:11 -0800957 if serve_only:
958 # Extra check to make sure we're not being called incorrectly.
959 if (options.clear_cache or options.exit or options.pregenerate_update or
960 options.board or options.image):
961 parser.error('Incompatible flags detected for serve_only mode.')
Chris Sosadbc20082012-12-10 13:39:11 -0800962 elif os.path.exists(cache_dir):
963 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -0800964 else:
965 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800966
Chris Sosadbc20082012-12-10 13:39:11 -0800967 _Log('Using cache directory %s' % cache_dir)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700968 _Log('Data dir is %s' % options.data_dir)
969 _Log('Source root is %s' % root_dir)
970 _Log('Serving from %s' % static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +0000971
Chris Sosa6a3697f2013-01-29 16:44:43 -0800972 # We allow global use here to share with cherrypy classes.
973 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -0700974 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -0700975 updater = autoupdate.Autoupdate(
976 root_dir=root_dir,
977 static_dir=static_dir,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700978 serve_only=serve_only,
Andrew de los Reyes52620802010-04-12 13:40:07 -0700979 urlbase=options.urlbase,
980 test_image=options.test_image,
Chris Sosa5d342a22010-09-28 16:54:41 -0700981 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700982 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -0800983 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -0700984 src_image=options.src_image,
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700985 patch_kernel=options.patch_kernel,
Chris Sosa08d55a22011-01-19 16:08:02 -0800986 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800987 copy_to_static_root=not options.exit,
988 private_key=options.private_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -0800989 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700990 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -0700991 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -0700992 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -0800993 )
Chris Sosa7c931362010-10-11 19:49:01 -0700994
Chris Sosa6a3697f2013-01-29 16:44:43 -0800995 if options.pregenerate_update:
996 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -0700997
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700998 if options.exit:
999 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001000
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001001 cherrypy.quickstart(DevServerRoot(), config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001002
1003
1004if __name__ == '__main__':
1005 main()