blob: 661ed6f733db100d2ccf2f62ffbeaedab1e4e372 [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
Dan Shi59ae7092013-06-04 14:37:27 -070053import threading
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070054import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070055from logging import handlers
56
57import cherrypy
58import cherrypy._cplogging
rtc@google.comded22402009-10-26 22:36:21 +000059
Chris Sosa0356d3b2010-09-16 15:46:22 -070060import autoupdate
Gilad Arnoldc65330c2012-09-20 15:17:48 -070061import common_util
Chris Sosa47a7d4e2012-03-28 11:26:55 -070062import downloader
Gilad Arnoldc65330c2012-09-20 15:17:48 -070063import log_util
64
Gilad Arnoldc65330c2012-09-20 15:17:48 -070065# 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
J. Richard Barnette6dfa5342013-06-04 11:48:56 -070082# at midnight between Friday and Saturday, with about three months
83# of old logs kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -070084#
85# For more, see the documentation for
86# logging.handlers.TimedRotatingFileHandler
J. Richard Barnette6dfa5342013-06-04 11:48:56 -070087_LOG_ROTATION_TIME = 'W4'
J. Richard Barnette3d977b82013-04-23 11:05:19 -070088_LOG_ROTATION_BACKUP = 13
89
Frank Farzan40160872011-12-12 18:39:18 -080090
Chris Sosa9164ca32012-03-28 11:04:50 -070091class DevServerError(Exception):
Chris Sosa47a7d4e2012-03-28 11:26:55 -070092 """Exception class used by this module."""
93 pass
94
95
Scott Zawalski4647ce62012-01-03 17:17:28 -050096def _LeadingWhiteSpaceCount(string):
97 """Count the amount of leading whitespace in a string.
98
99 Args:
100 string: The string to count leading whitespace in.
101 Returns:
102 number of white space chars before characters start.
103 """
104 matched = re.match('^\s+', string)
105 if matched:
106 return len(matched.group())
107
108 return 0
109
110
111def _PrintDocStringAsHTML(func):
112 """Make a functions docstring somewhat HTML style.
113
114 Args:
115 func: The function to return the docstring from.
116 Returns:
117 A string that is somewhat formated for a web browser.
118 """
119 # TODO(scottz): Make this parse Args/Returns in a prettier way.
120 # Arguments could be bolded and indented etc.
121 html_doc = []
122 for line in func.__doc__.splitlines():
123 leading_space = _LeadingWhiteSpaceCount(line)
124 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700125 line = '&nbsp;' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500126
127 html_doc.append('<BR>%s' % line)
128
129 return '\n'.join(html_doc)
130
131
Chris Sosa7c931362010-10-11 19:49:01 -0700132def _GetConfig(options):
133 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800134
135 # On a system with IPv6 not compiled into the kernel,
136 # AF_INET6 sockets will return a socket.error exception.
137 # On such systems, fall-back to IPv4.
138 socket_host = '::'
139 try:
140 socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
141 except socket.error:
142 socket_host = '0.0.0.0'
143
Chris Sosa7c931362010-10-11 19:49:01 -0700144 base_config = { 'global':
145 { 'server.log_request_headers': True,
146 'server.protocol_version': 'HTTP/1.1',
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800147 'server.socket_host': socket_host,
Chris Sosa7c931362010-10-11 19:49:01 -0700148 'server.socket_port': int(options.port),
Chris Sosa374c62d2010-10-14 09:13:54 -0700149 'response.timeout': 6000,
Chris Sosa6fe23942012-07-02 15:44:46 -0700150 'request.show_tracebacks': True,
Chris Sosa72333d12012-06-13 11:28:05 -0700151 'server.socket_timeout': 60,
Zdenek Behan1347a312011-02-10 03:59:17 +0100152 'tools.staticdir.root':
153 os.path.dirname(os.path.abspath(sys.argv[0])),
Chris Sosa7c931362010-10-11 19:49:01 -0700154 },
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700155 '/api':
156 {
157 # Gets rid of cherrypy parsing post file for args.
158 'request.process_request_body': False,
159 },
Chris Sosaa1ef0102010-10-21 16:22:35 -0700160 '/build':
161 {
162 'response.timeout': 100000,
163 },
Chris Sosa7c931362010-10-11 19:49:01 -0700164 '/update':
165 {
166 # Gets rid of cherrypy parsing post file for args.
167 'request.process_request_body': False,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700168 'response.timeout': 10000,
Chris Sosa7c931362010-10-11 19:49:01 -0700169 },
170 # Sets up the static dir for file hosting.
171 '/static':
172 { 'tools.staticdir.dir': 'static',
173 'tools.staticdir.on': True,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700174 'response.timeout': 10000,
Chris Sosa7c931362010-10-11 19:49:01 -0700175 },
176 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700177 if options.production:
Chris Sosad1ea86b2012-07-12 13:35:37 -0700178 base_config['global'].update({'server.thread_pool': 75})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500179
Chris Sosa7c931362010-10-11 19:49:01 -0700180 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000181
Darin Petkove17164a2010-08-11 13:24:41 -0700182
Zdenek Behan608f46c2011-02-19 00:47:16 +0100183def _PrepareToServeUpdatesOnly(image_dir, static_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700184 """Sets up symlink to image_dir for serving purposes."""
185 assert os.path.exists(image_dir), '%s must exist.' % image_dir
186 # If we're serving out of an archived build dir (e.g. a
187 # buildbot), prepare this webserver's magic 'static/' dir with a
188 # link to the build archive.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700189 _Log('Preparing autoupdate for "serve updates only" mode.')
Zdenek Behan608f46c2011-02-19 00:47:16 +0100190 if os.path.lexists('%s/archive' % static_dir):
191 if image_dir != os.readlink('%s/archive' % static_dir):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700192 _Log('removing stale symlink to %s' % image_dir)
Zdenek Behan608f46c2011-02-19 00:47:16 +0100193 os.unlink('%s/archive' % static_dir)
194 os.symlink(image_dir, '%s/archive' % static_dir)
Chris Sosacde6bf42012-05-31 18:36:39 -0700195
Chris Sosa0356d3b2010-09-16 15:46:22 -0700196 else:
Zdenek Behan608f46c2011-02-19 00:47:16 +0100197 os.symlink(image_dir, '%s/archive' % static_dir)
Chris Sosacde6bf42012-05-31 18:36:39 -0700198
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700199 _Log('archive dir: %s ready to be used to serve images.' % image_dir)
Chris Sosa7c931362010-10-11 19:49:01 -0700200
201
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700202def _GetRecursiveMemberObject(root, member_list):
203 """Returns an object corresponding to a nested member list.
204
205 Args:
206 root: the root object to search
207 member_list: list of nested members to search
208 Returns:
209 An object corresponding to the member name list; None otherwise.
210 """
211 for member in member_list:
212 next_root = root.__class__.__dict__.get(member)
213 if not next_root:
214 return None
215 root = next_root
216 return root
217
218
219def _IsExposed(name):
220 """Returns True iff |name| has an `exposed' attribute and it is set."""
221 return hasattr(name, 'exposed') and name.exposed
222
223
Gilad Arnold748c8322012-10-12 09:51:35 -0700224def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700225 """Returns a CherryPy-exposed method, if such exists.
226
227 Args:
228 root: the root object for searching
229 nested_member: a slash-joined path to the nested member
230 ignored: method paths to be ignored
231 Returns:
232 A function object corresponding to the path defined by |member_list| from
233 the |root| object, if the function is exposed and not ignored; None
234 otherwise.
235 """
Gilad Arnold748c8322012-10-12 09:51:35 -0700236 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700237 _GetRecursiveMemberObject(root, nested_member.split('/')))
238 if (method and type(method) == types.FunctionType and _IsExposed(method)):
239 return method
240
241
Gilad Arnold748c8322012-10-12 09:51:35 -0700242def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700243 """Finds exposed CherryPy methods.
244
245 Args:
246 root: the root object for searching
247 prefix: slash-joined chain of members leading to current object
248 unlisted: URLs to be excluded regardless of their exposed status
249 Returns:
250 List of exposed URLs that are not unlisted.
251 """
252 method_list = []
253 for member in sorted(root.__class__.__dict__.keys()):
254 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700255 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700256 continue
257 member_obj = root.__class__.__dict__[member]
258 if _IsExposed(member_obj):
259 if type(member_obj) == types.FunctionType:
260 method_list.append(prefixed_member)
261 else:
262 method_list += _FindExposedMethods(
263 member_obj, prefixed_member, unlisted)
264 return method_list
265
266
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700267class ApiRoot(object):
268 """RESTful API for Dev Server information."""
269 exposed = True
270
271 @cherrypy.expose
272 def hostinfo(self, ip):
273 """Returns a JSON dictionary containing information about the given ip.
274
Gilad Arnold1b908392012-10-05 11:36:27 -0700275 Args:
276 ip: address of host whose info is requested
277 Returns:
278 A JSON dictionary containing all or some of the following fields:
279 last_event_type (int): last update event type received
280 last_event_status (int): last update event status received
281 last_known_version (string): last known version reported in update ping
282 forced_update_label (string): update label to force next update ping to
283 use, set by setnextupdate
284 See the OmahaEvent class in update_engine/omaha_request_action.h for
285 event type and status code definitions. If the ip does not exist an empty
286 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700287
Gilad Arnold1b908392012-10-05 11:36:27 -0700288 Example URL:
289 http://myhost/api/hostinfo?ip=192.168.1.5
290 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700291 return updater.HandleHostInfoPing(ip)
292
293 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800294 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700295 """Returns a JSON object containing a log of host event.
296
297 Args:
298 ip: address of host whose event log is requested, or `all'
299 Returns:
300 A JSON encoded list (log) of dictionaries (events), each of which
301 containing a `timestamp' and other event fields, as described under
302 /api/hostinfo.
303
304 Example URL:
305 http://myhost/api/hostlog?ip=192.168.1.5
306 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800307 return updater.HandleHostLogPing(ip)
308
309 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700310 def setnextupdate(self, ip):
311 """Allows the response to the next update ping from a host to be set.
312
313 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 11:36:27 -0700314 /update command.
315 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700316 body_length = int(cherrypy.request.headers['Content-Length'])
317 label = cherrypy.request.rfile.read(body_length)
318
319 if label:
320 label = label.strip()
321 if label:
322 return updater.HandleSetUpdatePing(ip, label)
323 raise cherrypy.HTTPError(400, 'No label provided.')
324
325
Gilad Arnold55a2a372012-10-02 09:46:32 -0700326 @cherrypy.expose
327 def fileinfo(self, *path_args):
328 """Returns information about a given staged file.
329
330 Args:
331 path_args: path to the file inside the server's static staging directory
332 Returns:
333 A JSON encoded dictionary with information about the said file, which may
334 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700335 size (int): the file size in bytes
336 sha1 (string): a base64 encoded SHA1 hash
337 sha256 (string): a base64 encoded SHA256 hash
338
339 Example URL:
340 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700341 """
342 file_path = os.path.join(updater.static_dir, *path_args)
343 if not os.path.exists(file_path):
344 raise DevServerError('file not found: %s' % file_path)
345 try:
346 file_size = os.path.getsize(file_path)
347 file_sha1 = common_util.GetFileSha1(file_path)
348 file_sha256 = common_util.GetFileSha256(file_path)
349 except os.error, e:
350 raise DevServerError('failed to get info for file %s: %s' %
Gilad Arnolde74b3812013-04-22 11:27:38 -0700351 (file_path, e))
352
353 is_delta = autoupdate.Autoupdate.IsDeltaFormatFile(file_path)
354
355 return json.dumps({
356 autoupdate.Autoupdate.SIZE_ATTR: file_size,
357 autoupdate.Autoupdate.SHA1_ATTR: file_sha1,
358 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
359 autoupdate.Autoupdate.ISDELTA_ATTR: is_delta
360 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700361
Chris Sosa76e44b92013-01-31 12:11:38 -0800362
David Rochberg7c79a812011-01-19 14:24:45 -0500363class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700364 """The Root Class for the Dev Server.
365
366 CherryPy works as follows:
367 For each method in this class, cherrpy interprets root/path
368 as a call to an instance of DevServerRoot->method_name. For example,
369 a call to http://myhost/build will call build. CherryPy automatically
370 parses http args and places them as keyword arguments in each method.
371 For paths http://myhost/update/dir1/dir2, you can use *args so that
372 cherrypy uses the update method and puts the extra paths in args.
373 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700374 # Method names that should not be listed on the index page.
375 _UNLISTED_METHODS = ['index', 'doc']
376
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700377 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700378
Dan Shi59ae7092013-06-04 14:37:27 -0700379 # Number of threads that devserver is staging images.
380 _staging_thread_count = 0
381 # Lock used to lock increasing/decreasing count.
382 _staging_thread_count_lock = threading.Lock()
383
David Rochberg7c79a812011-01-19 14:24:45 -0500384 def __init__(self):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700385 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800386 self._telemetry_lock_dict = common_util.LockDict()
David Rochberg7c79a812011-01-19 14:24:45 -0500387
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700388 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500389 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700390 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700391 import builder
392 if self._builder is None:
393 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500394 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700395
Chris Sosacde6bf42012-05-31 18:36:39 -0700396 @staticmethod
397 def _canonicalize_archive_url(archive_url):
398 """Canonicalizes archive_url strings.
399
400 Raises:
401 DevserverError: if archive_url is not set.
402 """
403 if archive_url:
Chris Sosa76e44b92013-01-31 12:11:38 -0800404 if not archive_url.startswith('gs://'):
405 raise DevServerError("Archive URL isn't from Google Storage.")
406
Chris Sosacde6bf42012-05-31 18:36:39 -0700407 return archive_url.rstrip('/')
408 else:
409 raise DevServerError("Must specify an archive_url in the request")
410
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700411 @cherrypy.expose
Frank Farzanbcb571e2012-01-03 11:48:17 -0800412 def download(self, **kwargs):
413 """Downloads and archives full/delta payloads from Google Storage.
414
Chris Sosa76e44b92013-01-31 12:11:38 -0800415 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700416 This methods downloads artifacts. It may download artifacts in the
417 background in which case a caller should call wait_for_status to get
418 the status of the background artifact downloads. They should use the same
419 args passed to download.
420
Frank Farzanbcb571e2012-01-03 11:48:17 -0800421 Args:
422 archive_url: Google Storage URL for the build.
423
424 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700425 http://myhost/download?archive_url=gs://chromeos-image-archive/
426 x86-generic/R17-1208.0.0-a1-b338
Frank Farzanbcb571e2012-01-03 11:48:17 -0800427 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800428 return self.stage(archive_url=kwargs.get('archive_url'),
429 artifacts='full_payload,test_suites,stateful')
430
Dan Shi59ae7092013-06-04 14:37:27 -0700431
Chris Sosa76e44b92013-01-31 12:11:38 -0800432 @cherrypy.expose
433 def stage(self, **kwargs):
434 """Downloads and caches the artifacts from Google Storage URL.
435
436 Downloads and caches the artifacts Google Storage URL. Returns once these
437 have been downloaded on the devserver. A call to this will attempt to cache
438 non-specified artifacts in the background for the given from the given URL
439 following the principle of spatial locality. Spatial locality of different
440 artifacts is explicitly defined in the build_artifact module.
441
442 These artifacts will then be available from the static/ sub-directory of
443 the devserver.
444
445 Args:
446 archive_url: Google Storage URL for the build.
447 artifacts: Comma separated list of artifacts to download.
448
449 Example:
450 To download the autotest and test suites tarballs:
451 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
452 artifacts=autotest,test_suites
453 To download the full update payload:
454 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
455 artifacts=full_payload
456
457 For both these examples, one could find these artifacts at:
458 http://devserver_url:<port>/static/archive/<relative_path>*
459
460 Note for this example, relative path is the archive_url stripped of its
461 basename i.e. path/ in the examples above. Specific example:
462
463 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
464
465 Will get staged to:
466
467 http://devserver_url:<port>/static/archive/x86-mario-release/R26-3920.0.0
468 """
Chris Sosacde6bf42012-05-31 18:36:39 -0700469 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Chris Sosa76e44b92013-01-31 12:11:38 -0800470 artifacts = kwargs.get('artifacts', '')
471 if not artifacts:
472 raise DevServerError('No artifacts specified.')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700473
Dan Shi59ae7092013-06-04 14:37:27 -0700474 with DevServerRoot._staging_thread_count_lock:
475 DevServerRoot._staging_thread_count += 1
476 try:
477 downloader.Downloader(updater.static_dir, archive_url).Download(
478 artifacts.split(','))
479 finally:
480 with DevServerRoot._staging_thread_count_lock:
481 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800482 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700483
Dan Shi59ae7092013-06-04 14:37:27 -0700484
Chris Sosacde6bf42012-05-31 18:36:39 -0700485 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800486 def setup_telemetry(self, **kwargs):
487 """Extracts and sets up telemetry
488
489 This method goes through the telemetry deps packages, and stages them on
490 the devserver to be used by the drones and the telemetry tests.
491
492 Args:
493 archive_url: Google Storage URL for the build.
494
495 Returns:
496 Path to the source folder for the telemetry codebase once it is staged.
497 """
498 archive_url = kwargs.get('archive_url')
499 self.stage(archive_url=archive_url, artifacts='autotest')
500
501 build = '/'.join(downloader.Downloader.ParseUrl(archive_url))
502 build_path = os.path.join(updater.static_dir, build)
503 deps_path = os.path.join(build_path, 'autotest/packages')
504 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
505 src_folder = os.path.join(telemetry_path, 'src')
506
507 with self._telemetry_lock_dict.lock(telemetry_path):
508 if os.path.exists(src_folder):
509 # Telemetry is already fully stage return
510 return src_folder
511
512 common_util.MkDirP(telemetry_path)
513
514 # Copy over the required deps tar balls to the telemetry directory.
515 for dep in TELEMETRY_DEPS:
516 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700517 if not os.path.exists(dep_path):
518 # This dep does not exist (could be new), do not extract it.
519 continue
Simran Basi4baad082013-02-14 13:39:18 -0800520 try:
521 common_util.ExtractTarball(dep_path, telemetry_path)
522 except common_util.CommonUtilError as e:
523 shutil.rmtree(telemetry_path)
524 raise DevServerError(str(e))
525
526 # By default all the tarballs extract to test_src but some parts of
527 # the telemetry code specifically hardcoded to exist inside of 'src'.
528 test_src = os.path.join(telemetry_path, 'test_src')
529 try:
530 shutil.move(test_src, src_folder)
531 except shutil.Error:
532 # This can occur if src_folder already exists. Remove and retry move.
533 shutil.rmtree(src_folder)
534 raise DevServerError('Failure in telemetry setup for build %s. Appears'
535 ' that the test_src to src move failed.' % build)
536
537 return src_folder
538
539 @cherrypy.expose
Chris Sosacde6bf42012-05-31 18:36:39 -0700540 def wait_for_status(self, **kwargs):
541 """Waits for background artifacts to be downloaded from Google Storage.
542
Chris Sosa76e44b92013-01-31 12:11:38 -0800543 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosacde6bf42012-05-31 18:36:39 -0700544 Args:
545 archive_url: Google Storage URL for the build.
546
547 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700548 http://myhost/wait_for_status?archive_url=gs://chromeos-image-archive/
549 x86-generic/R17-1208.0.0-a1-b338
Chris Sosacde6bf42012-05-31 18:36:39 -0700550 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800551 return self.stage(archive_url=kwargs.get('archive_url'),
552 artifacts='full_payload,test_suites,autotest,stateful')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700553
554 @cherrypy.expose
Chris Masone816e38c2012-05-02 12:22:36 -0700555 def stage_debug(self, **kwargs):
556 """Downloads and stages debug symbol payloads from Google Storage.
557
Chris Sosa76e44b92013-01-31 12:11:38 -0800558 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
559 This methods downloads the debug symbol build artifact
560 synchronously, and then stages it for use by symbolicate_dump.
Chris Masone816e38c2012-05-02 12:22:36 -0700561
562 Args:
563 archive_url: Google Storage URL for the build.
564
565 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700566 http://myhost/stage_debug?archive_url=gs://chromeos-image-archive/
567 x86-generic/R17-1208.0.0-a1-b338
Chris Masone816e38c2012-05-02 12:22:36 -0700568 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800569 return self.stage(archive_url=kwargs.get('archive_url'),
570 artifacts='symbols')
Chris Masone816e38c2012-05-02 12:22:36 -0700571
572 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800573 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700574 """Symbolicates a minidump using pre-downloaded symbols, returns it.
575
576 Callers will need to POST to this URL with a body of MIME-type
577 "multipart/form-data".
578 The body should include a single argument, 'minidump', containing the
579 binary-formatted minidump to symbolicate.
580
Chris Masone816e38c2012-05-02 12:22:36 -0700581 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800582 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700583 minidump: The binary minidump file to symbolicate.
584 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800585 # Ensure the symbols have been staged.
586 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
587 if self.stage(archive_url=archive_url, artifacts='symbols') != 'Success':
588 raise DevServerError('Failed to stage symbols for %s' % archive_url)
589
Chris Masone816e38c2012-05-02 12:22:36 -0700590 to_return = ''
591 with tempfile.NamedTemporaryFile() as local:
592 while True:
593 data = minidump.file.read(8192)
594 if not data:
595 break
596 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800597
Chris Masone816e38c2012-05-02 12:22:36 -0700598 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800599
600 symbols_directory = os.path.join(downloader.Downloader.GetBuildDir(
601 updater.static_dir, archive_url), 'debug', 'breakpad')
602
603 stackwalk = subprocess.Popen(
604 ['minidump_stackwalk', local.name, symbols_directory],
605 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
606
Chris Masone816e38c2012-05-02 12:22:36 -0700607 to_return, error_text = stackwalk.communicate()
608 if stackwalk.returncode != 0:
609 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
610 error_text, stackwalk.returncode))
611
612 return to_return
613
614 @cherrypy.expose
Scott Zawalski16954532012-03-20 15:31:36 -0400615 def latestbuild(self, **params):
616 """Return a string representing the latest build for a given target.
617
618 Args:
619 target: The build target, typically a combination of the board and the
620 type of build e.g. x86-mario-release.
621 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
622 provided the latest RXX build will be returned.
623 Returns:
624 A string representation of the latest build if one exists, i.e.
625 R19-1993.0.0-a1-b1480.
626 An empty string if no latest could be found.
627 """
628 if not params:
629 return _PrintDocStringAsHTML(self.latestbuild)
630
631 if 'target' not in params:
632 raise cherrypy.HTTPError('500 Internal Server Error',
633 'Error: target= is required!')
634 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700635 return common_util.GetLatestBuildVersion(
Scott Zawalski16954532012-03-20 15:31:36 -0400636 updater.static_dir, params['target'],
637 milestone=params.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700638 except common_util.CommonUtilError as errmsg:
Scott Zawalski16954532012-03-20 15:31:36 -0400639 raise cherrypy.HTTPError('500 Internal Server Error', str(errmsg))
640
641 @cherrypy.expose
Scott Zawalski84a39c92012-01-13 15:12:42 -0500642 def controlfiles(self, **params):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500643 """Return a control file or a list of all known control files.
644
645 Example URL:
646 To List all control files:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500647 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500648 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500649 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 -0500650
651 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500652 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500653 control_path: If you want the contents of a control file set this
654 to the path. E.g. client/site_tests/sleeptest/control
655 Optional, if not provided return a list of control files is returned.
656 Returns:
657 Contents of a control file if control_path is provided.
658 A list of control files if no control_path is provided.
659 """
Scott Zawalski4647ce62012-01-03 17:17:28 -0500660 if not params:
661 return _PrintDocStringAsHTML(self.controlfiles)
662
Scott Zawalski84a39c92012-01-13 15:12:42 -0500663 if 'build' not in params:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500664 raise cherrypy.HTTPError('500 Internal Server Error',
Scott Zawalski84a39c92012-01-13 15:12:42 -0500665 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500666
667 if 'control_path' not in params:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700668 return common_util.GetControlFileList(
669 updater.static_dir, params['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500670 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700671 return common_util.GetControlFile(
672 updater.static_dir, params['build'], params['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800673
674 @cherrypy.expose
Gilad Arnold6f99b982012-09-12 10:49:40 -0700675 def stage_images(self, **kwargs):
676 """Downloads and stages a Chrome OS image from Google Storage.
677
Chris Sosa76e44b92013-01-31 12:11:38 -0800678 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700679 This method downloads a zipped archive from a specified GS location, then
680 extracts and stages the specified list of images and stages them under
Chris Sosa76e44b92013-01-31 12:11:38 -0800681 static/BOARD/BUILD/. Download is synchronous.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700682
683 Args:
684 archive_url: Google Storage URL for the build.
685 image_types: comma-separated list of images to download, may include
686 'test', 'recovery', and 'base'
687
688 Example URL:
689 http://myhost/stage_images?archive_url=gs://chromeos-image-archive/
690 x86-generic/R17-1208.0.0-a1-b338&image_types=test,base
691 """
Gilad Arnold6f99b982012-09-12 10:49:40 -0700692 image_types = kwargs.get('image_types').split(',')
Chris Sosa76e44b92013-01-31 12:11:38 -0800693 image_types_list = [image + '_image' for image in image_types]
694 self.stage(archive_url=kwargs.get('archive_url'), artifacts=','.join(
695 image_types_list))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700696
697 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700698 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700699 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700700 return ('Welcome to the Dev Server!<br>\n'
701 '<br>\n'
702 'Here are the available methods, click for documentation:<br>\n'
703 '<br>\n'
704 '%s' %
705 '<br>\n'.join(
706 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700707 for name in _FindExposedMethods(
708 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700709
710 @cherrypy.expose
711 def doc(self, *args):
712 """Shows the documentation for available methods / URLs.
713
714 Example:
715 http://myhost/doc/update
716 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700717 name = '/'.join(args)
718 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700719 if not method:
720 raise DevServerError("No exposed method named `%s'" % name)
721 if not method.__doc__:
722 raise DevServerError("No documentation for exposed method `%s'" % name)
723 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -0700724
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700725 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700726 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700727 """Handles an update check from a Chrome OS client.
728
729 The HTTP request should contain the standard Omaha-style XML blob. The URL
730 line may contain an additional intermediate path to the update payload.
731
732 Example:
733 http://myhost/update/optional/path/to/payload
734 """
Chris Sosa7c931362010-10-11 19:49:01 -0700735 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -0800736 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -0700737 data = cherrypy.request.rfile.read(body_length)
738 return updater.HandleUpdatePing(data, label)
739
Chris Sosa0356d3b2010-09-16 15:46:22 -0700740
Dan Shif5ce2de2013-04-25 16:06:32 -0700741 @cherrypy.expose
742 def check_health(self):
743 """Collect the health status of devserver to see if it's ready for staging.
744
745 @return: A JSON dictionary containing all or some of the following fields:
Dan Shi59ae7092013-06-04 14:37:27 -0700746 free_disk (int): free disk space in GB
747 staging_thread_count (int): number of devserver threads currently
748 staging an image
Dan Shif5ce2de2013-04-25 16:06:32 -0700749 """
750 # Get free disk space.
751 stat = os.statvfs(updater.static_dir)
752 free_disk = stat.f_bsize * stat.f_bavail / 1000000000
753
754 return json.dumps({
755 'free_disk': free_disk,
Dan Shi59ae7092013-06-04 14:37:27 -0700756 'staging_thread_count': DevServerRoot._staging_thread_count,
Dan Shif5ce2de2013-04-25 16:06:32 -0700757 })
758
759
Chris Sosadbc20082012-12-10 13:39:11 -0800760def _CleanCache(cache_dir, wipe):
761 """Wipes any excess cached items in the cache_dir.
762
763 Args:
764 cache_dir: the directory we are wiping from.
765 wipe: If True, wipe all the contents -- not just the excess.
766 """
767 if wipe:
768 # Clear the cache and exit on error.
769 cmd = 'rm -rf %s/*' % cache_dir
770 if os.system(cmd) != 0:
771 _Log('Failed to clear the cache with %s' % cmd)
772 sys.exit(1)
773 else:
774 # Clear all but the last N cached updates
775 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
776 (cache_dir, CACHED_ENTRIES))
777 if os.system(cmd) != 0:
778 _Log('Failed to clean up old delta cache files with %s' % cmd)
779 sys.exit(1)
780
781
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700782def _AddTestingOptions(parser):
783 group = optparse.OptionGroup(
784 parser, 'Advanced Testing Options', 'These are used by test scripts and '
785 'developers writing integration tests utilizing the devserver. They are '
786 'not intended to be really used outside the scope of someone '
787 'knowledgable about the test.')
788 group.add_option('--exit',
789 action='store_true',
790 help='do not start the server (yet pregenerate/clear cache)')
791 group.add_option('--host_log',
792 action='store_true', default=False,
793 help='record history of host update events (/api/hostlog)')
794 group.add_option('--max_updates',
795 metavar='NUM', default= -1, type='int',
796 help='maximum number of update checks handled positively '
797 '(default: unlimited)')
798 group.add_option('--private_key',
799 metavar='PATH', default=None,
800 help='path to the private key in pem format. If this is set '
801 'the devserver will generate update payloads that are '
802 'signed with this key.')
803 group.add_option('--proxy_port',
804 metavar='PORT', default=None, type='int',
805 help='port to have the client connect to -- basically the '
806 'devserver lies to the update to tell it to get the payload '
807 'from a different port that will proxy the request back to '
808 'the devserver. The proxy must be managed outside the '
809 'devserver.')
810 group.add_option('--remote_payload',
811 action='store_true', default=False,
812 help='Payload is being served from a remote machine')
813 group.add_option('-u', '--urlbase',
814 metavar='URL',
815 help='base URL for update images, other than the '
816 'devserver. Use in conjunction with remote_payload.')
817 parser.add_option_group(group)
818
819
820def _AddUpdateOptions(parser):
821 group = optparse.OptionGroup(
822 parser, 'Autoupdate Options', 'These options can be used to change '
823 'how the devserver either generates or serve update payloads. Please '
824 'note that all of these option affect how a payload is generated and so '
825 'do not work in archive-only mode.')
826 group.add_option('--board',
827 help='By default the devserver will create an update '
828 'payload from the latest image built for the board '
829 'a device that is requesting an update has. When we '
830 'pre-generate an update (see below) and we do not specify '
831 'another update_type option like image or payload, the '
832 'devserver needs to know the board to generate the latest '
833 'image for. This is that board.')
834 group.add_option('--critical_update',
835 action='store_true', default=False,
836 help='Present update payload as critical')
837 group.add_option('--for_vm',
838 dest='vm', action='store_true',
839 help='DEPRECATED: see no_patch_kernel.')
840 group.add_option('--image',
841 metavar='FILE',
842 help='Generate and serve an update using this image to any '
843 'device that requests an update.')
844 group.add_option('--no_patch_kernel',
845 dest='patch_kernel', action='store_false', default=True,
846 help='When generating an update payload, do not patch the '
847 'kernel with kernel verification blob from the stateful '
848 'partition.')
849 group.add_option('--payload',
850 metavar='PATH',
851 help='use the update payload from specified directory '
852 '(update.gz).')
853 group.add_option('-p', '--pregenerate_update',
854 action='store_true', default=False,
855 help='pre-generate the update payload before accepting '
856 'update requests. Useful to help debug payload generation '
857 'issues quickly. Also if an update payload will take a '
858 'long time to generate, a client may timeout if you do not'
859 'pregenerate the update.')
860 group.add_option('--src_image',
861 metavar='PATH', default='',
862 help='If specified, delta updates will be generated using '
863 'this image as the source image. Delta updates are when '
864 'you are updating from a "source image" to a another '
865 'image.')
866 parser.add_option_group(group)
867
868
869def _AddProductionOptions(parser):
870 group = optparse.OptionGroup(
871 parser, 'Advanced Server Options', 'These options can be used to changed '
872 'for advanced server behavior.')
873 # TODO(sosa): Clean up the fact we have archive_dir and data_dir. It's ugly.
874 # Should be --archive_mode + optional data_dir.
875 group.add_option('--archive_dir',
876 metavar='PATH',
877 help='Enables archive-only mode. This disables any '
878 'update or package generation related functionality. This '
879 'mode also works without a Chromium OS chroot.')
880 group.add_option('--clear_cache',
881 action='store_true', default=False,
882 help='At startup, removes all cached entries from the'
883 'devserver\'s cache.')
884 group.add_option('--logfile',
885 metavar='PATH',
886 help='log output to this file instead of stdout')
887 group.add_option('--production',
888 action='store_true', default=False,
889 help='have the devserver use production values when '
890 'starting up. This includes using more threads and '
891 'performing less logging.')
892 parser.add_option_group(group)
893
894
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700895def _MakeLogHandler(logfile):
896 """Create a LogHandler instance used to log all messages."""
897 hdlr_cls = handlers.TimedRotatingFileHandler
898 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
899 backupCount=_LOG_ROTATION_BACKUP)
900 # The cherrypy documentation says to use the _cplogging module for
901 # this, even though it's named as a private module.
902 # pylint: disable=W0212
903 hdlr.setFormatter(cherrypy._cplogging.logfmt)
904 return hdlr
905
906
Chris Sosacde6bf42012-05-31 18:36:39 -0700907def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700908 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -0800909 parser = optparse.OptionParser(usage=usage)
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700910 parser.add_option('--data_dir',
911 metavar='PATH',
912 default=os.path.dirname(os.path.abspath(sys.argv[0])),
913 help='writable directory where static lives')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700914 parser.add_option('--port',
915 default=8080, type='int',
916 help='port for the dev server to use (default: 8080)')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700917 parser.add_option('-t', '--test_image',
918 action='store_true',
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700919 help='If set, look for the chromiumos_test_image.bin file '
920 'when generating update payloads rather than the '
921 'chromiumos_image.bin which is the default.')
922 _AddProductionOptions(parser)
923 _AddUpdateOptions(parser)
924 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -0700925 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +0000926
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700927 # Handle options that must be set globally in cherrypy. Do this
928 # work up front, because calls to _Log() below depend on this
929 # initialization.
930 if options.production:
931 cherrypy.config.update({'environment': 'production'})
932 if not options.logfile:
933 cherrypy.config.update({'log.screen': True})
934 else:
935 cherrypy.config.update({'log.error_file': '',
936 'log.access_file': ''})
937 hdlr = _MakeLogHandler(options.logfile)
938 # Pylint can't seem to process these two calls properly
939 # pylint: disable=E1101
940 cherrypy.log.access_log.addHandler(hdlr)
941 cherrypy.log.error_log.addHandler(hdlr)
942 # pylint: enable=E1101
943
Chris Sosa7c931362010-10-11 19:49:01 -0700944 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
945 root_dir = os.path.realpath('%s/../..' % devserver_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700946 serve_only = False
947
Zdenek Behan608f46c2011-02-19 00:47:16 +0100948 static_dir = os.path.realpath('%s/static' % options.data_dir)
949 os.system('mkdir -p %s' % static_dir)
950
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700951 # TODO(sosa): Remove after deprecation.
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700952 if options.vm:
953 options.patch_kernel = False
954
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700955 if options.archive_dir:
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700956 # TODO(zbehan) Remove legacy support:
957 # archive_dir is the directory where static/archive will point.
958 # If this is an absolute path, all is fine. If someone calls this
959 # using a relative path, that is relative to src/platform/dev/.
960 # That use case is unmaintainable, but since applications use it
961 # with =./static, instead of a boolean flag, we'll make this
962 # relative to devserver_dir to keep these unbroken. For now.
Zdenek Behan608f46c2011-02-19 00:47:16 +0100963 archive_dir = options.archive_dir
964 if not os.path.isabs(archive_dir):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700965 archive_dir = os.path.realpath(os.path.join(devserver_dir, archive_dir))
Zdenek Behan608f46c2011-02-19 00:47:16 +0100966 _PrepareToServeUpdatesOnly(archive_dir, static_dir)
Zdenek Behan6d93e552011-03-02 22:35:49 +0100967 static_dir = os.path.realpath(archive_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700968 serve_only = True
Chris Sosa0356d3b2010-09-16 15:46:22 -0700969
Don Garrettf90edf02010-11-16 17:36:14 -0800970 cache_dir = os.path.join(static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700971 # If our devserver is only supposed to serve payloads, we shouldn't be
972 # mucking with the cache at all. If the devserver hadn't previously
973 # generated a cache and is expected, the caller is using it wrong.
Chris Sosadbc20082012-12-10 13:39:11 -0800974 if serve_only:
975 # Extra check to make sure we're not being called incorrectly.
976 if (options.clear_cache or options.exit or options.pregenerate_update or
977 options.board or options.image):
978 parser.error('Incompatible flags detected for serve_only mode.')
Chris Sosadbc20082012-12-10 13:39:11 -0800979 elif os.path.exists(cache_dir):
980 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -0800981 else:
982 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800983
Chris Sosadbc20082012-12-10 13:39:11 -0800984 _Log('Using cache directory %s' % cache_dir)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700985 _Log('Data dir is %s' % options.data_dir)
986 _Log('Source root is %s' % root_dir)
987 _Log('Serving from %s' % static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +0000988
Chris Sosa6a3697f2013-01-29 16:44:43 -0800989 # We allow global use here to share with cherrypy classes.
990 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -0700991 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -0700992 updater = autoupdate.Autoupdate(
993 root_dir=root_dir,
994 static_dir=static_dir,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700995 serve_only=serve_only,
Andrew de los Reyes52620802010-04-12 13:40:07 -0700996 urlbase=options.urlbase,
997 test_image=options.test_image,
Chris Sosa5d342a22010-09-28 16:54:41 -0700998 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700999 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001000 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -07001001 src_image=options.src_image,
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001002 patch_kernel=options.patch_kernel,
Chris Sosa08d55a22011-01-19 16:08:02 -08001003 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001004 copy_to_static_root=not options.exit,
1005 private_key=options.private_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001006 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001007 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001008 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001009 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001010 )
Chris Sosa7c931362010-10-11 19:49:01 -07001011
Chris Sosa6a3697f2013-01-29 16:44:43 -08001012 if options.pregenerate_update:
1013 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -07001014
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001015 if options.exit:
1016 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001017
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001018 cherrypy.quickstart(DevServerRoot(), config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001019
1020
1021if __name__ == '__main__':
1022 main()