blob: 259b1a2733ad27a60383f14ac6da53a71b4c7b30 [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 """
beepsbd337242013-07-09 22:44:06 -0700105 # pylint: disable=W1401
Scott Zawalski4647ce62012-01-03 17:17:28 -0500106 matched = re.match('^\s+', string)
107 if matched:
108 return len(matched.group())
109
110 return 0
111
112
113def _PrintDocStringAsHTML(func):
114 """Make a functions docstring somewhat HTML style.
115
116 Args:
117 func: The function to return the docstring from.
118 Returns:
119 A string that is somewhat formated for a web browser.
120 """
121 # TODO(scottz): Make this parse Args/Returns in a prettier way.
122 # Arguments could be bolded and indented etc.
123 html_doc = []
124 for line in func.__doc__.splitlines():
125 leading_space = _LeadingWhiteSpaceCount(line)
126 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700127 line = '&nbsp;' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500128
129 html_doc.append('<BR>%s' % line)
130
131 return '\n'.join(html_doc)
132
133
Chris Sosa7c931362010-10-11 19:49:01 -0700134def _GetConfig(options):
135 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800136
137 # On a system with IPv6 not compiled into the kernel,
138 # AF_INET6 sockets will return a socket.error exception.
139 # On such systems, fall-back to IPv4.
140 socket_host = '::'
141 try:
142 socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
143 except socket.error:
144 socket_host = '0.0.0.0'
145
Chris Sosa7c931362010-10-11 19:49:01 -0700146 base_config = { 'global':
147 { 'server.log_request_headers': True,
148 'server.protocol_version': 'HTTP/1.1',
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800149 'server.socket_host': socket_host,
Chris Sosa7c931362010-10-11 19:49:01 -0700150 'server.socket_port': int(options.port),
Chris Sosa374c62d2010-10-14 09:13:54 -0700151 'response.timeout': 6000,
Chris Sosa6fe23942012-07-02 15:44:46 -0700152 'request.show_tracebacks': True,
Chris Sosa72333d12012-06-13 11:28:05 -0700153 'server.socket_timeout': 60,
joychenecc02aa2013-07-17 18:27:35 -0700154 'server.thread_pool': 2,
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':
joychened64b222013-06-21 16:39:34 -0700173 { 'tools.staticdir.dir': options.static_dir,
Chris Sosa7c931362010-10-11 19:49:01 -0700174 '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:
Alex Miller93beca52013-07-30 19:25:09 -0700179 base_config['global'].update({'server.thread_pool': 150})
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
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700184def _GetRecursiveMemberObject(root, member_list):
185 """Returns an object corresponding to a nested member list.
186
187 Args:
188 root: the root object to search
189 member_list: list of nested members to search
190 Returns:
191 An object corresponding to the member name list; None otherwise.
192 """
193 for member in member_list:
194 next_root = root.__class__.__dict__.get(member)
195 if not next_root:
196 return None
197 root = next_root
198 return root
199
200
201def _IsExposed(name):
202 """Returns True iff |name| has an `exposed' attribute and it is set."""
203 return hasattr(name, 'exposed') and name.exposed
204
205
Gilad Arnold748c8322012-10-12 09:51:35 -0700206def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700207 """Returns a CherryPy-exposed method, if such exists.
208
209 Args:
210 root: the root object for searching
211 nested_member: a slash-joined path to the nested member
212 ignored: method paths to be ignored
213 Returns:
214 A function object corresponding to the path defined by |member_list| from
215 the |root| object, if the function is exposed and not ignored; None
216 otherwise.
217 """
Gilad Arnold748c8322012-10-12 09:51:35 -0700218 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700219 _GetRecursiveMemberObject(root, nested_member.split('/')))
220 if (method and type(method) == types.FunctionType and _IsExposed(method)):
221 return method
222
223
Gilad Arnold748c8322012-10-12 09:51:35 -0700224def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700225 """Finds exposed CherryPy methods.
226
227 Args:
228 root: the root object for searching
229 prefix: slash-joined chain of members leading to current object
230 unlisted: URLs to be excluded regardless of their exposed status
231 Returns:
232 List of exposed URLs that are not unlisted.
233 """
234 method_list = []
235 for member in sorted(root.__class__.__dict__.keys()):
236 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700237 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700238 continue
239 member_obj = root.__class__.__dict__[member]
240 if _IsExposed(member_obj):
241 if type(member_obj) == types.FunctionType:
242 method_list.append(prefixed_member)
243 else:
244 method_list += _FindExposedMethods(
245 member_obj, prefixed_member, unlisted)
246 return method_list
247
248
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700249class ApiRoot(object):
250 """RESTful API for Dev Server information."""
251 exposed = True
252
253 @cherrypy.expose
254 def hostinfo(self, ip):
255 """Returns a JSON dictionary containing information about the given ip.
256
Gilad Arnold1b908392012-10-05 11:36:27 -0700257 Args:
258 ip: address of host whose info is requested
259 Returns:
260 A JSON dictionary containing all or some of the following fields:
261 last_event_type (int): last update event type received
262 last_event_status (int): last update event status received
263 last_known_version (string): last known version reported in update ping
264 forced_update_label (string): update label to force next update ping to
265 use, set by setnextupdate
266 See the OmahaEvent class in update_engine/omaha_request_action.h for
267 event type and status code definitions. If the ip does not exist an empty
268 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700269
Gilad Arnold1b908392012-10-05 11:36:27 -0700270 Example URL:
271 http://myhost/api/hostinfo?ip=192.168.1.5
272 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700273 return updater.HandleHostInfoPing(ip)
274
275 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800276 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700277 """Returns a JSON object containing a log of host event.
278
279 Args:
280 ip: address of host whose event log is requested, or `all'
281 Returns:
282 A JSON encoded list (log) of dictionaries (events), each of which
283 containing a `timestamp' and other event fields, as described under
284 /api/hostinfo.
285
286 Example URL:
287 http://myhost/api/hostlog?ip=192.168.1.5
288 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800289 return updater.HandleHostLogPing(ip)
290
291 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700292 def setnextupdate(self, ip):
293 """Allows the response to the next update ping from a host to be set.
294
295 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 11:36:27 -0700296 /update command.
297 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700298 body_length = int(cherrypy.request.headers['Content-Length'])
299 label = cherrypy.request.rfile.read(body_length)
300
301 if label:
302 label = label.strip()
303 if label:
304 return updater.HandleSetUpdatePing(ip, label)
305 raise cherrypy.HTTPError(400, 'No label provided.')
306
307
Gilad Arnold55a2a372012-10-02 09:46:32 -0700308 @cherrypy.expose
309 def fileinfo(self, *path_args):
310 """Returns information about a given staged file.
311
312 Args:
313 path_args: path to the file inside the server's static staging directory
314 Returns:
315 A JSON encoded dictionary with information about the said file, which may
316 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700317 size (int): the file size in bytes
318 sha1 (string): a base64 encoded SHA1 hash
319 sha256 (string): a base64 encoded SHA256 hash
320
321 Example URL:
322 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700323 """
324 file_path = os.path.join(updater.static_dir, *path_args)
325 if not os.path.exists(file_path):
326 raise DevServerError('file not found: %s' % file_path)
327 try:
328 file_size = os.path.getsize(file_path)
329 file_sha1 = common_util.GetFileSha1(file_path)
330 file_sha256 = common_util.GetFileSha256(file_path)
331 except os.error, e:
332 raise DevServerError('failed to get info for file %s: %s' %
Gilad Arnolde74b3812013-04-22 11:27:38 -0700333 (file_path, e))
334
335 is_delta = autoupdate.Autoupdate.IsDeltaFormatFile(file_path)
336
337 return json.dumps({
338 autoupdate.Autoupdate.SIZE_ATTR: file_size,
339 autoupdate.Autoupdate.SHA1_ATTR: file_sha1,
340 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
341 autoupdate.Autoupdate.ISDELTA_ATTR: is_delta
342 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700343
Chris Sosa76e44b92013-01-31 12:11:38 -0800344
David Rochberg7c79a812011-01-19 14:24:45 -0500345class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700346 """The Root Class for the Dev Server.
347
348 CherryPy works as follows:
349 For each method in this class, cherrpy interprets root/path
350 as a call to an instance of DevServerRoot->method_name. For example,
351 a call to http://myhost/build will call build. CherryPy automatically
352 parses http args and places them as keyword arguments in each method.
353 For paths http://myhost/update/dir1/dir2, you can use *args so that
354 cherrypy uses the update method and puts the extra paths in args.
355 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700356 # Method names that should not be listed on the index page.
357 _UNLISTED_METHODS = ['index', 'doc']
358
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700359 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700360
Dan Shi59ae7092013-06-04 14:37:27 -0700361 # Number of threads that devserver is staging images.
362 _staging_thread_count = 0
363 # Lock used to lock increasing/decreasing count.
364 _staging_thread_count_lock = threading.Lock()
365
joychen3cb228e2013-06-12 12:13:13 -0700366 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700367 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800368 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700369 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500370
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700371 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500372 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700373 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700374 import builder
375 if self._builder is None:
376 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500377 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700378
Chris Sosacde6bf42012-05-31 18:36:39 -0700379 @staticmethod
380 def _canonicalize_archive_url(archive_url):
381 """Canonicalizes archive_url strings.
382
383 Raises:
384 DevserverError: if archive_url is not set.
385 """
386 if archive_url:
Chris Sosa76e44b92013-01-31 12:11:38 -0800387 if not archive_url.startswith('gs://'):
388 raise DevServerError("Archive URL isn't from Google Storage.")
389
Chris Sosacde6bf42012-05-31 18:36:39 -0700390 return archive_url.rstrip('/')
391 else:
392 raise DevServerError("Must specify an archive_url in the request")
393
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700394 @cherrypy.expose
Frank Farzanbcb571e2012-01-03 11:48:17 -0800395 def download(self, **kwargs):
396 """Downloads and archives full/delta payloads from Google Storage.
397
Chris Sosa76e44b92013-01-31 12:11:38 -0800398 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700399 This methods downloads artifacts. It may download artifacts in the
400 background in which case a caller should call wait_for_status to get
401 the status of the background artifact downloads. They should use the same
402 args passed to download.
403
Frank Farzanbcb571e2012-01-03 11:48:17 -0800404 Args:
405 archive_url: Google Storage URL for the build.
406
407 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700408 http://myhost/download?archive_url=gs://chromeos-image-archive/
409 x86-generic/R17-1208.0.0-a1-b338
Frank Farzanbcb571e2012-01-03 11:48:17 -0800410 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800411 return self.stage(archive_url=kwargs.get('archive_url'),
412 artifacts='full_payload,test_suites,stateful')
413
Dan Shi59ae7092013-06-04 14:37:27 -0700414
Chris Sosa76e44b92013-01-31 12:11:38 -0800415 @cherrypy.expose
416 def stage(self, **kwargs):
417 """Downloads and caches the artifacts from Google Storage URL.
418
419 Downloads and caches the artifacts Google Storage URL. Returns once these
420 have been downloaded on the devserver. A call to this will attempt to cache
421 non-specified artifacts in the background for the given from the given URL
422 following the principle of spatial locality. Spatial locality of different
423 artifacts is explicitly defined in the build_artifact module.
424
425 These artifacts will then be available from the static/ sub-directory of
426 the devserver.
427
428 Args:
429 archive_url: Google Storage URL for the build.
430 artifacts: Comma separated list of artifacts to download.
431
432 Example:
433 To download the autotest and test suites tarballs:
434 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
435 artifacts=autotest,test_suites
436 To download the full update payload:
437 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
438 artifacts=full_payload
439
440 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700441 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800442
443 Note for this example, relative path is the archive_url stripped of its
444 basename i.e. path/ in the examples above. Specific example:
445
446 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
447
448 Will get staged to:
449
joychened64b222013-06-21 16:39:34 -0700450 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Chris Sosa76e44b92013-01-31 12:11:38 -0800451 """
Chris Sosacde6bf42012-05-31 18:36:39 -0700452 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Chris Sosa76e44b92013-01-31 12:11:38 -0800453 artifacts = kwargs.get('artifacts', '')
454 if not artifacts:
455 raise DevServerError('No artifacts specified.')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700456
Dan Shi59ae7092013-06-04 14:37:27 -0700457 with DevServerRoot._staging_thread_count_lock:
458 DevServerRoot._staging_thread_count += 1
459 try:
460 downloader.Downloader(updater.static_dir, archive_url).Download(
461 artifacts.split(','))
462 finally:
463 with DevServerRoot._staging_thread_count_lock:
464 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800465 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700466
Dan Shi59ae7092013-06-04 14:37:27 -0700467
Chris Sosacde6bf42012-05-31 18:36:39 -0700468 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800469 def setup_telemetry(self, **kwargs):
470 """Extracts and sets up telemetry
471
472 This method goes through the telemetry deps packages, and stages them on
473 the devserver to be used by the drones and the telemetry tests.
474
475 Args:
476 archive_url: Google Storage URL for the build.
477
478 Returns:
479 Path to the source folder for the telemetry codebase once it is staged.
480 """
481 archive_url = kwargs.get('archive_url')
482 self.stage(archive_url=archive_url, artifacts='autotest')
483
484 build = '/'.join(downloader.Downloader.ParseUrl(archive_url))
485 build_path = os.path.join(updater.static_dir, build)
486 deps_path = os.path.join(build_path, 'autotest/packages')
487 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
488 src_folder = os.path.join(telemetry_path, 'src')
489
490 with self._telemetry_lock_dict.lock(telemetry_path):
491 if os.path.exists(src_folder):
492 # Telemetry is already fully stage return
493 return src_folder
494
495 common_util.MkDirP(telemetry_path)
496
497 # Copy over the required deps tar balls to the telemetry directory.
498 for dep in TELEMETRY_DEPS:
499 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700500 if not os.path.exists(dep_path):
501 # This dep does not exist (could be new), do not extract it.
502 continue
Simran Basi4baad082013-02-14 13:39:18 -0800503 try:
504 common_util.ExtractTarball(dep_path, telemetry_path)
505 except common_util.CommonUtilError as e:
506 shutil.rmtree(telemetry_path)
507 raise DevServerError(str(e))
508
509 # By default all the tarballs extract to test_src but some parts of
510 # the telemetry code specifically hardcoded to exist inside of 'src'.
511 test_src = os.path.join(telemetry_path, 'test_src')
512 try:
513 shutil.move(test_src, src_folder)
514 except shutil.Error:
515 # This can occur if src_folder already exists. Remove and retry move.
516 shutil.rmtree(src_folder)
517 raise DevServerError('Failure in telemetry setup for build %s. Appears'
518 ' that the test_src to src move failed.' % build)
519
520 return src_folder
521
522 @cherrypy.expose
Chris Sosacde6bf42012-05-31 18:36:39 -0700523 def wait_for_status(self, **kwargs):
524 """Waits for background artifacts to be downloaded from Google Storage.
525
Chris Sosa76e44b92013-01-31 12:11:38 -0800526 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosacde6bf42012-05-31 18:36:39 -0700527 Args:
528 archive_url: Google Storage URL for the build.
529
530 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700531 http://myhost/wait_for_status?archive_url=gs://chromeos-image-archive/
532 x86-generic/R17-1208.0.0-a1-b338
Chris Sosacde6bf42012-05-31 18:36:39 -0700533 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800534 return self.stage(archive_url=kwargs.get('archive_url'),
535 artifacts='full_payload,test_suites,autotest,stateful')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700536
537 @cherrypy.expose
Chris Masone816e38c2012-05-02 12:22:36 -0700538 def stage_debug(self, **kwargs):
539 """Downloads and stages debug symbol payloads from Google Storage.
540
Chris Sosa76e44b92013-01-31 12:11:38 -0800541 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
542 This methods downloads the debug symbol build artifact
543 synchronously, and then stages it for use by symbolicate_dump.
Chris Masone816e38c2012-05-02 12:22:36 -0700544
545 Args:
546 archive_url: Google Storage URL for the build.
547
548 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700549 http://myhost/stage_debug?archive_url=gs://chromeos-image-archive/
550 x86-generic/R17-1208.0.0-a1-b338
Chris Masone816e38c2012-05-02 12:22:36 -0700551 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800552 return self.stage(archive_url=kwargs.get('archive_url'),
553 artifacts='symbols')
Chris Masone816e38c2012-05-02 12:22:36 -0700554
555 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800556 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700557 """Symbolicates a minidump using pre-downloaded symbols, returns it.
558
559 Callers will need to POST to this URL with a body of MIME-type
560 "multipart/form-data".
561 The body should include a single argument, 'minidump', containing the
562 binary-formatted minidump to symbolicate.
563
Chris Masone816e38c2012-05-02 12:22:36 -0700564 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800565 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700566 minidump: The binary minidump file to symbolicate.
567 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800568 # Ensure the symbols have been staged.
569 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
570 if self.stage(archive_url=archive_url, artifacts='symbols') != 'Success':
571 raise DevServerError('Failed to stage symbols for %s' % archive_url)
572
Chris Masone816e38c2012-05-02 12:22:36 -0700573 to_return = ''
574 with tempfile.NamedTemporaryFile() as local:
575 while True:
576 data = minidump.file.read(8192)
577 if not data:
578 break
579 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800580
Chris Masone816e38c2012-05-02 12:22:36 -0700581 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800582
583 symbols_directory = os.path.join(downloader.Downloader.GetBuildDir(
584 updater.static_dir, archive_url), 'debug', 'breakpad')
585
586 stackwalk = subprocess.Popen(
587 ['minidump_stackwalk', local.name, symbols_directory],
588 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
589
Chris Masone816e38c2012-05-02 12:22:36 -0700590 to_return, error_text = stackwalk.communicate()
591 if stackwalk.returncode != 0:
592 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
593 error_text, stackwalk.returncode))
594
595 return to_return
596
597 @cherrypy.expose
Scott Zawalski16954532012-03-20 15:31:36 -0400598 def latestbuild(self, **params):
599 """Return a string representing the latest build for a given target.
600
601 Args:
602 target: The build target, typically a combination of the board and the
603 type of build e.g. x86-mario-release.
604 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
605 provided the latest RXX build will be returned.
606 Returns:
607 A string representation of the latest build if one exists, i.e.
608 R19-1993.0.0-a1-b1480.
609 An empty string if no latest could be found.
610 """
611 if not params:
612 return _PrintDocStringAsHTML(self.latestbuild)
613
614 if 'target' not in params:
615 raise cherrypy.HTTPError('500 Internal Server Error',
616 'Error: target= is required!')
617 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700618 return common_util.GetLatestBuildVersion(
Scott Zawalski16954532012-03-20 15:31:36 -0400619 updater.static_dir, params['target'],
620 milestone=params.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700621 except common_util.CommonUtilError as errmsg:
Scott Zawalski16954532012-03-20 15:31:36 -0400622 raise cherrypy.HTTPError('500 Internal Server Error', str(errmsg))
623
624 @cherrypy.expose
Scott Zawalski84a39c92012-01-13 15:12:42 -0500625 def controlfiles(self, **params):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500626 """Return a control file or a list of all known control files.
627
628 Example URL:
629 To List all control files:
beepsbd337242013-07-09 22:44:06 -0700630 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
631 To List all control files for, say, the bvt suite:
632 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500633 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500634 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 -0500635
636 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500637 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500638 control_path: If you want the contents of a control file set this
639 to the path. E.g. client/site_tests/sleeptest/control
640 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -0700641 suite_name: If control_path is not specified but a suite_name is
642 specified, list the control files belonging to that suite instead of
643 all control files. The empty string for suite_name will list all control
644 files for the build.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500645 Returns:
646 Contents of a control file if control_path is provided.
647 A list of control files if no control_path is provided.
648 """
Scott Zawalski4647ce62012-01-03 17:17:28 -0500649 if not params:
650 return _PrintDocStringAsHTML(self.controlfiles)
651
Scott Zawalski84a39c92012-01-13 15:12:42 -0500652 if 'build' not in params:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500653 raise cherrypy.HTTPError('500 Internal Server Error',
Scott Zawalski84a39c92012-01-13 15:12:42 -0500654 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500655
656 if 'control_path' not in params:
beepsbd337242013-07-09 22:44:06 -0700657 if 'suite_name' in params and params['suite_name']:
658 return common_util.GetControlFileListForSuite(
659 updater.static_dir, params['build'], params['suite_name'])
660 else:
661 return common_util.GetControlFileList(
662 updater.static_dir, params['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500663 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700664 return common_util.GetControlFile(
665 updater.static_dir, params['build'], params['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800666
667 @cherrypy.expose
Gilad Arnold6f99b982012-09-12 10:49:40 -0700668 def stage_images(self, **kwargs):
669 """Downloads and stages a Chrome OS image from Google Storage.
670
Chris Sosa76e44b92013-01-31 12:11:38 -0800671 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700672 This method downloads a zipped archive from a specified GS location, then
673 extracts and stages the specified list of images and stages them under
Chris Sosa76e44b92013-01-31 12:11:38 -0800674 static/BOARD/BUILD/. Download is synchronous.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700675
676 Args:
677 archive_url: Google Storage URL for the build.
678 image_types: comma-separated list of images to download, may include
679 'test', 'recovery', and 'base'
680
681 Example URL:
682 http://myhost/stage_images?archive_url=gs://chromeos-image-archive/
683 x86-generic/R17-1208.0.0-a1-b338&image_types=test,base
684 """
Gilad Arnold6f99b982012-09-12 10:49:40 -0700685 image_types = kwargs.get('image_types').split(',')
Chris Sosa76e44b92013-01-31 12:11:38 -0800686 image_types_list = [image + '_image' for image in image_types]
687 self.stage(archive_url=kwargs.get('archive_url'), artifacts=','.join(
688 image_types_list))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700689
690 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -0700691 def xbuddy(self, *args, **kwargs):
692 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -0700693
694 Args:
joycheneaf4cfc2013-07-02 08:38:57 -0700695 path_parts: the path following xbuddy/ in the call url is split into the
696 components of the path.
697 The path can be understood as a build_id/artifact, build_id is
698 composed of "board/version"
699
700 path_parts[0], the board, is the familiar board name, optionally
701 suffixed.
702 path_parts[1], the version, can be the google storage version
703 number, and may also be any of a number of xBuddy defined version
704 aliases that will be translated into the latest built image that
705 fits the description. defaults to latest.
706 path_parts[2], the artifact, is one of a number of image or artifact
707 aliases used by xbuddy, defined in xbuddy:ALIASES. Defaults to test
708
709 Kwargs:
joychen3cb228e2013-06-12 12:13:13 -0700710 return_dir: {true|false}
711 if set to true, returns the url to the update.gz
712 instead.
713
714 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -0700715 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -0700716 or
joycheneaf4cfc2013-07-02 08:38:57 -0700717 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -0700718
719 Returns:
720 A redirect to the image or update file on the devserver.
721 e.g. http://host:port/static/archive/x86-generic-release/
722 R26-4000.0.0/chromium-test-image.bin
723 or if return_dir is True, return path to the folder where
724 image or update file is
725 http://host:port/static/x86-generic-release/R26-4000.0.0/
726 """
727 boolean_string = kwargs.get('return_dir')
728 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
joycheneaf4cfc2013-07-02 08:38:57 -0700729 return_url = self._xbuddy.Get(args,
joychen3cb228e2013-06-12 12:13:13 -0700730 return_dir)
731 if return_dir:
joycheneaf4cfc2013-07-02 08:38:57 -0700732 directory = os.path.join(cherrypy.request.base, return_url)
733 _Log("Directory requested, returning: %s", directory)
734 return directory
joychen3cb228e2013-06-12 12:13:13 -0700735 else:
joycheneaf4cfc2013-07-02 08:38:57 -0700736 return_url = '/' + return_url
737 _Log("Payload requested, returning: %s", return_url)
joychen3cb228e2013-06-12 12:13:13 -0700738 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
joychenb0dfe552013-07-30 10:02:06 -0700792 Paths that can be handled by xbuddy are formatted:
793 http://myhost/update/xbuddy/board/version
794
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700795 Example:
796 http://myhost/update/optional/path/to/payload
797 """
joychen346531c2013-07-24 16:55:56 -0700798 if len(args) > 0 and args[0] == 'xbuddy':
799 # Interpret the rest of the path as an xbuddy path
800 label, found = self._xbuddy.Translate(args[1:] + ('full_payload',))
801 if not found:
802 _Log("Update payload not found for %s, xBuddy looking it up.", label)
803 else:
804 label = '/'.join(args)
805
806 _Log('Update label: %s', label)
Gilad Arnold286a0062012-01-12 13:47:02 -0800807 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -0700808 data = cherrypy.request.rfile.read(body_length)
809 return updater.HandleUpdatePing(data, label)
810
Chris Sosa0356d3b2010-09-16 15:46:22 -0700811
Dan Shif5ce2de2013-04-25 16:06:32 -0700812 @cherrypy.expose
813 def check_health(self):
814 """Collect the health status of devserver to see if it's ready for staging.
815
816 @return: A JSON dictionary containing all or some of the following fields:
Dan Shi59ae7092013-06-04 14:37:27 -0700817 free_disk (int): free disk space in GB
818 staging_thread_count (int): number of devserver threads currently
819 staging an image
Dan Shif5ce2de2013-04-25 16:06:32 -0700820 """
821 # Get free disk space.
822 stat = os.statvfs(updater.static_dir)
823 free_disk = stat.f_bsize * stat.f_bavail / 1000000000
824
825 return json.dumps({
826 'free_disk': free_disk,
Dan Shi59ae7092013-06-04 14:37:27 -0700827 'staging_thread_count': DevServerRoot._staging_thread_count,
Dan Shif5ce2de2013-04-25 16:06:32 -0700828 })
829
830
Chris Sosadbc20082012-12-10 13:39:11 -0800831def _CleanCache(cache_dir, wipe):
832 """Wipes any excess cached items in the cache_dir.
833
834 Args:
835 cache_dir: the directory we are wiping from.
836 wipe: If True, wipe all the contents -- not just the excess.
837 """
838 if wipe:
839 # Clear the cache and exit on error.
840 cmd = 'rm -rf %s/*' % cache_dir
841 if os.system(cmd) != 0:
842 _Log('Failed to clear the cache with %s' % cmd)
843 sys.exit(1)
844 else:
845 # Clear all but the last N cached updates
846 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
847 (cache_dir, CACHED_ENTRIES))
848 if os.system(cmd) != 0:
849 _Log('Failed to clean up old delta cache files with %s' % cmd)
850 sys.exit(1)
851
852
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700853def _AddTestingOptions(parser):
854 group = optparse.OptionGroup(
855 parser, 'Advanced Testing Options', 'These are used by test scripts and '
856 'developers writing integration tests utilizing the devserver. They are '
857 'not intended to be really used outside the scope of someone '
858 'knowledgable about the test.')
859 group.add_option('--exit',
860 action='store_true',
861 help='do not start the server (yet pregenerate/clear cache)')
862 group.add_option('--host_log',
863 action='store_true', default=False,
864 help='record history of host update events (/api/hostlog)')
865 group.add_option('--max_updates',
866 metavar='NUM', default= -1, type='int',
867 help='maximum number of update checks handled positively '
868 '(default: unlimited)')
869 group.add_option('--private_key',
870 metavar='PATH', default=None,
871 help='path to the private key in pem format. If this is set '
872 'the devserver will generate update payloads that are '
873 'signed with this key.')
874 group.add_option('--proxy_port',
875 metavar='PORT', default=None, type='int',
876 help='port to have the client connect to -- basically the '
877 'devserver lies to the update to tell it to get the payload '
878 'from a different port that will proxy the request back to '
879 'the devserver. The proxy must be managed outside the '
880 'devserver.')
881 group.add_option('--remote_payload',
882 action='store_true', default=False,
883 help='Payload is being served from a remote machine')
884 group.add_option('-u', '--urlbase',
885 metavar='URL',
886 help='base URL for update images, other than the '
887 'devserver. Use in conjunction with remote_payload.')
888 parser.add_option_group(group)
889
890
891def _AddUpdateOptions(parser):
892 group = optparse.OptionGroup(
893 parser, 'Autoupdate Options', 'These options can be used to change '
894 'how the devserver either generates or serve update payloads. Please '
895 'note that all of these option affect how a payload is generated and so '
896 'do not work in archive-only mode.')
897 group.add_option('--board',
898 help='By default the devserver will create an update '
899 'payload from the latest image built for the board '
900 'a device that is requesting an update has. When we '
901 'pre-generate an update (see below) and we do not specify '
902 'another update_type option like image or payload, the '
903 'devserver needs to know the board to generate the latest '
904 'image for. This is that board.')
905 group.add_option('--critical_update',
906 action='store_true', default=False,
907 help='Present update payload as critical')
908 group.add_option('--for_vm',
909 dest='vm', action='store_true',
910 help='DEPRECATED: see no_patch_kernel.')
911 group.add_option('--image',
912 metavar='FILE',
913 help='Generate and serve an update using this image to any '
914 'device that requests an update.')
915 group.add_option('--no_patch_kernel',
916 dest='patch_kernel', action='store_false', default=True,
917 help='When generating an update payload, do not patch the '
918 'kernel with kernel verification blob from the stateful '
919 'partition.')
920 group.add_option('--payload',
921 metavar='PATH',
922 help='use the update payload from specified directory '
923 '(update.gz).')
924 group.add_option('-p', '--pregenerate_update',
925 action='store_true', default=False,
926 help='pre-generate the update payload before accepting '
927 'update requests. Useful to help debug payload generation '
928 'issues quickly. Also if an update payload will take a '
929 'long time to generate, a client may timeout if you do not'
930 'pregenerate the update.')
931 group.add_option('--src_image',
932 metavar='PATH', default='',
933 help='If specified, delta updates will be generated using '
934 'this image as the source image. Delta updates are when '
935 'you are updating from a "source image" to a another '
936 'image.')
937 parser.add_option_group(group)
938
939
940def _AddProductionOptions(parser):
941 group = optparse.OptionGroup(
942 parser, 'Advanced Server Options', 'These options can be used to changed '
943 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700944 group.add_option('--archive_dir',
945 metavar='PATH',
joychened64b222013-06-21 16:39:34 -0700946 help='To be deprecated.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700947 group.add_option('--clear_cache',
948 action='store_true', default=False,
949 help='At startup, removes all cached entries from the'
950 'devserver\'s cache.')
951 group.add_option('--logfile',
952 metavar='PATH',
953 help='log output to this file instead of stdout')
954 group.add_option('--production',
955 action='store_true', default=False,
956 help='have the devserver use production values when '
957 'starting up. This includes using more threads and '
958 'performing less logging.')
959 parser.add_option_group(group)
960
961
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700962def _MakeLogHandler(logfile):
963 """Create a LogHandler instance used to log all messages."""
964 hdlr_cls = handlers.TimedRotatingFileHandler
965 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
966 backupCount=_LOG_ROTATION_BACKUP)
967 # The cherrypy documentation says to use the _cplogging module for
968 # this, even though it's named as a private module.
969 # pylint: disable=W0212
970 hdlr.setFormatter(cherrypy._cplogging.logfmt)
971 return hdlr
972
973
Chris Sosacde6bf42012-05-31 18:36:39 -0700974def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700975 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -0800976 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -0700977
978 # get directory that the devserver is run from
979 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
980 default_archive_dir = '%s/static' % devserver_dir
981 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700982 metavar='PATH',
joychened64b222013-06-21 16:39:34 -0700983 default=default_archive_dir,
984 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700985 parser.add_option('--port',
986 default=8080, type='int',
987 help='port for the dev server to use (default: 8080)')
Gilad Arnold9714d9b2012-10-04 10:09:42 -0700988 parser.add_option('-t', '--test_image',
989 action='store_true',
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700990 help='If set, look for the chromiumos_test_image.bin file '
991 'when generating update payloads rather than the '
992 'chromiumos_image.bin which is the default.')
joychen5260b9a2013-07-16 14:48:01 -0700993 parser.add_option('-x', '--xbuddy_manage_builds',
994 action='store_true',
995 default=False,
996 help='If set, allow xbuddy to manage images in'
997 'build/images.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700998 _AddProductionOptions(parser)
999 _AddUpdateOptions(parser)
1000 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001001 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001002
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001003 # Handle options that must be set globally in cherrypy. Do this
1004 # work up front, because calls to _Log() below depend on this
1005 # initialization.
1006 if options.production:
1007 cherrypy.config.update({'environment': 'production'})
1008 if not options.logfile:
1009 cherrypy.config.update({'log.screen': True})
1010 else:
1011 cherrypy.config.update({'log.error_file': '',
1012 'log.access_file': ''})
1013 hdlr = _MakeLogHandler(options.logfile)
1014 # Pylint can't seem to process these two calls properly
1015 # pylint: disable=E1101
1016 cherrypy.log.access_log.addHandler(hdlr)
1017 cherrypy.log.error_log.addHandler(hdlr)
1018 # pylint: enable=E1101
1019
Chris Sosa7c931362010-10-11 19:49:01 -07001020 root_dir = os.path.realpath('%s/../..' % devserver_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001021
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001022 # TODO(sosa): Remove after deprecation.
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001023 if options.vm:
1024 options.patch_kernel = False
1025
joychened64b222013-06-21 16:39:34 -07001026 # set static_dir, from which everything will be served
Sean O'Connor14b6a0a2010-03-20 23:23:48 -07001027 if options.archive_dir:
joychened64b222013-06-21 16:39:34 -07001028 # TODO(joyc) To be deprecated
Zdenek Behan608f46c2011-02-19 00:47:16 +01001029 archive_dir = options.archive_dir
1030 if not os.path.isabs(archive_dir):
joychened64b222013-06-21 16:39:34 -07001031 archive_dir = os.path.join(devserver_dir, archive_dir)
1032 options.static_dir = os.path.realpath(archive_dir)
joychened64b222013-06-21 16:39:34 -07001033 else:
1034 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001035
joychened64b222013-06-21 16:39:34 -07001036 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001037 # If our devserver is only supposed to serve payloads, we shouldn't be
1038 # mucking with the cache at all. If the devserver hadn't previously
1039 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001040 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001041 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001042 else:
1043 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001044
Chris Sosadbc20082012-12-10 13:39:11 -08001045 _Log('Using cache directory %s' % cache_dir)
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001046 _Log('Source root is %s' % root_dir)
joychened64b222013-06-21 16:39:34 -07001047 _Log('Serving from %s' % options.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,
joychened64b222013-06-21 16:39:34 -07001054 static_dir=options.static_dir,
Andrew de los Reyes52620802010-04-12 13:40:07 -07001055 urlbase=options.urlbase,
1056 test_image=options.test_image,
Chris Sosa5d342a22010-09-28 16:54:41 -07001057 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001058 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001059 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -07001060 src_image=options.src_image,
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001061 patch_kernel=options.patch_kernel,
Chris Sosa08d55a22011-01-19 16:08:02 -08001062 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001063 copy_to_static_root=not options.exit,
1064 private_key=options.private_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001065 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001066 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001067 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001068 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001069 )
Chris Sosa7c931362010-10-11 19:49:01 -07001070
Chris Sosa6a3697f2013-01-29 16:44:43 -08001071 if options.pregenerate_update:
1072 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -07001073
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001074 if options.exit:
1075 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001076
joychen5260b9a2013-07-16 14:48:01 -07001077 _xbuddy = xbuddy.XBuddy(options.xbuddy_manage_builds,
joychenb0dfe552013-07-30 10:02:06 -07001078 options.board,
joychen5260b9a2013-07-16 14:48:01 -07001079 root_dir=root_dir,
joychenb0dfe552013-07-30 10:02:06 -07001080 static_dir=options.static_dir,
1081 )
joychen3cb228e2013-06-12 12:13:13 -07001082 dev_server = DevServerRoot(_xbuddy)
1083
1084 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001085
1086
1087if __name__ == '__main__':
1088 main()