blob: b80744b4a23c4e7f148d6e02e3c216dd5b5de022 [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
joychen3cb228e2013-06-12 12:13:13 -070064import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070065
Gilad Arnoldc65330c2012-09-20 15:17:48 -070066# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080067def _Log(message, *args):
68 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 15:46:22 -070069
Frank Farzan40160872011-12-12 18:39:18 -080070
Chris Sosa417e55d2011-01-25 16:40:48 -080071CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080072
Simran Basi4baad082013-02-14 13:39:18 -080073TELEMETRY_FOLDER = 'telemetry_src'
74TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
75 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070076 'dep-chrome_test.tar.bz2',
77 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -080078
Chris Sosa0356d3b2010-09-16 15:46:22 -070079# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +000080updater = None
rtc@google.comded22402009-10-26 22:36:21 +000081
J. Richard Barnette3d977b82013-04-23 11:05:19 -070082# Log rotation parameters. These settings correspond to once a week
J. Richard Barnette6dfa5342013-06-04 11:48:56 -070083# at midnight between Friday and Saturday, with about three months
84# of old logs kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -070085#
86# For more, see the documentation for
87# logging.handlers.TimedRotatingFileHandler
J. Richard Barnette6dfa5342013-06-04 11:48:56 -070088_LOG_ROTATION_TIME = 'W4'
J. Richard Barnette3d977b82013-04-23 11:05:19 -070089_LOG_ROTATION_BACKUP = 13
90
Frank Farzan40160872011-12-12 18:39:18 -080091
Chris Sosa9164ca32012-03-28 11:04:50 -070092class DevServerError(Exception):
Chris Sosa47a7d4e2012-03-28 11:26:55 -070093 """Exception class used by this module."""
94 pass
95
96
Scott Zawalski4647ce62012-01-03 17:17:28 -050097def _LeadingWhiteSpaceCount(string):
98 """Count the amount of leading whitespace in a string.
99
100 Args:
101 string: The string to count leading whitespace in.
102 Returns:
103 number of white space chars before characters start.
104 """
105 matched = re.match('^\s+', string)
106 if matched:
107 return len(matched.group())
108
109 return 0
110
111
112def _PrintDocStringAsHTML(func):
113 """Make a functions docstring somewhat HTML style.
114
115 Args:
116 func: The function to return the docstring from.
117 Returns:
118 A string that is somewhat formated for a web browser.
119 """
120 # TODO(scottz): Make this parse Args/Returns in a prettier way.
121 # Arguments could be bolded and indented etc.
122 html_doc = []
123 for line in func.__doc__.splitlines():
124 leading_space = _LeadingWhiteSpaceCount(line)
125 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700126 line = '&nbsp;' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500127
128 html_doc.append('<BR>%s' % line)
129
130 return '\n'.join(html_doc)
131
132
Chris Sosa7c931362010-10-11 19:49:01 -0700133def _GetConfig(options):
134 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800135
136 # On a system with IPv6 not compiled into the kernel,
137 # AF_INET6 sockets will return a socket.error exception.
138 # On such systems, fall-back to IPv4.
139 socket_host = '::'
140 try:
141 socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
142 except socket.error:
143 socket_host = '0.0.0.0'
144
Chris Sosa7c931362010-10-11 19:49:01 -0700145 base_config = { 'global':
146 { 'server.log_request_headers': True,
147 'server.protocol_version': 'HTTP/1.1',
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800148 'server.socket_host': socket_host,
Chris Sosa7c931362010-10-11 19:49:01 -0700149 'server.socket_port': int(options.port),
Chris Sosa374c62d2010-10-14 09:13:54 -0700150 'response.timeout': 6000,
Chris Sosa6fe23942012-07-02 15:44:46 -0700151 'request.show_tracebacks': True,
Chris Sosa72333d12012-06-13 11:28:05 -0700152 'server.socket_timeout': 60,
Zdenek Behan1347a312011-02-10 03:59:17 +0100153 'tools.staticdir.root':
154 os.path.dirname(os.path.abspath(sys.argv[0])),
Chris Sosa7c931362010-10-11 19:49:01 -0700155 },
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700156 '/api':
157 {
158 # Gets rid of cherrypy parsing post file for args.
159 'request.process_request_body': False,
160 },
Chris Sosaa1ef0102010-10-21 16:22:35 -0700161 '/build':
162 {
163 'response.timeout': 100000,
164 },
Chris Sosa7c931362010-10-11 19:49:01 -0700165 '/update':
166 {
167 # Gets rid of cherrypy parsing post file for args.
168 'request.process_request_body': False,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700169 'response.timeout': 10000,
Chris Sosa7c931362010-10-11 19:49:01 -0700170 },
171 # Sets up the static dir for file hosting.
172 '/static':
173 { 'tools.staticdir.dir': 'static',
174 'tools.staticdir.on': True,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700175 'response.timeout': 10000,
Chris Sosa7c931362010-10-11 19:49:01 -0700176 },
177 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700178 if options.production:
Chris Sosad1ea86b2012-07-12 13:35:37 -0700179 base_config['global'].update({'server.thread_pool': 75})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500180
Chris Sosa7c931362010-10-11 19:49:01 -0700181 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000182
Darin Petkove17164a2010-08-11 13:24:41 -0700183
Zdenek Behan608f46c2011-02-19 00:47:16 +0100184def _PrepareToServeUpdatesOnly(image_dir, static_dir):
Chris Sosa0356d3b2010-09-16 15:46:22 -0700185 """Sets up symlink to image_dir for serving purposes."""
186 assert os.path.exists(image_dir), '%s must exist.' % image_dir
187 # If we're serving out of an archived build dir (e.g. a
188 # buildbot), prepare this webserver's magic 'static/' dir with a
189 # link to the build archive.
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700190 _Log('Preparing autoupdate for "serve updates only" mode.')
Zdenek Behan608f46c2011-02-19 00:47:16 +0100191 if os.path.lexists('%s/archive' % static_dir):
192 if image_dir != os.readlink('%s/archive' % static_dir):
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700193 _Log('removing stale symlink to %s' % image_dir)
Zdenek Behan608f46c2011-02-19 00:47:16 +0100194 os.unlink('%s/archive' % static_dir)
195 os.symlink(image_dir, '%s/archive' % static_dir)
Chris Sosacde6bf42012-05-31 18:36:39 -0700196
Chris Sosa0356d3b2010-09-16 15:46:22 -0700197 else:
Zdenek Behan608f46c2011-02-19 00:47:16 +0100198 os.symlink(image_dir, '%s/archive' % static_dir)
Chris Sosacde6bf42012-05-31 18:36:39 -0700199
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700200 _Log('archive dir: %s ready to be used to serve images.' % image_dir)
Chris Sosa7c931362010-10-11 19:49:01 -0700201
202
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700203def _GetRecursiveMemberObject(root, member_list):
204 """Returns an object corresponding to a nested member list.
205
206 Args:
207 root: the root object to search
208 member_list: list of nested members to search
209 Returns:
210 An object corresponding to the member name list; None otherwise.
211 """
212 for member in member_list:
213 next_root = root.__class__.__dict__.get(member)
214 if not next_root:
215 return None
216 root = next_root
217 return root
218
219
220def _IsExposed(name):
221 """Returns True iff |name| has an `exposed' attribute and it is set."""
222 return hasattr(name, 'exposed') and name.exposed
223
224
Gilad Arnold748c8322012-10-12 09:51:35 -0700225def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700226 """Returns a CherryPy-exposed method, if such exists.
227
228 Args:
229 root: the root object for searching
230 nested_member: a slash-joined path to the nested member
231 ignored: method paths to be ignored
232 Returns:
233 A function object corresponding to the path defined by |member_list| from
234 the |root| object, if the function is exposed and not ignored; None
235 otherwise.
236 """
Gilad Arnold748c8322012-10-12 09:51:35 -0700237 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700238 _GetRecursiveMemberObject(root, nested_member.split('/')))
239 if (method and type(method) == types.FunctionType and _IsExposed(method)):
240 return method
241
242
Gilad Arnold748c8322012-10-12 09:51:35 -0700243def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700244 """Finds exposed CherryPy methods.
245
246 Args:
247 root: the root object for searching
248 prefix: slash-joined chain of members leading to current object
249 unlisted: URLs to be excluded regardless of their exposed status
250 Returns:
251 List of exposed URLs that are not unlisted.
252 """
253 method_list = []
254 for member in sorted(root.__class__.__dict__.keys()):
255 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700256 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700257 continue
258 member_obj = root.__class__.__dict__[member]
259 if _IsExposed(member_obj):
260 if type(member_obj) == types.FunctionType:
261 method_list.append(prefixed_member)
262 else:
263 method_list += _FindExposedMethods(
264 member_obj, prefixed_member, unlisted)
265 return method_list
266
267
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700268class ApiRoot(object):
269 """RESTful API for Dev Server information."""
270 exposed = True
271
272 @cherrypy.expose
273 def hostinfo(self, ip):
274 """Returns a JSON dictionary containing information about the given ip.
275
Gilad Arnold1b908392012-10-05 11:36:27 -0700276 Args:
277 ip: address of host whose info is requested
278 Returns:
279 A JSON dictionary containing all or some of the following fields:
280 last_event_type (int): last update event type received
281 last_event_status (int): last update event status received
282 last_known_version (string): last known version reported in update ping
283 forced_update_label (string): update label to force next update ping to
284 use, set by setnextupdate
285 See the OmahaEvent class in update_engine/omaha_request_action.h for
286 event type and status code definitions. If the ip does not exist an empty
287 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700288
Gilad Arnold1b908392012-10-05 11:36:27 -0700289 Example URL:
290 http://myhost/api/hostinfo?ip=192.168.1.5
291 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700292 return updater.HandleHostInfoPing(ip)
293
294 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800295 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700296 """Returns a JSON object containing a log of host event.
297
298 Args:
299 ip: address of host whose event log is requested, or `all'
300 Returns:
301 A JSON encoded list (log) of dictionaries (events), each of which
302 containing a `timestamp' and other event fields, as described under
303 /api/hostinfo.
304
305 Example URL:
306 http://myhost/api/hostlog?ip=192.168.1.5
307 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800308 return updater.HandleHostLogPing(ip)
309
310 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700311 def setnextupdate(self, ip):
312 """Allows the response to the next update ping from a host to be set.
313
314 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 11:36:27 -0700315 /update command.
316 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700317 body_length = int(cherrypy.request.headers['Content-Length'])
318 label = cherrypy.request.rfile.read(body_length)
319
320 if label:
321 label = label.strip()
322 if label:
323 return updater.HandleSetUpdatePing(ip, label)
324 raise cherrypy.HTTPError(400, 'No label provided.')
325
326
Gilad Arnold55a2a372012-10-02 09:46:32 -0700327 @cherrypy.expose
328 def fileinfo(self, *path_args):
329 """Returns information about a given staged file.
330
331 Args:
332 path_args: path to the file inside the server's static staging directory
333 Returns:
334 A JSON encoded dictionary with information about the said file, which may
335 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700336 size (int): the file size in bytes
337 sha1 (string): a base64 encoded SHA1 hash
338 sha256 (string): a base64 encoded SHA256 hash
339
340 Example URL:
341 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700342 """
343 file_path = os.path.join(updater.static_dir, *path_args)
344 if not os.path.exists(file_path):
345 raise DevServerError('file not found: %s' % file_path)
346 try:
347 file_size = os.path.getsize(file_path)
348 file_sha1 = common_util.GetFileSha1(file_path)
349 file_sha256 = common_util.GetFileSha256(file_path)
350 except os.error, e:
351 raise DevServerError('failed to get info for file %s: %s' %
Gilad Arnolde74b3812013-04-22 11:27:38 -0700352 (file_path, e))
353
354 is_delta = autoupdate.Autoupdate.IsDeltaFormatFile(file_path)
355
356 return json.dumps({
357 autoupdate.Autoupdate.SIZE_ATTR: file_size,
358 autoupdate.Autoupdate.SHA1_ATTR: file_sha1,
359 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
360 autoupdate.Autoupdate.ISDELTA_ATTR: is_delta
361 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700362
Chris Sosa76e44b92013-01-31 12:11:38 -0800363
David Rochberg7c79a812011-01-19 14:24:45 -0500364class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700365 """The Root Class for the Dev Server.
366
367 CherryPy works as follows:
368 For each method in this class, cherrpy interprets root/path
369 as a call to an instance of DevServerRoot->method_name. For example,
370 a call to http://myhost/build will call build. CherryPy automatically
371 parses http args and places them as keyword arguments in each method.
372 For paths http://myhost/update/dir1/dir2, you can use *args so that
373 cherrypy uses the update method and puts the extra paths in args.
374 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700375 # Method names that should not be listed on the index page.
376 _UNLISTED_METHODS = ['index', 'doc']
377
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700378 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700379
Dan Shi59ae7092013-06-04 14:37:27 -0700380 # Number of threads that devserver is staging images.
381 _staging_thread_count = 0
382 # Lock used to lock increasing/decreasing count.
383 _staging_thread_count_lock = threading.Lock()
384
joychen3cb228e2013-06-12 12:13:13 -0700385 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700386 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800387 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700388 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500389
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700390 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500391 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700392 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700393 import builder
394 if self._builder is None:
395 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500396 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700397
Chris Sosacde6bf42012-05-31 18:36:39 -0700398 @staticmethod
399 def _canonicalize_archive_url(archive_url):
400 """Canonicalizes archive_url strings.
401
402 Raises:
403 DevserverError: if archive_url is not set.
404 """
405 if archive_url:
Chris Sosa76e44b92013-01-31 12:11:38 -0800406 if not archive_url.startswith('gs://'):
407 raise DevServerError("Archive URL isn't from Google Storage.")
408
Chris Sosacde6bf42012-05-31 18:36:39 -0700409 return archive_url.rstrip('/')
410 else:
411 raise DevServerError("Must specify an archive_url in the request")
412
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700413 @cherrypy.expose
Frank Farzanbcb571e2012-01-03 11:48:17 -0800414 def download(self, **kwargs):
415 """Downloads and archives full/delta payloads from Google Storage.
416
Chris Sosa76e44b92013-01-31 12:11:38 -0800417 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700418 This methods downloads artifacts. It may download artifacts in the
419 background in which case a caller should call wait_for_status to get
420 the status of the background artifact downloads. They should use the same
421 args passed to download.
422
Frank Farzanbcb571e2012-01-03 11:48:17 -0800423 Args:
424 archive_url: Google Storage URL for the build.
425
426 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700427 http://myhost/download?archive_url=gs://chromeos-image-archive/
428 x86-generic/R17-1208.0.0-a1-b338
Frank Farzanbcb571e2012-01-03 11:48:17 -0800429 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800430 return self.stage(archive_url=kwargs.get('archive_url'),
431 artifacts='full_payload,test_suites,stateful')
432
Dan Shi59ae7092013-06-04 14:37:27 -0700433
Chris Sosa76e44b92013-01-31 12:11:38 -0800434 @cherrypy.expose
435 def stage(self, **kwargs):
436 """Downloads and caches the artifacts from Google Storage URL.
437
438 Downloads and caches the artifacts Google Storage URL. Returns once these
439 have been downloaded on the devserver. A call to this will attempt to cache
440 non-specified artifacts in the background for the given from the given URL
441 following the principle of spatial locality. Spatial locality of different
442 artifacts is explicitly defined in the build_artifact module.
443
444 These artifacts will then be available from the static/ sub-directory of
445 the devserver.
446
447 Args:
448 archive_url: Google Storage URL for the build.
449 artifacts: Comma separated list of artifacts to download.
450
451 Example:
452 To download the autotest and test suites tarballs:
453 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
454 artifacts=autotest,test_suites
455 To download the full update payload:
456 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
457 artifacts=full_payload
458
459 For both these examples, one could find these artifacts at:
460 http://devserver_url:<port>/static/archive/<relative_path>*
461
462 Note for this example, relative path is the archive_url stripped of its
463 basename i.e. path/ in the examples above. Specific example:
464
465 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
466
467 Will get staged to:
468
469 http://devserver_url:<port>/static/archive/x86-mario-release/R26-3920.0.0
470 """
Chris Sosacde6bf42012-05-31 18:36:39 -0700471 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Chris Sosa76e44b92013-01-31 12:11:38 -0800472 artifacts = kwargs.get('artifacts', '')
473 if not artifacts:
474 raise DevServerError('No artifacts specified.')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700475
Dan Shi59ae7092013-06-04 14:37:27 -0700476 with DevServerRoot._staging_thread_count_lock:
477 DevServerRoot._staging_thread_count += 1
478 try:
479 downloader.Downloader(updater.static_dir, archive_url).Download(
480 artifacts.split(','))
481 finally:
482 with DevServerRoot._staging_thread_count_lock:
483 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800484 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700485
Dan Shi59ae7092013-06-04 14:37:27 -0700486
Chris Sosacde6bf42012-05-31 18:36:39 -0700487 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800488 def setup_telemetry(self, **kwargs):
489 """Extracts and sets up telemetry
490
491 This method goes through the telemetry deps packages, and stages them on
492 the devserver to be used by the drones and the telemetry tests.
493
494 Args:
495 archive_url: Google Storage URL for the build.
496
497 Returns:
498 Path to the source folder for the telemetry codebase once it is staged.
499 """
500 archive_url = kwargs.get('archive_url')
501 self.stage(archive_url=archive_url, artifacts='autotest')
502
503 build = '/'.join(downloader.Downloader.ParseUrl(archive_url))
504 build_path = os.path.join(updater.static_dir, build)
505 deps_path = os.path.join(build_path, 'autotest/packages')
506 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
507 src_folder = os.path.join(telemetry_path, 'src')
508
509 with self._telemetry_lock_dict.lock(telemetry_path):
510 if os.path.exists(src_folder):
511 # Telemetry is already fully stage return
512 return src_folder
513
514 common_util.MkDirP(telemetry_path)
515
516 # Copy over the required deps tar balls to the telemetry directory.
517 for dep in TELEMETRY_DEPS:
518 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700519 if not os.path.exists(dep_path):
520 # This dep does not exist (could be new), do not extract it.
521 continue
Simran Basi4baad082013-02-14 13:39:18 -0800522 try:
523 common_util.ExtractTarball(dep_path, telemetry_path)
524 except common_util.CommonUtilError as e:
525 shutil.rmtree(telemetry_path)
526 raise DevServerError(str(e))
527
528 # By default all the tarballs extract to test_src but some parts of
529 # the telemetry code specifically hardcoded to exist inside of 'src'.
530 test_src = os.path.join(telemetry_path, 'test_src')
531 try:
532 shutil.move(test_src, src_folder)
533 except shutil.Error:
534 # This can occur if src_folder already exists. Remove and retry move.
535 shutil.rmtree(src_folder)
536 raise DevServerError('Failure in telemetry setup for build %s. Appears'
537 ' that the test_src to src move failed.' % build)
538
539 return src_folder
540
541 @cherrypy.expose
Chris Sosacde6bf42012-05-31 18:36:39 -0700542 def wait_for_status(self, **kwargs):
543 """Waits for background artifacts to be downloaded from Google Storage.
544
Chris Sosa76e44b92013-01-31 12:11:38 -0800545 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosacde6bf42012-05-31 18:36:39 -0700546 Args:
547 archive_url: Google Storage URL for the build.
548
549 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700550 http://myhost/wait_for_status?archive_url=gs://chromeos-image-archive/
551 x86-generic/R17-1208.0.0-a1-b338
Chris Sosacde6bf42012-05-31 18:36:39 -0700552 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800553 return self.stage(archive_url=kwargs.get('archive_url'),
554 artifacts='full_payload,test_suites,autotest,stateful')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700555
556 @cherrypy.expose
Chris Masone816e38c2012-05-02 12:22:36 -0700557 def stage_debug(self, **kwargs):
558 """Downloads and stages debug symbol payloads from Google Storage.
559
Chris Sosa76e44b92013-01-31 12:11:38 -0800560 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
561 This methods downloads the debug symbol build artifact
562 synchronously, and then stages it for use by symbolicate_dump.
Chris Masone816e38c2012-05-02 12:22:36 -0700563
564 Args:
565 archive_url: Google Storage URL for the build.
566
567 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700568 http://myhost/stage_debug?archive_url=gs://chromeos-image-archive/
569 x86-generic/R17-1208.0.0-a1-b338
Chris Masone816e38c2012-05-02 12:22:36 -0700570 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800571 return self.stage(archive_url=kwargs.get('archive_url'),
572 artifacts='symbols')
Chris Masone816e38c2012-05-02 12:22:36 -0700573
574 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800575 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700576 """Symbolicates a minidump using pre-downloaded symbols, returns it.
577
578 Callers will need to POST to this URL with a body of MIME-type
579 "multipart/form-data".
580 The body should include a single argument, 'minidump', containing the
581 binary-formatted minidump to symbolicate.
582
Chris Masone816e38c2012-05-02 12:22:36 -0700583 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800584 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700585 minidump: The binary minidump file to symbolicate.
586 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800587 # Ensure the symbols have been staged.
588 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
589 if self.stage(archive_url=archive_url, artifacts='symbols') != 'Success':
590 raise DevServerError('Failed to stage symbols for %s' % archive_url)
591
Chris Masone816e38c2012-05-02 12:22:36 -0700592 to_return = ''
593 with tempfile.NamedTemporaryFile() as local:
594 while True:
595 data = minidump.file.read(8192)
596 if not data:
597 break
598 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800599
Chris Masone816e38c2012-05-02 12:22:36 -0700600 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800601
602 symbols_directory = os.path.join(downloader.Downloader.GetBuildDir(
603 updater.static_dir, archive_url), 'debug', 'breakpad')
604
605 stackwalk = subprocess.Popen(
606 ['minidump_stackwalk', local.name, symbols_directory],
607 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
608
Chris Masone816e38c2012-05-02 12:22:36 -0700609 to_return, error_text = stackwalk.communicate()
610 if stackwalk.returncode != 0:
611 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
612 error_text, stackwalk.returncode))
613
614 return to_return
615
616 @cherrypy.expose
Scott Zawalski16954532012-03-20 15:31:36 -0400617 def latestbuild(self, **params):
618 """Return a string representing the latest build for a given target.
619
620 Args:
621 target: The build target, typically a combination of the board and the
622 type of build e.g. x86-mario-release.
623 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
624 provided the latest RXX build will be returned.
625 Returns:
626 A string representation of the latest build if one exists, i.e.
627 R19-1993.0.0-a1-b1480.
628 An empty string if no latest could be found.
629 """
630 if not params:
631 return _PrintDocStringAsHTML(self.latestbuild)
632
633 if 'target' not in params:
634 raise cherrypy.HTTPError('500 Internal Server Error',
635 'Error: target= is required!')
636 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700637 return common_util.GetLatestBuildVersion(
Scott Zawalski16954532012-03-20 15:31:36 -0400638 updater.static_dir, params['target'],
639 milestone=params.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700640 except common_util.CommonUtilError as errmsg:
Scott Zawalski16954532012-03-20 15:31:36 -0400641 raise cherrypy.HTTPError('500 Internal Server Error', str(errmsg))
642
643 @cherrypy.expose
Scott Zawalski84a39c92012-01-13 15:12:42 -0500644 def controlfiles(self, **params):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500645 """Return a control file or a list of all known control files.
646
647 Example URL:
648 To List all control files:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500649 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500650 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500651 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 -0500652
653 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500654 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500655 control_path: If you want the contents of a control file set this
656 to the path. E.g. client/site_tests/sleeptest/control
657 Optional, if not provided return a list of control files is returned.
658 Returns:
659 Contents of a control file if control_path is provided.
660 A list of control files if no control_path is provided.
661 """
Scott Zawalski4647ce62012-01-03 17:17:28 -0500662 if not params:
663 return _PrintDocStringAsHTML(self.controlfiles)
664
Scott Zawalski84a39c92012-01-13 15:12:42 -0500665 if 'build' not in params:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500666 raise cherrypy.HTTPError('500 Internal Server Error',
Scott Zawalski84a39c92012-01-13 15:12:42 -0500667 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500668
669 if 'control_path' not in params:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700670 return common_util.GetControlFileList(
671 updater.static_dir, params['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500672 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700673 return common_util.GetControlFile(
674 updater.static_dir, params['build'], params['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800675
676 @cherrypy.expose
Gilad Arnold6f99b982012-09-12 10:49:40 -0700677 def stage_images(self, **kwargs):
678 """Downloads and stages a Chrome OS image from Google Storage.
679
Chris Sosa76e44b92013-01-31 12:11:38 -0800680 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700681 This method downloads a zipped archive from a specified GS location, then
682 extracts and stages the specified list of images and stages them under
Chris Sosa76e44b92013-01-31 12:11:38 -0800683 static/BOARD/BUILD/. Download is synchronous.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700684
685 Args:
686 archive_url: Google Storage URL for the build.
687 image_types: comma-separated list of images to download, may include
688 'test', 'recovery', and 'base'
689
690 Example URL:
691 http://myhost/stage_images?archive_url=gs://chromeos-image-archive/
692 x86-generic/R17-1208.0.0-a1-b338&image_types=test,base
693 """
Gilad Arnold6f99b982012-09-12 10:49:40 -0700694 image_types = kwargs.get('image_types').split(',')
Chris Sosa76e44b92013-01-31 12:11:38 -0800695 image_types_list = [image + '_image' for image in image_types]
696 self.stage(archive_url=kwargs.get('archive_url'), artifacts=','.join(
697 image_types_list))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700698
699 @cherrypy.expose
joychen3cb228e2013-06-12 12:13:13 -0700700 def xbuddy(self, **kwargs):
701 """The full xBuddy call, returns path to resource on this devserver.
702
703 Args:
704 path: build_id/alias
705 build_id is composed of "board/version"
706 The board is the familiar board name, optionally suffixed.
707 The version can be the google storage version number, and may also be
708 one of a number of aliases that will be translated into the latest
709 built image that fits the description.
710 The alias is one of a number of image or artifact aliases used by
711 xbuddy, defined in xbuddy:ALIASES
712 return_dir: {true|false}
713 if set to true, returns the url to the update.gz
714 instead.
715
716 Example URL:
717 http://host:port/xbuddy?path=/x86-generic/R26-4000.0.0/test
718 or
719 http://host:port/xbuddy?path=/x86-generic/R26-4000.0.0/
720 test&return_dir=true
721
722 Returns:
723 A redirect to the image or update file on the devserver.
724 e.g. http://host:port/static/archive/x86-generic-release/
725 R26-4000.0.0/chromium-test-image.bin
726 or if return_dir is True, return path to the folder where
727 image or update file is
728 http://host:port/static/x86-generic-release/R26-4000.0.0/
729 """
730 boolean_string = kwargs.get('return_dir')
731 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
732 devserver_url = cherrypy.request.base
733 return_url = self._xbuddy.Get(kwargs.get('path'),
734 return_dir)
735 if return_dir:
736 return os.path.join(devserver_url, return_url)
737 else:
738 raise cherrypy.HTTPRedirect(return_url, 302)
739
740 @cherrypy.expose
741 def xbuddy_list(self):
742 """Lists the currently available images & time since last access.
743
744 @return: A string representation of a list of tuples
745 [(build_id, time since last access),...]
746 """
747 return self._xbuddy.List()
748
749 @cherrypy.expose
750 def xbuddy_capacity(self):
751 """Returns the number of images cached by xBuddy.
752
753 @return: Capacity of this devserver.
754 """
755 return self._xbuddy.Capacity()
756
757 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700758 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700759 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700760 return ('Welcome to the Dev Server!<br>\n'
761 '<br>\n'
762 'Here are the available methods, click for documentation:<br>\n'
763 '<br>\n'
764 '%s' %
765 '<br>\n'.join(
766 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700767 for name in _FindExposedMethods(
768 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700769
770 @cherrypy.expose
771 def doc(self, *args):
772 """Shows the documentation for available methods / URLs.
773
774 Example:
775 http://myhost/doc/update
776 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700777 name = '/'.join(args)
778 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700779 if not method:
780 raise DevServerError("No exposed method named `%s'" % name)
781 if not method.__doc__:
782 raise DevServerError("No documentation for exposed method `%s'" % name)
783 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -0700784
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700785 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700786 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700787 """Handles an update check from a Chrome OS client.
788
789 The HTTP request should contain the standard Omaha-style XML blob. The URL
790 line may contain an additional intermediate path to the update payload.
791
792 Example:
793 http://myhost/update/optional/path/to/payload
794 """
Chris Sosa7c931362010-10-11 19:49:01 -0700795 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -0800796 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -0700797 data = cherrypy.request.rfile.read(body_length)
798 return updater.HandleUpdatePing(data, label)
799
Chris Sosa0356d3b2010-09-16 15:46:22 -0700800
Dan Shif5ce2de2013-04-25 16:06:32 -0700801 @cherrypy.expose
802 def check_health(self):
803 """Collect the health status of devserver to see if it's ready for staging.
804
805 @return: A JSON dictionary containing all or some of the following fields:
Dan Shi59ae7092013-06-04 14:37:27 -0700806 free_disk (int): free disk space in GB
807 staging_thread_count (int): number of devserver threads currently
808 staging an image
Dan Shif5ce2de2013-04-25 16:06:32 -0700809 """
810 # Get free disk space.
811 stat = os.statvfs(updater.static_dir)
812 free_disk = stat.f_bsize * stat.f_bavail / 1000000000
813
814 return json.dumps({
815 'free_disk': free_disk,
Dan Shi59ae7092013-06-04 14:37:27 -0700816 'staging_thread_count': DevServerRoot._staging_thread_count,
Dan Shif5ce2de2013-04-25 16:06:32 -0700817 })
818
819
Chris Sosadbc20082012-12-10 13:39:11 -0800820def _CleanCache(cache_dir, wipe):
821 """Wipes any excess cached items in the cache_dir.
822
823 Args:
824 cache_dir: the directory we are wiping from.
825 wipe: If True, wipe all the contents -- not just the excess.
826 """
827 if wipe:
828 # Clear the cache and exit on error.
829 cmd = 'rm -rf %s/*' % cache_dir
830 if os.system(cmd) != 0:
831 _Log('Failed to clear the cache with %s' % cmd)
832 sys.exit(1)
833 else:
834 # Clear all but the last N cached updates
835 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
836 (cache_dir, CACHED_ENTRIES))
837 if os.system(cmd) != 0:
838 _Log('Failed to clean up old delta cache files with %s' % cmd)
839 sys.exit(1)
840
841
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700842def _AddTestingOptions(parser):
843 group = optparse.OptionGroup(
844 parser, 'Advanced Testing Options', 'These are used by test scripts and '
845 'developers writing integration tests utilizing the devserver. They are '
846 'not intended to be really used outside the scope of someone '
847 'knowledgable about the test.')
848 group.add_option('--exit',
849 action='store_true',
850 help='do not start the server (yet pregenerate/clear cache)')
851 group.add_option('--host_log',
852 action='store_true', default=False,
853 help='record history of host update events (/api/hostlog)')
854 group.add_option('--max_updates',
855 metavar='NUM', default= -1, type='int',
856 help='maximum number of update checks handled positively '
857 '(default: unlimited)')
858 group.add_option('--private_key',
859 metavar='PATH', default=None,
860 help='path to the private key in pem format. If this is set '
861 'the devserver will generate update payloads that are '
862 'signed with this key.')
863 group.add_option('--proxy_port',
864 metavar='PORT', default=None, type='int',
865 help='port to have the client connect to -- basically the '
866 'devserver lies to the update to tell it to get the payload '
867 'from a different port that will proxy the request back to '
868 'the devserver. The proxy must be managed outside the '
869 'devserver.')
870 group.add_option('--remote_payload',
871 action='store_true', default=False,
872 help='Payload is being served from a remote machine')
873 group.add_option('-u', '--urlbase',
874 metavar='URL',
875 help='base URL for update images, other than the '
876 'devserver. Use in conjunction with remote_payload.')
877 parser.add_option_group(group)
878
879
880def _AddUpdateOptions(parser):
881 group = optparse.OptionGroup(
882 parser, 'Autoupdate Options', 'These options can be used to change '
883 'how the devserver either generates or serve update payloads. Please '
884 'note that all of these option affect how a payload is generated and so '
885 'do not work in archive-only mode.')
886 group.add_option('--board',
887 help='By default the devserver will create an update '
888 'payload from the latest image built for the board '
889 'a device that is requesting an update has. When we '
890 'pre-generate an update (see below) and we do not specify '
891 'another update_type option like image or payload, the '
892 'devserver needs to know the board to generate the latest '
893 'image for. This is that board.')
894 group.add_option('--critical_update',
895 action='store_true', default=False,
896 help='Present update payload as critical')
897 group.add_option('--for_vm',
898 dest='vm', action='store_true',
899 help='DEPRECATED: see no_patch_kernel.')
900 group.add_option('--image',
901 metavar='FILE',
902 help='Generate and serve an update using this image to any '
903 'device that requests an update.')
904 group.add_option('--no_patch_kernel',
905 dest='patch_kernel', action='store_false', default=True,
906 help='When generating an update payload, do not patch the '
907 'kernel with kernel verification blob from the stateful '
908 'partition.')
909 group.add_option('--payload',
910 metavar='PATH',
911 help='use the update payload from specified directory '
912 '(update.gz).')
913 group.add_option('-p', '--pregenerate_update',
914 action='store_true', default=False,
915 help='pre-generate the update payload before accepting '
916 'update requests. Useful to help debug payload generation '
917 'issues quickly. Also if an update payload will take a '
918 'long time to generate, a client may timeout if you do not'
919 'pregenerate the update.')
920 group.add_option('--src_image',
921 metavar='PATH', default='',
922 help='If specified, delta updates will be generated using '
923 'this image as the source image. Delta updates are when '
924 'you are updating from a "source image" to a another '
925 'image.')
926 parser.add_option_group(group)
927
928
929def _AddProductionOptions(parser):
930 group = optparse.OptionGroup(
931 parser, 'Advanced Server Options', 'These options can be used to changed '
932 'for advanced server behavior.')
933 # TODO(sosa): Clean up the fact we have archive_dir and data_dir. It's ugly.
934 # Should be --archive_mode + optional data_dir.
935 group.add_option('--archive_dir',
936 metavar='PATH',
937 help='Enables archive-only mode. This disables any '
938 'update or package generation related functionality. This '
939 'mode also works without a Chromium OS chroot.')
940 group.add_option('--clear_cache',
941 action='store_true', default=False,
942 help='At startup, removes all cached entries from the'
943 'devserver\'s cache.')
944 group.add_option('--logfile',
945 metavar='PATH',
946 help='log output to this file instead of stdout')
947 group.add_option('--production',
948 action='store_true', default=False,
949 help='have the devserver use production values when '
950 'starting up. This includes using more threads and '
951 'performing less logging.')
952 parser.add_option_group(group)
953
954
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700955def _MakeLogHandler(logfile):
956 """Create a LogHandler instance used to log all messages."""
957 hdlr_cls = handlers.TimedRotatingFileHandler
958 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
959 backupCount=_LOG_ROTATION_BACKUP)
960 # The cherrypy documentation says to use the _cplogging module for
961 # this, even though it's named as a private module.
962 # pylint: disable=W0212
963 hdlr.setFormatter(cherrypy._cplogging.logfmt)
964 return hdlr
965
966
Chris Sosacde6bf42012-05-31 18:36:39 -0700967def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700968 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -0800969 parser = optparse.OptionParser(usage=usage)
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700970 parser.add_option('--data_dir',
971 metavar='PATH',
972 default=os.path.dirname(os.path.abspath(sys.argv[0])),
973 help='writable directory where static lives')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700974 parser.add_option('--port',
975 default=8080, type='int',
976 help='port for the dev server to use (default: 8080)')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700977 parser.add_option('-t', '--test_image',
978 action='store_true',
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700979 help='If set, look for the chromiumos_test_image.bin file '
980 'when generating update payloads rather than the '
981 'chromiumos_image.bin which is the default.')
982 _AddProductionOptions(parser)
983 _AddUpdateOptions(parser)
984 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -0700985 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +0000986
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700987 # Handle options that must be set globally in cherrypy. Do this
988 # work up front, because calls to _Log() below depend on this
989 # initialization.
990 if options.production:
991 cherrypy.config.update({'environment': 'production'})
992 if not options.logfile:
993 cherrypy.config.update({'log.screen': True})
994 else:
995 cherrypy.config.update({'log.error_file': '',
996 'log.access_file': ''})
997 hdlr = _MakeLogHandler(options.logfile)
998 # Pylint can't seem to process these two calls properly
999 # pylint: disable=E1101
1000 cherrypy.log.access_log.addHandler(hdlr)
1001 cherrypy.log.error_log.addHandler(hdlr)
1002 # pylint: enable=E1101
1003
Chris Sosa7c931362010-10-11 19:49:01 -07001004 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
1005 root_dir = os.path.realpath('%s/../..' % devserver_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001006 serve_only = False
1007
Zdenek Behan608f46c2011-02-19 00:47:16 +01001008 static_dir = os.path.realpath('%s/static' % options.data_dir)
1009 os.system('mkdir -p %s' % static_dir)
1010
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001011 # TODO(sosa): Remove after deprecation.
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001012 if options.vm:
1013 options.patch_kernel = False
1014
Sean O'Connor14b6a0a2010-03-20 23:23:48 -07001015 if options.archive_dir:
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001016 # TODO(zbehan) Remove legacy support:
1017 # archive_dir is the directory where static/archive will point.
1018 # If this is an absolute path, all is fine. If someone calls this
1019 # using a relative path, that is relative to src/platform/dev/.
1020 # That use case is unmaintainable, but since applications use it
1021 # with =./static, instead of a boolean flag, we'll make this
1022 # relative to devserver_dir to keep these unbroken. For now.
Zdenek Behan608f46c2011-02-19 00:47:16 +01001023 archive_dir = options.archive_dir
1024 if not os.path.isabs(archive_dir):
Chris Sosa47a7d4e2012-03-28 11:26:55 -07001025 archive_dir = os.path.realpath(os.path.join(devserver_dir, archive_dir))
Zdenek Behan608f46c2011-02-19 00:47:16 +01001026 _PrepareToServeUpdatesOnly(archive_dir, static_dir)
Zdenek Behan6d93e552011-03-02 22:35:49 +01001027 static_dir = os.path.realpath(archive_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001028 serve_only = True
Chris Sosa0356d3b2010-09-16 15:46:22 -07001029
Don Garrettf90edf02010-11-16 17:36:14 -08001030 cache_dir = os.path.join(static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001031 # If our devserver is only supposed to serve payloads, we shouldn't be
1032 # mucking with the cache at all. If the devserver hadn't previously
1033 # generated a cache and is expected, the caller is using it wrong.
Chris Sosadbc20082012-12-10 13:39:11 -08001034 if serve_only:
1035 # Extra check to make sure we're not being called incorrectly.
1036 if (options.clear_cache or options.exit or options.pregenerate_update or
1037 options.board or options.image):
1038 parser.error('Incompatible flags detected for serve_only mode.')
Chris Sosadbc20082012-12-10 13:39:11 -08001039 elif os.path.exists(cache_dir):
1040 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001041 else:
1042 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001043
Chris Sosadbc20082012-12-10 13:39:11 -08001044 _Log('Using cache directory %s' % cache_dir)
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001045 _Log('Data dir is %s' % options.data_dir)
1046 _Log('Source root is %s' % root_dir)
1047 _Log('Serving from %s' % static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001048
Chris Sosa6a3697f2013-01-29 16:44:43 -08001049 # We allow global use here to share with cherrypy classes.
1050 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001051 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001052 updater = autoupdate.Autoupdate(
1053 root_dir=root_dir,
1054 static_dir=static_dir,
Chris Sosa0356d3b2010-09-16 15:46:22 -07001055 serve_only=serve_only,
Andrew de los Reyes52620802010-04-12 13:40:07 -07001056 urlbase=options.urlbase,
1057 test_image=options.test_image,
Chris Sosa5d342a22010-09-28 16:54:41 -07001058 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001059 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001060 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -07001061 src_image=options.src_image,
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001062 patch_kernel=options.patch_kernel,
Chris Sosa08d55a22011-01-19 16:08:02 -08001063 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001064 copy_to_static_root=not options.exit,
1065 private_key=options.private_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001066 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001067 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001068 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001069 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001070 )
Chris Sosa7c931362010-10-11 19:49:01 -07001071
Chris Sosa6a3697f2013-01-29 16:44:43 -08001072 if options.pregenerate_update:
1073 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -07001074
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001075 if options.exit:
1076 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001077
joychen3cb228e2013-06-12 12:13:13 -07001078 _xbuddy = xbuddy.XBuddy(static_dir)
1079 dev_server = DevServerRoot(_xbuddy)
1080
1081 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001082
1083
1084if __name__ == '__main__':
1085 main()