blob: bdc502b748c0880ecf1ea8e6de34eac039f9233c [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
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
Dan Shi59ae7092013-06-04 14:37:27 -0700378 # Number of threads that devserver is staging images.
379 _staging_thread_count = 0
380 # Lock used to lock increasing/decreasing count.
381 _staging_thread_count_lock = threading.Lock()
382
David Rochberg7c79a812011-01-19 14:24:45 -0500383 def __init__(self):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700384 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800385 self._telemetry_lock_dict = common_util.LockDict()
David Rochberg7c79a812011-01-19 14:24:45 -0500386
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700387 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500388 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700389 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700390 import builder
391 if self._builder is None:
392 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500393 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700394
Chris Sosacde6bf42012-05-31 18:36:39 -0700395 @staticmethod
396 def _canonicalize_archive_url(archive_url):
397 """Canonicalizes archive_url strings.
398
399 Raises:
400 DevserverError: if archive_url is not set.
401 """
402 if archive_url:
Chris Sosa76e44b92013-01-31 12:11:38 -0800403 if not archive_url.startswith('gs://'):
404 raise DevServerError("Archive URL isn't from Google Storage.")
405
Chris Sosacde6bf42012-05-31 18:36:39 -0700406 return archive_url.rstrip('/')
407 else:
408 raise DevServerError("Must specify an archive_url in the request")
409
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700410 @cherrypy.expose
Frank Farzanbcb571e2012-01-03 11:48:17 -0800411 def download(self, **kwargs):
412 """Downloads and archives full/delta payloads from Google Storage.
413
Chris Sosa76e44b92013-01-31 12:11:38 -0800414 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700415 This methods downloads artifacts. It may download artifacts in the
416 background in which case a caller should call wait_for_status to get
417 the status of the background artifact downloads. They should use the same
418 args passed to download.
419
Frank Farzanbcb571e2012-01-03 11:48:17 -0800420 Args:
421 archive_url: Google Storage URL for the build.
422
423 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700424 http://myhost/download?archive_url=gs://chromeos-image-archive/
425 x86-generic/R17-1208.0.0-a1-b338
Frank Farzanbcb571e2012-01-03 11:48:17 -0800426 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800427 return self.stage(archive_url=kwargs.get('archive_url'),
428 artifacts='full_payload,test_suites,stateful')
429
Dan Shi59ae7092013-06-04 14:37:27 -0700430
Chris Sosa76e44b92013-01-31 12:11:38 -0800431 @cherrypy.expose
432 def stage(self, **kwargs):
433 """Downloads and caches the artifacts from Google Storage URL.
434
435 Downloads and caches the artifacts Google Storage URL. Returns once these
436 have been downloaded on the devserver. A call to this will attempt to cache
437 non-specified artifacts in the background for the given from the given URL
438 following the principle of spatial locality. Spatial locality of different
439 artifacts is explicitly defined in the build_artifact module.
440
441 These artifacts will then be available from the static/ sub-directory of
442 the devserver.
443
444 Args:
445 archive_url: Google Storage URL for the build.
446 artifacts: Comma separated list of artifacts to download.
447
448 Example:
449 To download the autotest and test suites tarballs:
450 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
451 artifacts=autotest,test_suites
452 To download the full update payload:
453 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
454 artifacts=full_payload
455
456 For both these examples, one could find these artifacts at:
457 http://devserver_url:<port>/static/archive/<relative_path>*
458
459 Note for this example, relative path is the archive_url stripped of its
460 basename i.e. path/ in the examples above. Specific example:
461
462 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
463
464 Will get staged to:
465
466 http://devserver_url:<port>/static/archive/x86-mario-release/R26-3920.0.0
467 """
Chris Sosacde6bf42012-05-31 18:36:39 -0700468 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Chris Sosa76e44b92013-01-31 12:11:38 -0800469 artifacts = kwargs.get('artifacts', '')
470 if not artifacts:
471 raise DevServerError('No artifacts specified.')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700472
Dan Shi59ae7092013-06-04 14:37:27 -0700473 with DevServerRoot._staging_thread_count_lock:
474 DevServerRoot._staging_thread_count += 1
475 try:
476 downloader.Downloader(updater.static_dir, archive_url).Download(
477 artifacts.split(','))
478 finally:
479 with DevServerRoot._staging_thread_count_lock:
480 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800481 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700482
Dan Shi59ae7092013-06-04 14:37:27 -0700483
Chris Sosacde6bf42012-05-31 18:36:39 -0700484 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800485 def setup_telemetry(self, **kwargs):
486 """Extracts and sets up telemetry
487
488 This method goes through the telemetry deps packages, and stages them on
489 the devserver to be used by the drones and the telemetry tests.
490
491 Args:
492 archive_url: Google Storage URL for the build.
493
494 Returns:
495 Path to the source folder for the telemetry codebase once it is staged.
496 """
497 archive_url = kwargs.get('archive_url')
498 self.stage(archive_url=archive_url, artifacts='autotest')
499
500 build = '/'.join(downloader.Downloader.ParseUrl(archive_url))
501 build_path = os.path.join(updater.static_dir, build)
502 deps_path = os.path.join(build_path, 'autotest/packages')
503 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
504 src_folder = os.path.join(telemetry_path, 'src')
505
506 with self._telemetry_lock_dict.lock(telemetry_path):
507 if os.path.exists(src_folder):
508 # Telemetry is already fully stage return
509 return src_folder
510
511 common_util.MkDirP(telemetry_path)
512
513 # Copy over the required deps tar balls to the telemetry directory.
514 for dep in TELEMETRY_DEPS:
515 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700516 if not os.path.exists(dep_path):
517 # This dep does not exist (could be new), do not extract it.
518 continue
Simran Basi4baad082013-02-14 13:39:18 -0800519 try:
520 common_util.ExtractTarball(dep_path, telemetry_path)
521 except common_util.CommonUtilError as e:
522 shutil.rmtree(telemetry_path)
523 raise DevServerError(str(e))
524
525 # By default all the tarballs extract to test_src but some parts of
526 # the telemetry code specifically hardcoded to exist inside of 'src'.
527 test_src = os.path.join(telemetry_path, 'test_src')
528 try:
529 shutil.move(test_src, src_folder)
530 except shutil.Error:
531 # This can occur if src_folder already exists. Remove and retry move.
532 shutil.rmtree(src_folder)
533 raise DevServerError('Failure in telemetry setup for build %s. Appears'
534 ' that the test_src to src move failed.' % build)
535
536 return src_folder
537
538 @cherrypy.expose
Chris Sosacde6bf42012-05-31 18:36:39 -0700539 def wait_for_status(self, **kwargs):
540 """Waits for background artifacts to be downloaded from Google Storage.
541
Chris Sosa76e44b92013-01-31 12:11:38 -0800542 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosacde6bf42012-05-31 18:36:39 -0700543 Args:
544 archive_url: Google Storage URL for the build.
545
546 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700547 http://myhost/wait_for_status?archive_url=gs://chromeos-image-archive/
548 x86-generic/R17-1208.0.0-a1-b338
Chris Sosacde6bf42012-05-31 18:36:39 -0700549 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800550 return self.stage(archive_url=kwargs.get('archive_url'),
551 artifacts='full_payload,test_suites,autotest,stateful')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700552
553 @cherrypy.expose
Chris Masone816e38c2012-05-02 12:22:36 -0700554 def stage_debug(self, **kwargs):
555 """Downloads and stages debug symbol payloads from Google Storage.
556
Chris Sosa76e44b92013-01-31 12:11:38 -0800557 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
558 This methods downloads the debug symbol build artifact
559 synchronously, and then stages it for use by symbolicate_dump.
Chris Masone816e38c2012-05-02 12:22:36 -0700560
561 Args:
562 archive_url: Google Storage URL for the build.
563
564 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700565 http://myhost/stage_debug?archive_url=gs://chromeos-image-archive/
566 x86-generic/R17-1208.0.0-a1-b338
Chris Masone816e38c2012-05-02 12:22:36 -0700567 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800568 return self.stage(archive_url=kwargs.get('archive_url'),
569 artifacts='symbols')
Chris Masone816e38c2012-05-02 12:22:36 -0700570
571 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800572 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700573 """Symbolicates a minidump using pre-downloaded symbols, returns it.
574
575 Callers will need to POST to this URL with a body of MIME-type
576 "multipart/form-data".
577 The body should include a single argument, 'minidump', containing the
578 binary-formatted minidump to symbolicate.
579
Chris Masone816e38c2012-05-02 12:22:36 -0700580 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800581 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700582 minidump: The binary minidump file to symbolicate.
583 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800584 # Ensure the symbols have been staged.
585 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
586 if self.stage(archive_url=archive_url, artifacts='symbols') != 'Success':
587 raise DevServerError('Failed to stage symbols for %s' % archive_url)
588
Chris Masone816e38c2012-05-02 12:22:36 -0700589 to_return = ''
590 with tempfile.NamedTemporaryFile() as local:
591 while True:
592 data = minidump.file.read(8192)
593 if not data:
594 break
595 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800596
Chris Masone816e38c2012-05-02 12:22:36 -0700597 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800598
599 symbols_directory = os.path.join(downloader.Downloader.GetBuildDir(
600 updater.static_dir, archive_url), 'debug', 'breakpad')
601
602 stackwalk = subprocess.Popen(
603 ['minidump_stackwalk', local.name, symbols_directory],
604 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
605
Chris Masone816e38c2012-05-02 12:22:36 -0700606 to_return, error_text = stackwalk.communicate()
607 if stackwalk.returncode != 0:
608 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
609 error_text, stackwalk.returncode))
610
611 return to_return
612
613 @cherrypy.expose
Scott Zawalski16954532012-03-20 15:31:36 -0400614 def latestbuild(self, **params):
615 """Return a string representing the latest build for a given target.
616
617 Args:
618 target: The build target, typically a combination of the board and the
619 type of build e.g. x86-mario-release.
620 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
621 provided the latest RXX build will be returned.
622 Returns:
623 A string representation of the latest build if one exists, i.e.
624 R19-1993.0.0-a1-b1480.
625 An empty string if no latest could be found.
626 """
627 if not params:
628 return _PrintDocStringAsHTML(self.latestbuild)
629
630 if 'target' not in params:
631 raise cherrypy.HTTPError('500 Internal Server Error',
632 'Error: target= is required!')
633 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700634 return common_util.GetLatestBuildVersion(
Scott Zawalski16954532012-03-20 15:31:36 -0400635 updater.static_dir, params['target'],
636 milestone=params.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700637 except common_util.CommonUtilError as errmsg:
Scott Zawalski16954532012-03-20 15:31:36 -0400638 raise cherrypy.HTTPError('500 Internal Server Error', str(errmsg))
639
640 @cherrypy.expose
Scott Zawalski84a39c92012-01-13 15:12:42 -0500641 def controlfiles(self, **params):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500642 """Return a control file or a list of all known control files.
643
644 Example URL:
645 To List all control files:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500646 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500647 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500648 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 -0500649
650 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500651 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500652 control_path: If you want the contents of a control file set this
653 to the path. E.g. client/site_tests/sleeptest/control
654 Optional, if not provided return a list of control files is returned.
655 Returns:
656 Contents of a control file if control_path is provided.
657 A list of control files if no control_path is provided.
658 """
Scott Zawalski4647ce62012-01-03 17:17:28 -0500659 if not params:
660 return _PrintDocStringAsHTML(self.controlfiles)
661
Scott Zawalski84a39c92012-01-13 15:12:42 -0500662 if 'build' not in params:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500663 raise cherrypy.HTTPError('500 Internal Server Error',
Scott Zawalski84a39c92012-01-13 15:12:42 -0500664 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500665
666 if 'control_path' not in params:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700667 return common_util.GetControlFileList(
668 updater.static_dir, params['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500669 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700670 return common_util.GetControlFile(
671 updater.static_dir, params['build'], params['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800672
673 @cherrypy.expose
Gilad Arnold6f99b982012-09-12 10:49:40 -0700674 def stage_images(self, **kwargs):
675 """Downloads and stages a Chrome OS image from Google Storage.
676
Chris Sosa76e44b92013-01-31 12:11:38 -0800677 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700678 This method downloads a zipped archive from a specified GS location, then
679 extracts and stages the specified list of images and stages them under
Chris Sosa76e44b92013-01-31 12:11:38 -0800680 static/BOARD/BUILD/. Download is synchronous.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700681
682 Args:
683 archive_url: Google Storage URL for the build.
684 image_types: comma-separated list of images to download, may include
685 'test', 'recovery', and 'base'
686
687 Example URL:
688 http://myhost/stage_images?archive_url=gs://chromeos-image-archive/
689 x86-generic/R17-1208.0.0-a1-b338&image_types=test,base
690 """
Gilad Arnold6f99b982012-09-12 10:49:40 -0700691 image_types = kwargs.get('image_types').split(',')
Chris Sosa76e44b92013-01-31 12:11:38 -0800692 image_types_list = [image + '_image' for image in image_types]
693 self.stage(archive_url=kwargs.get('archive_url'), artifacts=','.join(
694 image_types_list))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700695
696 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700697 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700698 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700699 return ('Welcome to the Dev Server!<br>\n'
700 '<br>\n'
701 'Here are the available methods, click for documentation:<br>\n'
702 '<br>\n'
703 '%s' %
704 '<br>\n'.join(
705 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700706 for name in _FindExposedMethods(
707 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700708
709 @cherrypy.expose
710 def doc(self, *args):
711 """Shows the documentation for available methods / URLs.
712
713 Example:
714 http://myhost/doc/update
715 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700716 name = '/'.join(args)
717 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700718 if not method:
719 raise DevServerError("No exposed method named `%s'" % name)
720 if not method.__doc__:
721 raise DevServerError("No documentation for exposed method `%s'" % name)
722 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -0700723
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700724 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700725 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700726 """Handles an update check from a Chrome OS client.
727
728 The HTTP request should contain the standard Omaha-style XML blob. The URL
729 line may contain an additional intermediate path to the update payload.
730
731 Example:
732 http://myhost/update/optional/path/to/payload
733 """
Chris Sosa7c931362010-10-11 19:49:01 -0700734 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -0800735 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -0700736 data = cherrypy.request.rfile.read(body_length)
737 return updater.HandleUpdatePing(data, label)
738
Chris Sosa0356d3b2010-09-16 15:46:22 -0700739
Dan Shif5ce2de2013-04-25 16:06:32 -0700740 @cherrypy.expose
741 def check_health(self):
742 """Collect the health status of devserver to see if it's ready for staging.
743
744 @return: A JSON dictionary containing all or some of the following fields:
Dan Shi59ae7092013-06-04 14:37:27 -0700745 free_disk (int): free disk space in GB
746 staging_thread_count (int): number of devserver threads currently
747 staging an image
Dan Shif5ce2de2013-04-25 16:06:32 -0700748 """
749 # Get free disk space.
750 stat = os.statvfs(updater.static_dir)
751 free_disk = stat.f_bsize * stat.f_bavail / 1000000000
752
753 return json.dumps({
754 'free_disk': free_disk,
Dan Shi59ae7092013-06-04 14:37:27 -0700755 'staging_thread_count': DevServerRoot._staging_thread_count,
Dan Shif5ce2de2013-04-25 16:06:32 -0700756 })
757
758
Chris Sosadbc20082012-12-10 13:39:11 -0800759def _CleanCache(cache_dir, wipe):
760 """Wipes any excess cached items in the cache_dir.
761
762 Args:
763 cache_dir: the directory we are wiping from.
764 wipe: If True, wipe all the contents -- not just the excess.
765 """
766 if wipe:
767 # Clear the cache and exit on error.
768 cmd = 'rm -rf %s/*' % cache_dir
769 if os.system(cmd) != 0:
770 _Log('Failed to clear the cache with %s' % cmd)
771 sys.exit(1)
772 else:
773 # Clear all but the last N cached updates
774 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
775 (cache_dir, CACHED_ENTRIES))
776 if os.system(cmd) != 0:
777 _Log('Failed to clean up old delta cache files with %s' % cmd)
778 sys.exit(1)
779
780
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700781def _AddTestingOptions(parser):
782 group = optparse.OptionGroup(
783 parser, 'Advanced Testing Options', 'These are used by test scripts and '
784 'developers writing integration tests utilizing the devserver. They are '
785 'not intended to be really used outside the scope of someone '
786 'knowledgable about the test.')
787 group.add_option('--exit',
788 action='store_true',
789 help='do not start the server (yet pregenerate/clear cache)')
790 group.add_option('--host_log',
791 action='store_true', default=False,
792 help='record history of host update events (/api/hostlog)')
793 group.add_option('--max_updates',
794 metavar='NUM', default= -1, type='int',
795 help='maximum number of update checks handled positively '
796 '(default: unlimited)')
797 group.add_option('--private_key',
798 metavar='PATH', default=None,
799 help='path to the private key in pem format. If this is set '
800 'the devserver will generate update payloads that are '
801 'signed with this key.')
802 group.add_option('--proxy_port',
803 metavar='PORT', default=None, type='int',
804 help='port to have the client connect to -- basically the '
805 'devserver lies to the update to tell it to get the payload '
806 'from a different port that will proxy the request back to '
807 'the devserver. The proxy must be managed outside the '
808 'devserver.')
809 group.add_option('--remote_payload',
810 action='store_true', default=False,
811 help='Payload is being served from a remote machine')
812 group.add_option('-u', '--urlbase',
813 metavar='URL',
814 help='base URL for update images, other than the '
815 'devserver. Use in conjunction with remote_payload.')
816 parser.add_option_group(group)
817
818
819def _AddUpdateOptions(parser):
820 group = optparse.OptionGroup(
821 parser, 'Autoupdate Options', 'These options can be used to change '
822 'how the devserver either generates or serve update payloads. Please '
823 'note that all of these option affect how a payload is generated and so '
824 'do not work in archive-only mode.')
825 group.add_option('--board',
826 help='By default the devserver will create an update '
827 'payload from the latest image built for the board '
828 'a device that is requesting an update has. When we '
829 'pre-generate an update (see below) and we do not specify '
830 'another update_type option like image or payload, the '
831 'devserver needs to know the board to generate the latest '
832 'image for. This is that board.')
833 group.add_option('--critical_update',
834 action='store_true', default=False,
835 help='Present update payload as critical')
836 group.add_option('--for_vm',
837 dest='vm', action='store_true',
838 help='DEPRECATED: see no_patch_kernel.')
839 group.add_option('--image',
840 metavar='FILE',
841 help='Generate and serve an update using this image to any '
842 'device that requests an update.')
843 group.add_option('--no_patch_kernel',
844 dest='patch_kernel', action='store_false', default=True,
845 help='When generating an update payload, do not patch the '
846 'kernel with kernel verification blob from the stateful '
847 'partition.')
848 group.add_option('--payload',
849 metavar='PATH',
850 help='use the update payload from specified directory '
851 '(update.gz).')
852 group.add_option('-p', '--pregenerate_update',
853 action='store_true', default=False,
854 help='pre-generate the update payload before accepting '
855 'update requests. Useful to help debug payload generation '
856 'issues quickly. Also if an update payload will take a '
857 'long time to generate, a client may timeout if you do not'
858 'pregenerate the update.')
859 group.add_option('--src_image',
860 metavar='PATH', default='',
861 help='If specified, delta updates will be generated using '
862 'this image as the source image. Delta updates are when '
863 'you are updating from a "source image" to a another '
864 'image.')
865 parser.add_option_group(group)
866
867
868def _AddProductionOptions(parser):
869 group = optparse.OptionGroup(
870 parser, 'Advanced Server Options', 'These options can be used to changed '
871 'for advanced server behavior.')
872 # TODO(sosa): Clean up the fact we have archive_dir and data_dir. It's ugly.
873 # Should be --archive_mode + optional data_dir.
874 group.add_option('--archive_dir',
875 metavar='PATH',
876 help='Enables archive-only mode. This disables any '
877 'update or package generation related functionality. This '
878 'mode also works without a Chromium OS chroot.')
879 group.add_option('--clear_cache',
880 action='store_true', default=False,
881 help='At startup, removes all cached entries from the'
882 'devserver\'s cache.')
883 group.add_option('--logfile',
884 metavar='PATH',
885 help='log output to this file instead of stdout')
886 group.add_option('--production',
887 action='store_true', default=False,
888 help='have the devserver use production values when '
889 'starting up. This includes using more threads and '
890 'performing less logging.')
891 parser.add_option_group(group)
892
893
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700894def _MakeLogHandler(logfile):
895 """Create a LogHandler instance used to log all messages."""
896 hdlr_cls = handlers.TimedRotatingFileHandler
897 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
898 backupCount=_LOG_ROTATION_BACKUP)
899 # The cherrypy documentation says to use the _cplogging module for
900 # this, even though it's named as a private module.
901 # pylint: disable=W0212
902 hdlr.setFormatter(cherrypy._cplogging.logfmt)
903 return hdlr
904
905
Chris Sosacde6bf42012-05-31 18:36:39 -0700906def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700907 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -0800908 parser = optparse.OptionParser(usage=usage)
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700909 parser.add_option('--data_dir',
910 metavar='PATH',
911 default=os.path.dirname(os.path.abspath(sys.argv[0])),
912 help='writable directory where static lives')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700913 parser.add_option('--port',
914 default=8080, type='int',
915 help='port for the dev server to use (default: 8080)')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700916 parser.add_option('-t', '--test_image',
917 action='store_true',
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700918 help='If set, look for the chromiumos_test_image.bin file '
919 'when generating update payloads rather than the '
920 'chromiumos_image.bin which is the default.')
921 _AddProductionOptions(parser)
922 _AddUpdateOptions(parser)
923 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -0700924 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +0000925
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700926 # Handle options that must be set globally in cherrypy. Do this
927 # work up front, because calls to _Log() below depend on this
928 # initialization.
929 if options.production:
930 cherrypy.config.update({'environment': 'production'})
931 if not options.logfile:
932 cherrypy.config.update({'log.screen': True})
933 else:
934 cherrypy.config.update({'log.error_file': '',
935 'log.access_file': ''})
936 hdlr = _MakeLogHandler(options.logfile)
937 # Pylint can't seem to process these two calls properly
938 # pylint: disable=E1101
939 cherrypy.log.access_log.addHandler(hdlr)
940 cherrypy.log.error_log.addHandler(hdlr)
941 # pylint: enable=E1101
942
Chris Sosa7c931362010-10-11 19:49:01 -0700943 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
944 root_dir = os.path.realpath('%s/../..' % devserver_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700945 serve_only = False
946
Zdenek Behan608f46c2011-02-19 00:47:16 +0100947 static_dir = os.path.realpath('%s/static' % options.data_dir)
948 os.system('mkdir -p %s' % static_dir)
949
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700950 # TODO(sosa): Remove after deprecation.
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700951 if options.vm:
952 options.patch_kernel = False
953
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700954 if options.archive_dir:
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700955 # TODO(zbehan) Remove legacy support:
956 # archive_dir is the directory where static/archive will point.
957 # If this is an absolute path, all is fine. If someone calls this
958 # using a relative path, that is relative to src/platform/dev/.
959 # That use case is unmaintainable, but since applications use it
960 # with =./static, instead of a boolean flag, we'll make this
961 # relative to devserver_dir to keep these unbroken. For now.
Zdenek Behan608f46c2011-02-19 00:47:16 +0100962 archive_dir = options.archive_dir
963 if not os.path.isabs(archive_dir):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700964 archive_dir = os.path.realpath(os.path.join(devserver_dir, archive_dir))
Zdenek Behan608f46c2011-02-19 00:47:16 +0100965 _PrepareToServeUpdatesOnly(archive_dir, static_dir)
Zdenek Behan6d93e552011-03-02 22:35:49 +0100966 static_dir = os.path.realpath(archive_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -0700967 serve_only = True
Chris Sosa0356d3b2010-09-16 15:46:22 -0700968
Don Garrettf90edf02010-11-16 17:36:14 -0800969 cache_dir = os.path.join(static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700970 # If our devserver is only supposed to serve payloads, we shouldn't be
971 # mucking with the cache at all. If the devserver hadn't previously
972 # generated a cache and is expected, the caller is using it wrong.
Chris Sosadbc20082012-12-10 13:39:11 -0800973 if serve_only:
974 # Extra check to make sure we're not being called incorrectly.
975 if (options.clear_cache or options.exit or options.pregenerate_update or
976 options.board or options.image):
977 parser.error('Incompatible flags detected for serve_only mode.')
Chris Sosadbc20082012-12-10 13:39:11 -0800978 elif os.path.exists(cache_dir):
979 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -0800980 else:
981 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -0800982
Chris Sosadbc20082012-12-10 13:39:11 -0800983 _Log('Using cache directory %s' % cache_dir)
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700984 _Log('Data dir is %s' % options.data_dir)
985 _Log('Source root is %s' % root_dir)
986 _Log('Serving from %s' % static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +0000987
Chris Sosa6a3697f2013-01-29 16:44:43 -0800988 # We allow global use here to share with cherrypy classes.
989 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -0700990 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -0700991 updater = autoupdate.Autoupdate(
992 root_dir=root_dir,
993 static_dir=static_dir,
Chris Sosa0356d3b2010-09-16 15:46:22 -0700994 serve_only=serve_only,
Andrew de los Reyes52620802010-04-12 13:40:07 -0700995 urlbase=options.urlbase,
996 test_image=options.test_image,
Chris Sosa5d342a22010-09-28 16:54:41 -0700997 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -0700998 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -0800999 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -07001000 src_image=options.src_image,
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001001 patch_kernel=options.patch_kernel,
Chris Sosa08d55a22011-01-19 16:08:02 -08001002 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001003 copy_to_static_root=not options.exit,
1004 private_key=options.private_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001005 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001006 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001007 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001008 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001009 )
Chris Sosa7c931362010-10-11 19:49:01 -07001010
Chris Sosa6a3697f2013-01-29 16:44:43 -08001011 if options.pregenerate_update:
1012 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -07001013
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001014 if options.exit:
1015 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001016
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001017 cherrypy.quickstart(DevServerRoot(), config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001018
1019
1020if __name__ == '__main__':
1021 main()