blob: d1a609a87d368c3097deca88d3a49445215cfab0 [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
joychen84d13772013-08-06 09:17:23 -070029a particular package from a developer's chroot onto a requesting device.
Chris Sosa3ae4dc12013-03-29 11:47:00 -070030
31For example:
32gmerge gmerge -d <devserver_url>
33
34devserver will see if a newer package of gmerge is available. If gmerge is
35cros_work'd on, it will re-build gmerge. After this, gmerge will install that
36version of gmerge that the devserver just created/found.
37
38For autoupdates, there are many more advanced options that can help specify
39how to update and which payload to give to a requester.
40"""
41
Chris Sosa7c931362010-10-11 19:49:01 -070042
Gilad Arnold55a2a372012-10-02 09:46:32 -070043import json
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070044import optparse
rtc@google.comded22402009-10-26 22:36:21 +000045import os
Scott Zawalski4647ce62012-01-03 17:17:28 -050046import re
Simran Basi4baad082013-02-14 13:39:18 -080047import shutil
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -080048import socket
Chris Masone816e38c2012-05-02 12:22:36 -070049import subprocess
J. Richard Barnette3d977b82013-04-23 11:05:19 -070050import sys
Chris Masone816e38c2012-05-02 12:22:36 -070051import tempfile
Dan Shi59ae7092013-06-04 14:37:27 -070052import threading
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070053import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070054from logging import handlers
55
56import cherrypy
57import cherrypy._cplogging
rtc@google.comded22402009-10-26 22:36:21 +000058
Chris Sosa0356d3b2010-09-16 15:46:22 -070059import autoupdate
Gilad Arnoldc65330c2012-09-20 15:17:48 -070060import common_util
Chris Sosa47a7d4e2012-03-28 11:26:55 -070061import downloader
Gilad Arnoldc65330c2012-09-20 15:17:48 -070062import log_util
joychen3cb228e2013-06-12 12:13:13 -070063import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070064
Gilad Arnoldc65330c2012-09-20 15:17:48 -070065# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080066def _Log(message, *args):
67 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 15:46:22 -070068
Frank Farzan40160872011-12-12 18:39:18 -080069
Chris Sosa417e55d2011-01-25 16:40:48 -080070CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080071
Simran Basi4baad082013-02-14 13:39:18 -080072TELEMETRY_FOLDER = 'telemetry_src'
73TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
74 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070075 'dep-chrome_test.tar.bz2',
76 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -080077
Chris Sosa0356d3b2010-09-16 15:46:22 -070078# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +000079updater = None
rtc@google.comded22402009-10-26 22:36:21 +000080
J. Richard Barnette3d977b82013-04-23 11:05:19 -070081# Log rotation parameters. These settings correspond to once a week
J. Richard Barnette6dfa5342013-06-04 11:48:56 -070082# at midnight between Friday and Saturday, with about three months
83# of old logs kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -070084#
85# For more, see the documentation for
86# logging.handlers.TimedRotatingFileHandler
J. Richard Barnette6dfa5342013-06-04 11:48:56 -070087_LOG_ROTATION_TIME = 'W4'
J. Richard Barnette3d977b82013-04-23 11:05:19 -070088_LOG_ROTATION_BACKUP = 13
89
Frank Farzan40160872011-12-12 18:39:18 -080090
Chris Sosa9164ca32012-03-28 11:04:50 -070091class DevServerError(Exception):
Chris Sosa47a7d4e2012-03-28 11:26:55 -070092 """Exception class used by this module."""
93 pass
94
95
Scott Zawalski4647ce62012-01-03 17:17:28 -050096def _LeadingWhiteSpaceCount(string):
97 """Count the amount of leading whitespace in a string.
98
99 Args:
100 string: The string to count leading whitespace in.
101 Returns:
102 number of white space chars before characters start.
103 """
104 matched = re.match('^\s+', string)
105 if matched:
106 return len(matched.group())
107
108 return 0
109
110
111def _PrintDocStringAsHTML(func):
112 """Make a functions docstring somewhat HTML style.
113
114 Args:
115 func: The function to return the docstring from.
116 Returns:
117 A string that is somewhat formated for a web browser.
118 """
119 # TODO(scottz): Make this parse Args/Returns in a prettier way.
120 # Arguments could be bolded and indented etc.
121 html_doc = []
122 for line in func.__doc__.splitlines():
123 leading_space = _LeadingWhiteSpaceCount(line)
124 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700125 line = '&nbsp;' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500126
127 html_doc.append('<BR>%s' % line)
128
129 return '\n'.join(html_doc)
130
131
Chris Sosa7c931362010-10-11 19:49:01 -0700132def _GetConfig(options):
133 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800134
135 # On a system with IPv6 not compiled into the kernel,
136 # AF_INET6 sockets will return a socket.error exception.
137 # On such systems, fall-back to IPv4.
138 socket_host = '::'
139 try:
140 socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
141 except socket.error:
142 socket_host = '0.0.0.0'
143
Chris Sosa7c931362010-10-11 19:49:01 -0700144 base_config = { 'global':
145 { 'server.log_request_headers': True,
146 'server.protocol_version': 'HTTP/1.1',
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800147 'server.socket_host': socket_host,
Chris Sosa7c931362010-10-11 19:49:01 -0700148 'server.socket_port': int(options.port),
Chris Sosa374c62d2010-10-14 09:13:54 -0700149 'response.timeout': 6000,
Chris Sosa6fe23942012-07-02 15:44:46 -0700150 'request.show_tracebacks': True,
Chris Sosa72333d12012-06-13 11:28:05 -0700151 'server.socket_timeout': 60,
joychenecc02aa2013-07-17 18:27:35 -0700152 'server.thread_pool': 2,
Chris Sosa7c931362010-10-11 19:49:01 -0700153 },
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700154 '/api':
155 {
156 # Gets rid of cherrypy parsing post file for args.
157 'request.process_request_body': False,
158 },
Chris Sosaa1ef0102010-10-21 16:22:35 -0700159 '/build':
160 {
161 'response.timeout': 100000,
162 },
Chris Sosa7c931362010-10-11 19:49:01 -0700163 '/update':
164 {
165 # Gets rid of cherrypy parsing post file for args.
166 'request.process_request_body': False,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700167 'response.timeout': 10000,
Chris Sosa7c931362010-10-11 19:49:01 -0700168 },
169 # Sets up the static dir for file hosting.
170 '/static':
joychened64b222013-06-21 16:39:34 -0700171 { 'tools.staticdir.dir': options.static_dir,
Chris Sosa7c931362010-10-11 19:49:01 -0700172 'tools.staticdir.on': True,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700173 'response.timeout': 10000,
Chris Sosa7c931362010-10-11 19:49:01 -0700174 },
175 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700176 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700177 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500178
Chris Sosa7c931362010-10-11 19:49:01 -0700179 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000180
Darin Petkove17164a2010-08-11 13:24:41 -0700181
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700182def _GetRecursiveMemberObject(root, member_list):
183 """Returns an object corresponding to a nested member list.
184
185 Args:
186 root: the root object to search
187 member_list: list of nested members to search
188 Returns:
189 An object corresponding to the member name list; None otherwise.
190 """
191 for member in member_list:
192 next_root = root.__class__.__dict__.get(member)
193 if not next_root:
194 return None
195 root = next_root
196 return root
197
198
199def _IsExposed(name):
200 """Returns True iff |name| has an `exposed' attribute and it is set."""
201 return hasattr(name, 'exposed') and name.exposed
202
203
Gilad Arnold748c8322012-10-12 09:51:35 -0700204def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700205 """Returns a CherryPy-exposed method, if such exists.
206
207 Args:
208 root: the root object for searching
209 nested_member: a slash-joined path to the nested member
210 ignored: method paths to be ignored
211 Returns:
212 A function object corresponding to the path defined by |member_list| from
213 the |root| object, if the function is exposed and not ignored; None
214 otherwise.
215 """
Gilad Arnold748c8322012-10-12 09:51:35 -0700216 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700217 _GetRecursiveMemberObject(root, nested_member.split('/')))
218 if (method and type(method) == types.FunctionType and _IsExposed(method)):
219 return method
220
221
Gilad Arnold748c8322012-10-12 09:51:35 -0700222def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700223 """Finds exposed CherryPy methods.
224
225 Args:
226 root: the root object for searching
227 prefix: slash-joined chain of members leading to current object
228 unlisted: URLs to be excluded regardless of their exposed status
229 Returns:
230 List of exposed URLs that are not unlisted.
231 """
232 method_list = []
233 for member in sorted(root.__class__.__dict__.keys()):
234 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700235 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700236 continue
237 member_obj = root.__class__.__dict__[member]
238 if _IsExposed(member_obj):
239 if type(member_obj) == types.FunctionType:
240 method_list.append(prefixed_member)
241 else:
242 method_list += _FindExposedMethods(
243 member_obj, prefixed_member, unlisted)
244 return method_list
245
246
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700247class ApiRoot(object):
248 """RESTful API for Dev Server information."""
249 exposed = True
250
251 @cherrypy.expose
252 def hostinfo(self, ip):
253 """Returns a JSON dictionary containing information about the given ip.
254
Gilad Arnold1b908392012-10-05 11:36:27 -0700255 Args:
256 ip: address of host whose info is requested
257 Returns:
258 A JSON dictionary containing all or some of the following fields:
259 last_event_type (int): last update event type received
260 last_event_status (int): last update event status received
261 last_known_version (string): last known version reported in update ping
262 forced_update_label (string): update label to force next update ping to
263 use, set by setnextupdate
264 See the OmahaEvent class in update_engine/omaha_request_action.h for
265 event type and status code definitions. If the ip does not exist an empty
266 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700267
Gilad Arnold1b908392012-10-05 11:36:27 -0700268 Example URL:
269 http://myhost/api/hostinfo?ip=192.168.1.5
270 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700271 return updater.HandleHostInfoPing(ip)
272
273 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800274 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700275 """Returns a JSON object containing a log of host event.
276
277 Args:
278 ip: address of host whose event log is requested, or `all'
279 Returns:
280 A JSON encoded list (log) of dictionaries (events), each of which
281 containing a `timestamp' and other event fields, as described under
282 /api/hostinfo.
283
284 Example URL:
285 http://myhost/api/hostlog?ip=192.168.1.5
286 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800287 return updater.HandleHostLogPing(ip)
288
289 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700290 def setnextupdate(self, ip):
291 """Allows the response to the next update ping from a host to be set.
292
293 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 11:36:27 -0700294 /update command.
295 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700296 body_length = int(cherrypy.request.headers['Content-Length'])
297 label = cherrypy.request.rfile.read(body_length)
298
299 if label:
300 label = label.strip()
301 if label:
302 return updater.HandleSetUpdatePing(ip, label)
303 raise cherrypy.HTTPError(400, 'No label provided.')
304
305
Gilad Arnold55a2a372012-10-02 09:46:32 -0700306 @cherrypy.expose
307 def fileinfo(self, *path_args):
308 """Returns information about a given staged file.
309
310 Args:
311 path_args: path to the file inside the server's static staging directory
312 Returns:
313 A JSON encoded dictionary with information about the said file, which may
314 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700315 size (int): the file size in bytes
316 sha1 (string): a base64 encoded SHA1 hash
317 sha256 (string): a base64 encoded SHA256 hash
318
319 Example URL:
320 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700321 """
322 file_path = os.path.join(updater.static_dir, *path_args)
323 if not os.path.exists(file_path):
324 raise DevServerError('file not found: %s' % file_path)
325 try:
326 file_size = os.path.getsize(file_path)
327 file_sha1 = common_util.GetFileSha1(file_path)
328 file_sha256 = common_util.GetFileSha256(file_path)
329 except os.error, e:
330 raise DevServerError('failed to get info for file %s: %s' %
Gilad Arnolde74b3812013-04-22 11:27:38 -0700331 (file_path, e))
332
333 is_delta = autoupdate.Autoupdate.IsDeltaFormatFile(file_path)
334
335 return json.dumps({
336 autoupdate.Autoupdate.SIZE_ATTR: file_size,
337 autoupdate.Autoupdate.SHA1_ATTR: file_sha1,
338 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
339 autoupdate.Autoupdate.ISDELTA_ATTR: is_delta
340 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700341
Chris Sosa76e44b92013-01-31 12:11:38 -0800342
David Rochberg7c79a812011-01-19 14:24:45 -0500343class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700344 """The Root Class for the Dev Server.
345
346 CherryPy works as follows:
347 For each method in this class, cherrpy interprets root/path
348 as a call to an instance of DevServerRoot->method_name. For example,
349 a call to http://myhost/build will call build. CherryPy automatically
350 parses http args and places them as keyword arguments in each method.
351 For paths http://myhost/update/dir1/dir2, you can use *args so that
352 cherrypy uses the update method and puts the extra paths in args.
353 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700354 # Method names that should not be listed on the index page.
355 _UNLISTED_METHODS = ['index', 'doc']
356
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700357 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700358
Dan Shi59ae7092013-06-04 14:37:27 -0700359 # Number of threads that devserver is staging images.
360 _staging_thread_count = 0
361 # Lock used to lock increasing/decreasing count.
362 _staging_thread_count_lock = threading.Lock()
363
joychen3cb228e2013-06-12 12:13:13 -0700364 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700365 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800366 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700367 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500368
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700369 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500370 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700371 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700372 import builder
373 if self._builder is None:
374 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500375 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700376
Chris Sosacde6bf42012-05-31 18:36:39 -0700377 @staticmethod
378 def _canonicalize_archive_url(archive_url):
379 """Canonicalizes archive_url strings.
380
381 Raises:
382 DevserverError: if archive_url is not set.
383 """
384 if archive_url:
Chris Sosa76e44b92013-01-31 12:11:38 -0800385 if not archive_url.startswith('gs://'):
386 raise DevServerError("Archive URL isn't from Google Storage.")
387
Chris Sosacde6bf42012-05-31 18:36:39 -0700388 return archive_url.rstrip('/')
389 else:
390 raise DevServerError("Must specify an archive_url in the request")
391
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700392 @cherrypy.expose
Frank Farzanbcb571e2012-01-03 11:48:17 -0800393 def download(self, **kwargs):
394 """Downloads and archives full/delta payloads from Google Storage.
395
Chris Sosa76e44b92013-01-31 12:11:38 -0800396 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700397 This methods downloads artifacts. It may download artifacts in the
398 background in which case a caller should call wait_for_status to get
399 the status of the background artifact downloads. They should use the same
400 args passed to download.
401
Frank Farzanbcb571e2012-01-03 11:48:17 -0800402 Args:
403 archive_url: Google Storage URL for the build.
Dan Shif8eb0d12013-08-01 17:52:06 -0700404 artifacts: Comma separated list of artifacts to download.
Frank Farzanbcb571e2012-01-03 11:48:17 -0800405
406 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700407 http://myhost/download?archive_url=gs://chromeos-image-archive/
Dan Shif8eb0d12013-08-01 17:52:06 -0700408 x86-generic/R17-1208.0.0-a1-b338&artifacts=full_payload,test_suites,
409 stateful
Frank Farzanbcb571e2012-01-03 11:48:17 -0800410 """
Dan Shif8eb0d12013-08-01 17:52:06 -0700411 async = kwargs.get('async', False)
Chris Sosa76e44b92013-01-31 12:11:38 -0800412 return self.stage(archive_url=kwargs.get('archive_url'),
Dan Shiffaaf0c2013-08-06 21:22:31 -0700413 artifacts='full_payload,test_suites,stateful',
Dan Shif8eb0d12013-08-01 17:52:06 -0700414 async=async)
Chris Sosa76e44b92013-01-31 12:11:38 -0800415
Dan Shif8eb0d12013-08-01 17:52:06 -0700416 @cherrypy.expose
417 def is_staged(self, **kwargs):
418 """Check if artifacts have been downloaded.
419
420 @param archive_url: Google Storage URL for the build.
421 @param artifacts: Comma separated list of artifacts to download.
422 @returns: True of all artifacts are staged.
423
424 Example:
425 To check if autotest and test_suites are staged:
426 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
427 artifacts=autotest,test_suites
428 """
429 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
430 artifacts = kwargs.get('artifacts', '')
431 if not artifacts:
432 raise DevServerError('No artifacts specified.')
433 return str(downloader.Downloader(updater.static_dir, archive_url).IsStaged(
434 artifacts.split(',')))
Dan Shi59ae7092013-06-04 14:37:27 -0700435
Chris Sosa76e44b92013-01-31 12:11:38 -0800436 @cherrypy.expose
437 def stage(self, **kwargs):
438 """Downloads and caches the artifacts from Google Storage URL.
439
440 Downloads and caches the artifacts Google Storage URL. Returns once these
441 have been downloaded on the devserver. A call to this will attempt to cache
442 non-specified artifacts in the background for the given from the given URL
443 following the principle of spatial locality. Spatial locality of different
444 artifacts is explicitly defined in the build_artifact module.
445
446 These artifacts will then be available from the static/ sub-directory of
447 the devserver.
448
449 Args:
450 archive_url: Google Storage URL for the build.
451 artifacts: Comma separated list of artifacts to download.
Dan Shif8eb0d12013-08-01 17:52:06 -0700452 async: True to return without waiting for download to complete.
Chris Sosa76e44b92013-01-31 12:11:38 -0800453
454 Example:
455 To download the autotest and test suites tarballs:
456 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
457 artifacts=autotest,test_suites
458 To download the full update payload:
459 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
460 artifacts=full_payload
461
462 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700463 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800464
465 Note for this example, relative path is the archive_url stripped of its
466 basename i.e. path/ in the examples above. Specific example:
467
468 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
469
470 Will get staged to:
471
joychened64b222013-06-21 16:39:34 -0700472 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Chris Sosa76e44b92013-01-31 12:11:38 -0800473 """
Chris Sosacde6bf42012-05-31 18:36:39 -0700474 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Chris Sosa76e44b92013-01-31 12:11:38 -0800475 artifacts = kwargs.get('artifacts', '')
Dan Shif8eb0d12013-08-01 17:52:06 -0700476 async = kwargs.get('async', False)
Chris Sosa76e44b92013-01-31 12:11:38 -0800477 if not artifacts:
478 raise DevServerError('No artifacts specified.')
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700479
Dan Shi59ae7092013-06-04 14:37:27 -0700480 with DevServerRoot._staging_thread_count_lock:
481 DevServerRoot._staging_thread_count += 1
482 try:
Dan Shif8eb0d12013-08-01 17:52:06 -0700483 downloader.Downloader(updater.static_dir,
484 archive_url).Download(artifacts.split(','), async=async)
Dan Shi59ae7092013-06-04 14:37:27 -0700485 finally:
486 with DevServerRoot._staging_thread_count_lock:
487 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800488 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700489
490 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800491 def setup_telemetry(self, **kwargs):
492 """Extracts and sets up telemetry
493
494 This method goes through the telemetry deps packages, and stages them on
495 the devserver to be used by the drones and the telemetry tests.
496
497 Args:
498 archive_url: Google Storage URL for the build.
499
500 Returns:
501 Path to the source folder for the telemetry codebase once it is staged.
502 """
503 archive_url = kwargs.get('archive_url')
504 self.stage(archive_url=archive_url, artifacts='autotest')
505
506 build = '/'.join(downloader.Downloader.ParseUrl(archive_url))
507 build_path = os.path.join(updater.static_dir, build)
508 deps_path = os.path.join(build_path, 'autotest/packages')
509 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
510 src_folder = os.path.join(telemetry_path, 'src')
511
512 with self._telemetry_lock_dict.lock(telemetry_path):
513 if os.path.exists(src_folder):
514 # Telemetry is already fully stage return
515 return src_folder
516
517 common_util.MkDirP(telemetry_path)
518
519 # Copy over the required deps tar balls to the telemetry directory.
520 for dep in TELEMETRY_DEPS:
521 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700522 if not os.path.exists(dep_path):
523 # This dep does not exist (could be new), do not extract it.
524 continue
Simran Basi4baad082013-02-14 13:39:18 -0800525 try:
526 common_util.ExtractTarball(dep_path, telemetry_path)
527 except common_util.CommonUtilError as e:
528 shutil.rmtree(telemetry_path)
529 raise DevServerError(str(e))
530
531 # By default all the tarballs extract to test_src but some parts of
532 # the telemetry code specifically hardcoded to exist inside of 'src'.
533 test_src = os.path.join(telemetry_path, 'test_src')
534 try:
535 shutil.move(test_src, src_folder)
536 except shutil.Error:
537 # This can occur if src_folder already exists. Remove and retry move.
538 shutil.rmtree(src_folder)
539 raise DevServerError('Failure in telemetry setup for build %s. Appears'
540 ' that the test_src to src move failed.' % build)
541
542 return src_folder
543
544 @cherrypy.expose
Chris Sosacde6bf42012-05-31 18:36:39 -0700545 def wait_for_status(self, **kwargs):
546 """Waits for background artifacts to be downloaded from Google Storage.
547
Chris Sosa76e44b92013-01-31 12:11:38 -0800548 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Chris Sosacde6bf42012-05-31 18:36:39 -0700549 Args:
550 archive_url: Google Storage URL for the build.
551
552 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700553 http://myhost/wait_for_status?archive_url=gs://chromeos-image-archive/
554 x86-generic/R17-1208.0.0-a1-b338
Chris Sosacde6bf42012-05-31 18:36:39 -0700555 """
Dan Shiffaaf0c2013-08-06 21:22:31 -0700556 async = kwargs.get('async', False)
Chris Sosa76e44b92013-01-31 12:11:38 -0800557 return self.stage(archive_url=kwargs.get('archive_url'),
Dan Shiffaaf0c2013-08-06 21:22:31 -0700558 artifacts='full_payload,test_suites,autotest,stateful',
559 async=async)
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700560
561 @cherrypy.expose
Chris Masone816e38c2012-05-02 12:22:36 -0700562 def stage_debug(self, **kwargs):
563 """Downloads and stages debug symbol payloads from Google Storage.
564
Chris Sosa76e44b92013-01-31 12:11:38 -0800565 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
566 This methods downloads the debug symbol build artifact
567 synchronously, and then stages it for use by symbolicate_dump.
Chris Masone816e38c2012-05-02 12:22:36 -0700568
569 Args:
570 archive_url: Google Storage URL for the build.
571
572 Example URL:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700573 http://myhost/stage_debug?archive_url=gs://chromeos-image-archive/
574 x86-generic/R17-1208.0.0-a1-b338
Chris Masone816e38c2012-05-02 12:22:36 -0700575 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800576 return self.stage(archive_url=kwargs.get('archive_url'),
577 artifacts='symbols')
Chris Masone816e38c2012-05-02 12:22:36 -0700578
579 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800580 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700581 """Symbolicates a minidump using pre-downloaded symbols, returns it.
582
583 Callers will need to POST to this URL with a body of MIME-type
584 "multipart/form-data".
585 The body should include a single argument, 'minidump', containing the
586 binary-formatted minidump to symbolicate.
587
Chris Masone816e38c2012-05-02 12:22:36 -0700588 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800589 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700590 minidump: The binary minidump file to symbolicate.
591 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800592 # Ensure the symbols have been staged.
593 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
594 if self.stage(archive_url=archive_url, artifacts='symbols') != 'Success':
595 raise DevServerError('Failed to stage symbols for %s' % archive_url)
596
Chris Masone816e38c2012-05-02 12:22:36 -0700597 to_return = ''
598 with tempfile.NamedTemporaryFile() as local:
599 while True:
600 data = minidump.file.read(8192)
601 if not data:
602 break
603 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800604
Chris Masone816e38c2012-05-02 12:22:36 -0700605 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800606
607 symbols_directory = os.path.join(downloader.Downloader.GetBuildDir(
608 updater.static_dir, archive_url), 'debug', 'breakpad')
609
610 stackwalk = subprocess.Popen(
611 ['minidump_stackwalk', local.name, symbols_directory],
612 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
613
Chris Masone816e38c2012-05-02 12:22:36 -0700614 to_return, error_text = stackwalk.communicate()
615 if stackwalk.returncode != 0:
616 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
617 error_text, stackwalk.returncode))
618
619 return to_return
620
621 @cherrypy.expose
Scott Zawalski16954532012-03-20 15:31:36 -0400622 def latestbuild(self, **params):
623 """Return a string representing the latest build for a given target.
624
625 Args:
626 target: The build target, typically a combination of the board and the
627 type of build e.g. x86-mario-release.
628 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
629 provided the latest RXX build will be returned.
630 Returns:
631 A string representation of the latest build if one exists, i.e.
632 R19-1993.0.0-a1-b1480.
633 An empty string if no latest could be found.
634 """
635 if not params:
636 return _PrintDocStringAsHTML(self.latestbuild)
637
638 if 'target' not in params:
639 raise cherrypy.HTTPError('500 Internal Server Error',
640 'Error: target= is required!')
641 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700642 return common_util.GetLatestBuildVersion(
Scott Zawalski16954532012-03-20 15:31:36 -0400643 updater.static_dir, params['target'],
644 milestone=params.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700645 except common_util.CommonUtilError as errmsg:
Scott Zawalski16954532012-03-20 15:31:36 -0400646 raise cherrypy.HTTPError('500 Internal Server Error', str(errmsg))
647
648 @cherrypy.expose
Scott Zawalski84a39c92012-01-13 15:12:42 -0500649 def controlfiles(self, **params):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500650 """Return a control file or a list of all known control files.
651
652 Example URL:
653 To List all control files:
beepsbd337242013-07-09 22:44:06 -0700654 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
655 To List all control files for, say, the bvt suite:
656 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500657 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500658 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 -0500659
660 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500661 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500662 control_path: If you want the contents of a control file set this
663 to the path. E.g. client/site_tests/sleeptest/control
664 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -0700665 suite_name: If control_path is not specified but a suite_name is
666 specified, list the control files belonging to that suite instead of
667 all control files. The empty string for suite_name will list all control
668 files for the build.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500669 Returns:
670 Contents of a control file if control_path is provided.
671 A list of control files if no control_path is provided.
672 """
Scott Zawalski4647ce62012-01-03 17:17:28 -0500673 if not params:
674 return _PrintDocStringAsHTML(self.controlfiles)
675
Scott Zawalski84a39c92012-01-13 15:12:42 -0500676 if 'build' not in params:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500677 raise cherrypy.HTTPError('500 Internal Server Error',
Scott Zawalski84a39c92012-01-13 15:12:42 -0500678 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500679
680 if 'control_path' not in params:
beepsbd337242013-07-09 22:44:06 -0700681 if 'suite_name' in params and params['suite_name']:
682 return common_util.GetControlFileListForSuite(
683 updater.static_dir, params['build'], params['suite_name'])
684 else:
685 return common_util.GetControlFileList(
686 updater.static_dir, params['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500687 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700688 return common_util.GetControlFile(
689 updater.static_dir, params['build'], params['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800690
691 @cherrypy.expose
Gilad Arnold6f99b982012-09-12 10:49:40 -0700692 def stage_images(self, **kwargs):
693 """Downloads and stages a Chrome OS image from Google Storage.
694
Chris Sosa76e44b92013-01-31 12:11:38 -0800695 THIS METHOD IS DEPRECATED: use stage(..., artifacts=...) instead.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700696 This method downloads a zipped archive from a specified GS location, then
697 extracts and stages the specified list of images and stages them under
Chris Sosa76e44b92013-01-31 12:11:38 -0800698 static/BOARD/BUILD/. Download is synchronous.
Gilad Arnold6f99b982012-09-12 10:49:40 -0700699
700 Args:
701 archive_url: Google Storage URL for the build.
702 image_types: comma-separated list of images to download, may include
703 'test', 'recovery', and 'base'
704
705 Example URL:
706 http://myhost/stage_images?archive_url=gs://chromeos-image-archive/
707 x86-generic/R17-1208.0.0-a1-b338&image_types=test,base
708 """
Gilad Arnold6f99b982012-09-12 10:49:40 -0700709 image_types = kwargs.get('image_types').split(',')
Chris Sosa76e44b92013-01-31 12:11:38 -0800710 image_types_list = [image + '_image' for image in image_types]
711 self.stage(archive_url=kwargs.get('archive_url'), artifacts=','.join(
712 image_types_list))
Gilad Arnold6f99b982012-09-12 10:49:40 -0700713
714 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -0700715 def xbuddy(self, *args, **kwargs):
716 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -0700717
718 Args:
joycheneaf4cfc2013-07-02 08:38:57 -0700719 path_parts: the path following xbuddy/ in the call url is split into the
720 components of the path.
721 The path can be understood as a build_id/artifact, build_id is
722 composed of "board/version"
723
724 path_parts[0], the board, is the familiar board name, optionally
725 suffixed.
726 path_parts[1], the version, can be the google storage version
727 number, and may also be any of a number of xBuddy defined version
728 aliases that will be translated into the latest built image that
729 fits the description. defaults to latest.
730 path_parts[2], the artifact, is one of a number of image or artifact
731 aliases used by xbuddy, defined in xbuddy:ALIASES. Defaults to test
732
733 Kwargs:
joychen3cb228e2013-06-12 12:13:13 -0700734 return_dir: {true|false}
735 if set to true, returns the url to the update.gz
736 instead.
737
738 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -0700739 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -0700740 or
joycheneaf4cfc2013-07-02 08:38:57 -0700741 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -0700742
743 Returns:
744 A redirect to the image or update file on the devserver.
745 e.g. http://host:port/static/archive/x86-generic-release/
746 R26-4000.0.0/chromium-test-image.bin
747 or if return_dir is True, return path to the folder where
748 image or update file is
749 http://host:port/static/x86-generic-release/R26-4000.0.0/
750 """
751 boolean_string = kwargs.get('return_dir')
752 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
joycheneaf4cfc2013-07-02 08:38:57 -0700753 return_url = self._xbuddy.Get(args,
joychen3cb228e2013-06-12 12:13:13 -0700754 return_dir)
755 if return_dir:
joycheneaf4cfc2013-07-02 08:38:57 -0700756 directory = os.path.join(cherrypy.request.base, return_url)
757 _Log("Directory requested, returning: %s", directory)
758 return directory
joychen3cb228e2013-06-12 12:13:13 -0700759 else:
joycheneaf4cfc2013-07-02 08:38:57 -0700760 return_url = '/' + return_url
761 _Log("Payload requested, returning: %s", return_url)
joychen3cb228e2013-06-12 12:13:13 -0700762 raise cherrypy.HTTPRedirect(return_url, 302)
763
764 @cherrypy.expose
765 def xbuddy_list(self):
766 """Lists the currently available images & time since last access.
767
768 @return: A string representation of a list of tuples
769 [(build_id, time since last access),...]
770 """
771 return self._xbuddy.List()
772
773 @cherrypy.expose
774 def xbuddy_capacity(self):
775 """Returns the number of images cached by xBuddy.
776
777 @return: Capacity of this devserver.
778 """
779 return self._xbuddy.Capacity()
780
781 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700782 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700783 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700784 return ('Welcome to the Dev Server!<br>\n'
785 '<br>\n'
786 'Here are the available methods, click for documentation:<br>\n'
787 '<br>\n'
788 '%s' %
789 '<br>\n'.join(
790 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700791 for name in _FindExposedMethods(
792 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700793
794 @cherrypy.expose
795 def doc(self, *args):
796 """Shows the documentation for available methods / URLs.
797
798 Example:
799 http://myhost/doc/update
800 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700801 name = '/'.join(args)
802 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700803 if not method:
804 raise DevServerError("No exposed method named `%s'" % name)
805 if not method.__doc__:
806 raise DevServerError("No documentation for exposed method `%s'" % name)
807 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -0700808
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700809 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700810 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700811 """Handles an update check from a Chrome OS client.
812
813 The HTTP request should contain the standard Omaha-style XML blob. The URL
814 line may contain an additional intermediate path to the update payload.
815
joychenb0dfe552013-07-30 10:02:06 -0700816 Paths that can be handled by xbuddy are formatted:
817 http://myhost/update/xbuddy/board/version
818
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700819 Example:
820 http://myhost/update/optional/path/to/payload
821 """
joychen346531c2013-07-24 16:55:56 -0700822 if len(args) > 0 and args[0] == 'xbuddy':
823 # Interpret the rest of the path as an xbuddy path
824 label, found = self._xbuddy.Translate(args[1:] + ('full_payload',))
825 if not found:
826 _Log("Update payload not found for %s, xBuddy looking it up.", label)
827 else:
828 label = '/'.join(args)
829
830 _Log('Update label: %s', label)
Gilad Arnold286a0062012-01-12 13:47:02 -0800831 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -0700832 data = cherrypy.request.rfile.read(body_length)
833 return updater.HandleUpdatePing(data, label)
834
Chris Sosa0356d3b2010-09-16 15:46:22 -0700835
Dan Shif5ce2de2013-04-25 16:06:32 -0700836 @cherrypy.expose
837 def check_health(self):
838 """Collect the health status of devserver to see if it's ready for staging.
839
840 @return: A JSON dictionary containing all or some of the following fields:
Dan Shi59ae7092013-06-04 14:37:27 -0700841 free_disk (int): free disk space in GB
842 staging_thread_count (int): number of devserver threads currently
843 staging an image
Dan Shif5ce2de2013-04-25 16:06:32 -0700844 """
845 # Get free disk space.
846 stat = os.statvfs(updater.static_dir)
847 free_disk = stat.f_bsize * stat.f_bavail / 1000000000
848
849 return json.dumps({
850 'free_disk': free_disk,
Dan Shi59ae7092013-06-04 14:37:27 -0700851 'staging_thread_count': DevServerRoot._staging_thread_count,
Dan Shif5ce2de2013-04-25 16:06:32 -0700852 })
853
854
Chris Sosadbc20082012-12-10 13:39:11 -0800855def _CleanCache(cache_dir, wipe):
856 """Wipes any excess cached items in the cache_dir.
857
858 Args:
859 cache_dir: the directory we are wiping from.
860 wipe: If True, wipe all the contents -- not just the excess.
861 """
862 if wipe:
863 # Clear the cache and exit on error.
864 cmd = 'rm -rf %s/*' % cache_dir
865 if os.system(cmd) != 0:
866 _Log('Failed to clear the cache with %s' % cmd)
867 sys.exit(1)
868 else:
869 # Clear all but the last N cached updates
870 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
871 (cache_dir, CACHED_ENTRIES))
872 if os.system(cmd) != 0:
873 _Log('Failed to clean up old delta cache files with %s' % cmd)
874 sys.exit(1)
875
876
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700877def _AddTestingOptions(parser):
878 group = optparse.OptionGroup(
879 parser, 'Advanced Testing Options', 'These are used by test scripts and '
880 'developers writing integration tests utilizing the devserver. They are '
881 'not intended to be really used outside the scope of someone '
882 'knowledgable about the test.')
883 group.add_option('--exit',
884 action='store_true',
885 help='do not start the server (yet pregenerate/clear cache)')
886 group.add_option('--host_log',
887 action='store_true', default=False,
888 help='record history of host update events (/api/hostlog)')
889 group.add_option('--max_updates',
890 metavar='NUM', default= -1, type='int',
891 help='maximum number of update checks handled positively '
892 '(default: unlimited)')
893 group.add_option('--private_key',
894 metavar='PATH', default=None,
895 help='path to the private key in pem format. If this is set '
896 'the devserver will generate update payloads that are '
897 'signed with this key.')
898 group.add_option('--proxy_port',
899 metavar='PORT', default=None, type='int',
900 help='port to have the client connect to -- basically the '
901 'devserver lies to the update to tell it to get the payload '
902 'from a different port that will proxy the request back to '
903 'the devserver. The proxy must be managed outside the '
904 'devserver.')
905 group.add_option('--remote_payload',
906 action='store_true', default=False,
907 help='Payload is being served from a remote machine')
908 group.add_option('-u', '--urlbase',
909 metavar='URL',
910 help='base URL for update images, other than the '
911 'devserver. Use in conjunction with remote_payload.')
912 parser.add_option_group(group)
913
914
915def _AddUpdateOptions(parser):
916 group = optparse.OptionGroup(
917 parser, 'Autoupdate Options', 'These options can be used to change '
918 'how the devserver either generates or serve update payloads. Please '
919 'note that all of these option affect how a payload is generated and so '
920 'do not work in archive-only mode.')
921 group.add_option('--board',
922 help='By default the devserver will create an update '
923 'payload from the latest image built for the board '
924 'a device that is requesting an update has. When we '
925 'pre-generate an update (see below) and we do not specify '
926 'another update_type option like image or payload, the '
927 'devserver needs to know the board to generate the latest '
928 'image for. This is that board.')
929 group.add_option('--critical_update',
930 action='store_true', default=False,
931 help='Present update payload as critical')
932 group.add_option('--for_vm',
933 dest='vm', action='store_true',
934 help='DEPRECATED: see no_patch_kernel.')
935 group.add_option('--image',
936 metavar='FILE',
937 help='Generate and serve an update using this image to any '
938 'device that requests an update.')
939 group.add_option('--no_patch_kernel',
940 dest='patch_kernel', action='store_false', default=True,
941 help='When generating an update payload, do not patch the '
942 'kernel with kernel verification blob from the stateful '
943 'partition.')
944 group.add_option('--payload',
945 metavar='PATH',
946 help='use the update payload from specified directory '
947 '(update.gz).')
948 group.add_option('-p', '--pregenerate_update',
949 action='store_true', default=False,
950 help='pre-generate the update payload before accepting '
951 'update requests. Useful to help debug payload generation '
952 'issues quickly. Also if an update payload will take a '
953 'long time to generate, a client may timeout if you do not'
954 'pregenerate the update.')
955 group.add_option('--src_image',
956 metavar='PATH', default='',
957 help='If specified, delta updates will be generated using '
958 'this image as the source image. Delta updates are when '
959 'you are updating from a "source image" to a another '
960 'image.')
961 parser.add_option_group(group)
962
963
964def _AddProductionOptions(parser):
965 group = optparse.OptionGroup(
966 parser, 'Advanced Server Options', 'These options can be used to changed '
967 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700968 group.add_option('--clear_cache',
969 action='store_true', default=False,
970 help='At startup, removes all cached entries from the'
971 'devserver\'s cache.')
972 group.add_option('--logfile',
973 metavar='PATH',
974 help='log output to this file instead of stdout')
975 group.add_option('--production',
976 action='store_true', default=False,
977 help='have the devserver use production values when '
978 'starting up. This includes using more threads and '
979 'performing less logging.')
980 parser.add_option_group(group)
981
982
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700983def _MakeLogHandler(logfile):
984 """Create a LogHandler instance used to log all messages."""
985 hdlr_cls = handlers.TimedRotatingFileHandler
986 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
987 backupCount=_LOG_ROTATION_BACKUP)
988 # The cherrypy documentation says to use the _cplogging module for
989 # this, even though it's named as a private module.
990 # pylint: disable=W0212
991 hdlr.setFormatter(cherrypy._cplogging.logfmt)
992 return hdlr
993
994
Chris Sosacde6bf42012-05-31 18:36:39 -0700995def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -0700996 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -0800997 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -0700998
999 # get directory that the devserver is run from
1000 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001001 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001002 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001003 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001004 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001005 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001006 parser.add_option('--port',
1007 default=8080, type='int',
1008 help='port for the dev server to use (default: 8080)')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001009 parser.add_option('-t', '--test_image',
1010 action='store_true',
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001011 help='If set, look for the chromiumos_test_image.bin file '
1012 'when generating update payloads rather than the '
1013 'chromiumos_image.bin which is the default.')
joychen5260b9a2013-07-16 14:48:01 -07001014 parser.add_option('-x', '--xbuddy_manage_builds',
1015 action='store_true',
1016 default=False,
1017 help='If set, allow xbuddy to manage images in'
1018 'build/images.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001019 _AddProductionOptions(parser)
1020 _AddUpdateOptions(parser)
1021 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001022 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001023
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001024 # Handle options that must be set globally in cherrypy. Do this
1025 # work up front, because calls to _Log() below depend on this
1026 # initialization.
1027 if options.production:
1028 cherrypy.config.update({'environment': 'production'})
1029 if not options.logfile:
1030 cherrypy.config.update({'log.screen': True})
1031 else:
1032 cherrypy.config.update({'log.error_file': '',
1033 'log.access_file': ''})
1034 hdlr = _MakeLogHandler(options.logfile)
1035 # Pylint can't seem to process these two calls properly
1036 # pylint: disable=E1101
1037 cherrypy.log.access_log.addHandler(hdlr)
1038 cherrypy.log.error_log.addHandler(hdlr)
1039 # pylint: enable=E1101
1040
Chris Sosa7c931362010-10-11 19:49:01 -07001041 root_dir = os.path.realpath('%s/../..' % devserver_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001042
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001043 # TODO(sosa): Remove after deprecation.
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001044 if options.vm:
1045 options.patch_kernel = False
1046
joychened64b222013-06-21 16:39:34 -07001047 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001048 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001049
joychened64b222013-06-21 16:39:34 -07001050 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001051 # If our devserver is only supposed to serve payloads, we shouldn't be
1052 # mucking with the cache at all. If the devserver hadn't previously
1053 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001054 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001055 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001056 else:
1057 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001058
Chris Sosadbc20082012-12-10 13:39:11 -08001059 _Log('Using cache directory %s' % cache_dir)
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001060 _Log('Source root is %s' % root_dir)
joychened64b222013-06-21 16:39:34 -07001061 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001062
Chris Sosa6a3697f2013-01-29 16:44:43 -08001063 # We allow global use here to share with cherrypy classes.
1064 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001065 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001066 updater = autoupdate.Autoupdate(
1067 root_dir=root_dir,
joychened64b222013-06-21 16:39:34 -07001068 static_dir=options.static_dir,
Andrew de los Reyes52620802010-04-12 13:40:07 -07001069 urlbase=options.urlbase,
1070 test_image=options.test_image,
Chris Sosa5d342a22010-09-28 16:54:41 -07001071 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001072 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001073 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -07001074 src_image=options.src_image,
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001075 patch_kernel=options.patch_kernel,
Chris Sosa08d55a22011-01-19 16:08:02 -08001076 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001077 copy_to_static_root=not options.exit,
1078 private_key=options.private_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001079 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001080 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001081 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001082 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001083 )
Chris Sosa7c931362010-10-11 19:49:01 -07001084
Chris Sosa6a3697f2013-01-29 16:44:43 -08001085 if options.pregenerate_update:
1086 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -07001087
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001088 if options.exit:
1089 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001090
joychen5260b9a2013-07-16 14:48:01 -07001091 _xbuddy = xbuddy.XBuddy(options.xbuddy_manage_builds,
joychenb0dfe552013-07-30 10:02:06 -07001092 options.board,
joychen5260b9a2013-07-16 14:48:01 -07001093 root_dir=root_dir,
joychenb0dfe552013-07-30 10:02:06 -07001094 static_dir=options.static_dir,
1095 )
joychen3cb228e2013-06-12 12:13:13 -07001096 dev_server = DevServerRoot(_xbuddy)
1097
1098 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001099
1100
1101if __name__ == '__main__':
1102 main()