blob: 5d627dd455317cf76a5ec7ed691346260fb67197 [file] [log] [blame]
Gabe Black3b567202015-09-23 14:07:59 -07001#!/usr/bin/python2
Chris Sosa7c931362010-10-11 19:49:01 -07002
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
Gabe Black3b567202015-09-23 14:07:59 -070042from __future__ import print_function
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
Dan Shiafd0e492015-05-27 14:23:51 -070054import time
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070055import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070056from logging import handlers
57
58import cherrypy
Chris Sosa855b8932013-08-21 13:24:55 -070059from cherrypy import _cplogging as cplogging
60from cherrypy.process import plugins
rtc@google.comded22402009-10-26 22:36:21 +000061
Chris Sosa0356d3b2010-09-16 15:46:22 -070062import autoupdate
Chris Sosa75490802013-09-30 17:21:45 -070063import build_artifact
Gilad Arnold11fbef42014-02-10 11:04:13 -080064import cherrypy_ext
Gilad Arnoldc65330c2012-09-20 15:17:48 -070065import common_util
Simran Basief83d6a2014-08-28 14:32:01 -070066import devserver_constants
Chris Sosa47a7d4e2012-03-28 11:26:55 -070067import downloader
Chris Sosa7cd23202013-10-15 17:22:57 -070068import gsutil_util
Gilad Arnoldc65330c2012-09-20 15:17:48 -070069import log_util
joychen3cb228e2013-06-12 12:13:13 -070070import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070071
Gilad Arnoldc65330c2012-09-20 15:17:48 -070072# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080073def _Log(message, *args):
74 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 15:46:22 -070075
Dan Shiafd0e492015-05-27 14:23:51 -070076try:
77 import psutil
78except ImportError:
79 # Ignore psutil import failure. This is for backwards compatibility, so
80 # "cros flash" can still update duts with build without psutil installed.
81 # The reason is that, during cros flash, local devserver code is copied over
82 # to DUT, and devserver will be running inside DUT to stage the build.
83 _Log('Python module psutil is not installed, devserver load data will not be '
84 'collected')
85 psutil = None
Dan Shi94dcbe82015-06-08 20:51:13 -070086except OSError as e:
87 # Ignore error like following. psutil may not work properly in builder. Ignore
88 # the error as load information of devserver is not used in builder.
89 # OSError: [Errno 2] No such file or directory: '/dev/pts/0'
90 _Log('psutil is failed to be imported, error: %s. devserver load data will '
91 'not be collected.', e)
92 psutil = None
93
Frank Farzan40160872011-12-12 18:39:18 -080094
Chris Sosa417e55d2011-01-25 16:40:48 -080095CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080096
Simran Basi4baad082013-02-14 13:39:18 -080097TELEMETRY_FOLDER = 'telemetry_src'
98TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
99 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -0700100 'dep-chrome_test.tar.bz2',
101 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -0800102
Chris Sosa0356d3b2010-09-16 15:46:22 -0700103# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +0000104updater = None
rtc@google.comded22402009-10-26 22:36:21 +0000105
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700106# Log rotation parameters. These settings correspond to once a week
J. Richard Barnette6dfa5342013-06-04 11:48:56 -0700107# at midnight between Friday and Saturday, with about three months
108# of old logs kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700109#
110# For more, see the documentation for
111# logging.handlers.TimedRotatingFileHandler
J. Richard Barnette6dfa5342013-06-04 11:48:56 -0700112_LOG_ROTATION_TIME = 'W4'
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700113_LOG_ROTATION_BACKUP = 13
114
Dan Shiafd0e492015-05-27 14:23:51 -0700115# Number of seconds between the collection of disk and network IO counters.
116STATS_INTERVAL = 10.0
Frank Farzan40160872011-12-12 18:39:18 -0800117
Chris Sosa9164ca32012-03-28 11:04:50 -0700118class DevServerError(Exception):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700119 """Exception class used by this module."""
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700120
121
Dan Shiafd0e492015-05-27 14:23:51 -0700122def require_psutil():
Gabe Black3b567202015-09-23 14:07:59 -0700123 """Decorator for functions require psutil to run."""
Dan Shiafd0e492015-05-27 14:23:51 -0700124 def deco_require_psutil(func):
125 """Wrapper of the decorator function.
126
Gabe Black3b567202015-09-23 14:07:59 -0700127 Args:
128 func: function to be called.
Dan Shiafd0e492015-05-27 14:23:51 -0700129 """
130 def func_require_psutil(*args, **kwargs):
131 """Decorator for functions require psutil to run.
132
133 If psutil is not installed, skip calling the function.
134
Gabe Black3b567202015-09-23 14:07:59 -0700135 Args:
136 *args: arguments for function to be called.
137 **kwargs: keyword arguments for function to be called.
Dan Shiafd0e492015-05-27 14:23:51 -0700138 """
139 if psutil:
140 return func(*args, **kwargs)
141 else:
142 _Log('Python module psutil is not installed. Function call %s is '
143 'skipped.' % func)
144 return func_require_psutil
145 return deco_require_psutil
146
147
Gabe Black3b567202015-09-23 14:07:59 -0700148def _canonicalize_archive_url(archive_url):
149 """Canonicalizes archive_url strings.
150
151 Raises:
152 DevserverError: if archive_url is not set.
153 """
154 if archive_url:
155 if not archive_url.startswith('gs://'):
156 raise DevServerError("Archive URL isn't from Google Storage (%s) ." %
157 archive_url)
158
159 return archive_url.rstrip('/')
160 else:
161 raise DevServerError("Must specify an archive_url in the request")
162
163
164def _canonicalize_local_path(local_path):
165 """Canonicalizes |local_path| strings.
166
167 Raises:
168 DevserverError: if |local_path| is not set.
169 """
170 # Restrict staging of local content to only files within the static
171 # directory.
172 local_path = os.path.abspath(local_path)
173 if not local_path.startswith(updater.static_dir):
174 raise DevServerError('Local path %s must be a subdirectory of the static'
175 ' directory: %s' % (local_path, updater.static_dir))
176
177 return local_path.rstrip('/')
178
179
180def _get_artifacts(kwargs):
181 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
182
183 Raises:
184 DevserverError if no artifacts would be returned.
185 """
186 artifacts = kwargs.get('artifacts')
187 files = kwargs.get('files')
188 if not artifacts and not files:
189 raise DevServerError('No artifacts specified.')
190
191 # Note we NEED to coerce files to a string as we get raw unicode from
192 # cherrypy and we treat files as strings elsewhere in the code.
193 return (str(artifacts).split(',') if artifacts else [],
194 str(files).split(',') if files else [])
195
196
197def _get_downloader(kwargs):
198 """Returns the downloader based on passed in arguments.
199
200 Args:
201 kwargs: Keyword arguments for the request.
202 """
203 local_path = kwargs.get('local_path')
204 if local_path:
205 local_path = _canonicalize_local_path(local_path)
206
207 dl = None
208 if local_path:
209 dl = downloader.LocalDownloader(updater.static_dir, local_path)
210
211 # Only Android build requires argument build_id. If it's not set, assume
212 # the download request is for ChromeOS.
213 build_id = kwargs.get('build_id', None)
214 if not build_id:
215 archive_url = kwargs.get('archive_url')
216 if not archive_url and not local_path:
217 raise DevServerError('Requires archive_url or local_path to be '
218 'specified.')
219 if archive_url and local_path:
220 raise DevServerError('archive_url and local_path can not both be '
221 'specified.')
222 if not dl:
223 archive_url = _canonicalize_archive_url(archive_url)
224 dl = downloader.GoogleStorageDownloader(updater.static_dir, archive_url)
225 elif not dl:
226 target = kwargs.get('target', None)
227 if not target:
228 raise DevServerError('target must be specified for Android build.')
229 dl = downloader.LaunchControlDownloader(updater.static_dir, build_id,
230 target)
231
232 return dl
233
234
235def _get_downloader_and_factory(kwargs):
236 """Returns the downloader and artifact factory based on passed in arguments.
237
238 Args:
239 kwargs: Keyword arguments for the request.
240 """
241 artifacts, files = _get_artifacts(kwargs)
242 dl = _get_downloader(kwargs)
243
244 if (isinstance(dl, downloader.GoogleStorageDownloader) or
245 isinstance(dl, downloader.LocalDownloader)):
246 factory_class = build_artifact.ChromeOSArtifactFactory
247 elif isinstance(dl, downloader.LaunchControlDownloader):
248 factory_class = build_artifact.AndroidArtifactFactory
249 else:
250 raise DevServerError('Unrecognized value for downloader type: %s' %
251 type(dl))
252
253 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
254
255 return dl, factory
256
257
Scott Zawalski4647ce62012-01-03 17:17:28 -0500258def _LeadingWhiteSpaceCount(string):
259 """Count the amount of leading whitespace in a string.
260
261 Args:
262 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800263
Scott Zawalski4647ce62012-01-03 17:17:28 -0500264 Returns:
265 number of white space chars before characters start.
266 """
Gabe Black3b567202015-09-23 14:07:59 -0700267 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500268 if matched:
269 return len(matched.group())
270
271 return 0
272
273
274def _PrintDocStringAsHTML(func):
275 """Make a functions docstring somewhat HTML style.
276
277 Args:
278 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800279
Scott Zawalski4647ce62012-01-03 17:17:28 -0500280 Returns:
281 A string that is somewhat formated for a web browser.
282 """
283 # TODO(scottz): Make this parse Args/Returns in a prettier way.
284 # Arguments could be bolded and indented etc.
285 html_doc = []
286 for line in func.__doc__.splitlines():
287 leading_space = _LeadingWhiteSpaceCount(line)
288 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700289 line = '&nbsp;' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500290
291 html_doc.append('<BR>%s' % line)
292
293 return '\n'.join(html_doc)
294
295
Simran Basief83d6a2014-08-28 14:32:01 -0700296def _GetUpdateTimestampHandler(static_dir):
297 """Returns a handler to update directory staged.timestamp.
298
299 This handler resets the stage.timestamp whenever static content is accessed.
300
301 Args:
302 static_dir: Directory from which static content is being staged.
303
304 Returns:
305 A cherrypy handler to update the timestamp of accessed content.
306 """
307 def UpdateTimestampHandler():
308 if not '404' in cherrypy.response.status:
309 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
310 cherrypy.request.path_info)
311 if build_match:
312 build_dir = os.path.join(static_dir, build_match.group('build'))
313 downloader.Downloader.TouchTimestampForStaged(build_dir)
314 return UpdateTimestampHandler
315
316
Chris Sosa7c931362010-10-11 19:49:01 -0700317def _GetConfig(options):
318 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800319
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800320 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800321 # Fall back to IPv4 when python is not configured with IPv6.
322 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800323 socket_host = '0.0.0.0'
324
Simran Basief83d6a2014-08-28 14:32:01 -0700325 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
326 # on the on_end_resource hook. This hook is called once processing is
327 # complete and the response is ready to be returned.
328 cherrypy.tools.update_timestamp = cherrypy.Tool(
329 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
330
Gabe Black3b567202015-09-23 14:07:59 -0700331 base_config = {'global':
332 {'server.log_request_headers': True,
333 'server.protocol_version': 'HTTP/1.1',
334 'server.socket_host': socket_host,
335 'server.socket_port': int(options.port),
336 'response.timeout': 6000,
337 'request.show_tracebacks': True,
338 'server.socket_timeout': 60,
339 'server.thread_pool': 2,
340 'engine.autoreload.on': False,
341 },
342 '/api':
343 {
344 # Gets rid of cherrypy parsing post file for args.
345 'request.process_request_body': False,
346 },
347 '/build':
348 {'response.timeout': 100000,
349 },
350 '/update':
351 {
352 # Gets rid of cherrypy parsing post file for args.
353 'request.process_request_body': False,
354 'response.timeout': 10000,
355 },
356 # Sets up the static dir for file hosting.
357 '/static':
358 {'tools.staticdir.dir': options.static_dir,
359 'tools.staticdir.on': True,
360 'response.timeout': 10000,
361 'tools.update_timestamp.on': True,
362 },
363 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700364 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700365 base_config['global'].update({'server.thread_pool': 150})
Chris Sosa7cd23202013-10-15 17:22:57 -0700366 # TODO(sosa): Do this more cleanly.
367 gsutil_util.GSUTIL_ATTEMPTS = 5
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500368
Chris Sosa7c931362010-10-11 19:49:01 -0700369 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000370
Darin Petkove17164a2010-08-11 13:24:41 -0700371
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700372def _GetRecursiveMemberObject(root, member_list):
373 """Returns an object corresponding to a nested member list.
374
375 Args:
376 root: the root object to search
377 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800378
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700379 Returns:
380 An object corresponding to the member name list; None otherwise.
381 """
382 for member in member_list:
383 next_root = root.__class__.__dict__.get(member)
384 if not next_root:
385 return None
386 root = next_root
387 return root
388
389
390def _IsExposed(name):
391 """Returns True iff |name| has an `exposed' attribute and it is set."""
392 return hasattr(name, 'exposed') and name.exposed
393
394
Gilad Arnold748c8322012-10-12 09:51:35 -0700395def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700396 """Returns a CherryPy-exposed method, if such exists.
397
398 Args:
399 root: the root object for searching
400 nested_member: a slash-joined path to the nested member
401 ignored: method paths to be ignored
Don Garrettf84631a2014-01-07 18:21:26 -0800402
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700403 Returns:
404 A function object corresponding to the path defined by |member_list| from
405 the |root| object, if the function is exposed and not ignored; None
406 otherwise.
407 """
Gilad Arnold748c8322012-10-12 09:51:35 -0700408 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700409 _GetRecursiveMemberObject(root, nested_member.split('/')))
Gabe Black3b567202015-09-23 14:07:59 -0700410 if method and type(method) == types.FunctionType and _IsExposed(method):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700411 return method
412
413
Gilad Arnold748c8322012-10-12 09:51:35 -0700414def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700415 """Finds exposed CherryPy methods.
416
417 Args:
418 root: the root object for searching
419 prefix: slash-joined chain of members leading to current object
420 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800421
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700422 Returns:
423 List of exposed URLs that are not unlisted.
424 """
425 method_list = []
426 for member in sorted(root.__class__.__dict__.keys()):
427 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700428 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700429 continue
430 member_obj = root.__class__.__dict__[member]
431 if _IsExposed(member_obj):
432 if type(member_obj) == types.FunctionType:
433 method_list.append(prefixed_member)
434 else:
435 method_list += _FindExposedMethods(
436 member_obj, prefixed_member, unlisted)
437 return method_list
438
439
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700440class ApiRoot(object):
441 """RESTful API for Dev Server information."""
442 exposed = True
443
444 @cherrypy.expose
445 def hostinfo(self, ip):
446 """Returns a JSON dictionary containing information about the given ip.
447
Gilad Arnold1b908392012-10-05 11:36:27 -0700448 Args:
449 ip: address of host whose info is requested
Don Garrettf84631a2014-01-07 18:21:26 -0800450
Gilad Arnold1b908392012-10-05 11:36:27 -0700451 Returns:
452 A JSON dictionary containing all or some of the following fields:
453 last_event_type (int): last update event type received
454 last_event_status (int): last update event status received
455 last_known_version (string): last known version reported in update ping
456 forced_update_label (string): update label to force next update ping to
457 use, set by setnextupdate
458 See the OmahaEvent class in update_engine/omaha_request_action.h for
459 event type and status code definitions. If the ip does not exist an empty
460 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700461
Gilad Arnold1b908392012-10-05 11:36:27 -0700462 Example URL:
463 http://myhost/api/hostinfo?ip=192.168.1.5
464 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700465 return updater.HandleHostInfoPing(ip)
466
467 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800468 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700469 """Returns a JSON object containing a log of host event.
470
471 Args:
472 ip: address of host whose event log is requested, or `all'
Don Garrettf84631a2014-01-07 18:21:26 -0800473
Gilad Arnold1b908392012-10-05 11:36:27 -0700474 Returns:
475 A JSON encoded list (log) of dictionaries (events), each of which
476 containing a `timestamp' and other event fields, as described under
477 /api/hostinfo.
478
479 Example URL:
480 http://myhost/api/hostlog?ip=192.168.1.5
481 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800482 return updater.HandleHostLogPing(ip)
483
484 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700485 def setnextupdate(self, ip):
486 """Allows the response to the next update ping from a host to be set.
487
488 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 11:36:27 -0700489 /update command.
490 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700491 body_length = int(cherrypy.request.headers['Content-Length'])
492 label = cherrypy.request.rfile.read(body_length)
493
494 if label:
495 label = label.strip()
496 if label:
497 return updater.HandleSetUpdatePing(ip, label)
Chris Sosa4b951602014-04-09 20:26:07 -0700498 raise common_util.DevServerHTTPError(400, 'No label provided.')
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700499
500
Gilad Arnold55a2a372012-10-02 09:46:32 -0700501 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800502 def fileinfo(self, *args):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700503 """Returns information about a given staged file.
504
505 Args:
Don Garrettf84631a2014-01-07 18:21:26 -0800506 args: path to the file inside the server's static staging directory
507
Gilad Arnold55a2a372012-10-02 09:46:32 -0700508 Returns:
509 A JSON encoded dictionary with information about the said file, which may
510 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700511 size (int): the file size in bytes
512 sha1 (string): a base64 encoded SHA1 hash
513 sha256 (string): a base64 encoded SHA256 hash
514
515 Example URL:
516 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700517 """
Don Garrettf84631a2014-01-07 18:21:26 -0800518 file_path = os.path.join(updater.static_dir, *args)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700519 if not os.path.exists(file_path):
520 raise DevServerError('file not found: %s' % file_path)
521 try:
522 file_size = os.path.getsize(file_path)
523 file_sha1 = common_util.GetFileSha1(file_path)
524 file_sha256 = common_util.GetFileSha256(file_path)
525 except os.error, e:
526 raise DevServerError('failed to get info for file %s: %s' %
Gilad Arnolde74b3812013-04-22 11:27:38 -0700527 (file_path, e))
528
529 is_delta = autoupdate.Autoupdate.IsDeltaFormatFile(file_path)
530
531 return json.dumps({
532 autoupdate.Autoupdate.SIZE_ATTR: file_size,
533 autoupdate.Autoupdate.SHA1_ATTR: file_sha1,
534 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
535 autoupdate.Autoupdate.ISDELTA_ATTR: is_delta
536 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700537
Chris Sosa76e44b92013-01-31 12:11:38 -0800538
David Rochberg7c79a812011-01-19 14:24:45 -0500539class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700540 """The Root Class for the Dev Server.
541
542 CherryPy works as follows:
543 For each method in this class, cherrpy interprets root/path
544 as a call to an instance of DevServerRoot->method_name. For example,
545 a call to http://myhost/build will call build. CherryPy automatically
546 parses http args and places them as keyword arguments in each method.
547 For paths http://myhost/update/dir1/dir2, you can use *args so that
548 cherrypy uses the update method and puts the extra paths in args.
549 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700550 # Method names that should not be listed on the index page.
551 _UNLISTED_METHODS = ['index', 'doc']
552
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700553 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700554
Dan Shi59ae7092013-06-04 14:37:27 -0700555 # Number of threads that devserver is staging images.
556 _staging_thread_count = 0
557 # Lock used to lock increasing/decreasing count.
558 _staging_thread_count_lock = threading.Lock()
559
Dan Shiafd0e492015-05-27 14:23:51 -0700560 @require_psutil()
561 def _refresh_io_stats(self):
562 """A call running in a thread to update IO stats periodically."""
563 prev_disk_io_counters = psutil.disk_io_counters()
564 prev_network_io_counters = psutil.net_io_counters()
565 prev_read_time = time.time()
566 while True:
567 time.sleep(STATS_INTERVAL)
568 now = time.time()
569 interval = now - prev_read_time
570 prev_read_time = now
571 # Disk IO is for all disks.
572 disk_io_counters = psutil.disk_io_counters()
573 network_io_counters = psutil.net_io_counters()
574
575 self.disk_read_bytes_per_sec = (
576 disk_io_counters.read_bytes -
577 prev_disk_io_counters.read_bytes)/interval
578 self.disk_write_bytes_per_sec = (
579 disk_io_counters.write_bytes -
580 prev_disk_io_counters.write_bytes)/interval
581 prev_disk_io_counters = disk_io_counters
582
583 self.network_sent_bytes_per_sec = (
584 network_io_counters.bytes_sent -
585 prev_network_io_counters.bytes_sent)/interval
586 self.network_recv_bytes_per_sec = (
587 network_io_counters.bytes_recv -
588 prev_network_io_counters.bytes_recv)/interval
589 prev_network_io_counters = network_io_counters
590
591 @require_psutil()
592 def _start_io_stat_thread(self):
Gabe Black3b567202015-09-23 14:07:59 -0700593 """Start the thread to collect IO stats."""
Dan Shiafd0e492015-05-27 14:23:51 -0700594 thread = threading.Thread(target=self._refresh_io_stats)
595 thread.daemon = True
596 thread.start()
597
joychen3cb228e2013-06-12 12:13:13 -0700598 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700599 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800600 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700601 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500602
Dan Shiafd0e492015-05-27 14:23:51 -0700603 # Cache of disk IO stats, a thread refresh the stats every 10 seconds.
604 # lock is not used for these variables as the only thread writes to these
605 # variables is _refresh_io_stats.
606 self.disk_read_bytes_per_sec = 0
607 self.disk_write_bytes_per_sec = 0
608 # Cache of network IO stats.
609 self.network_sent_bytes_per_sec = 0
610 self.network_recv_bytes_per_sec = 0
611 self._start_io_stat_thread()
612
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700613 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500614 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700615 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700616 import builder
617 if self._builder is None:
618 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500619 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700620
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700621 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700622 def is_staged(self, **kwargs):
623 """Check if artifacts have been downloaded.
624
Chris Sosa6b0c6172013-08-05 17:01:33 -0700625 async: True to return without waiting for download to complete.
626 artifacts: Comma separated list of named artifacts to download.
627 These are defined in artifact_info and have their implementation
628 in build_artifact.py.
629 files: Comma separated list of file artifacts to stage. These
630 will be available as is in the corresponding static directory with no
631 custom post-processing.
632
633 returns: True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700634
635 Example:
636 To check if autotest and test_suites are staged:
637 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
638 artifacts=autotest,test_suites
639 """
Gabe Black3b567202015-09-23 14:07:59 -0700640 dl, factory = _get_downloader_and_factory(kwargs)
641 return str(dl.IsStaged(factory))
Dan Shi59ae7092013-06-04 14:37:27 -0700642
Chris Sosa76e44b92013-01-31 12:11:38 -0800643 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800644 def list_image_dir(self, **kwargs):
645 """Take an archive url and list the contents in its staged directory.
646
647 Args:
648 kwargs:
649 archive_url: Google Storage URL for the build.
650
651 Example:
652 To list the contents of where this devserver should have staged
653 gs://image-archive/<board>-release/<build> call:
654 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
655
656 Returns:
657 A string with information about the contents of the image directory.
658 """
Gabe Black3b567202015-09-23 14:07:59 -0700659 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800660 try:
Gabe Black3b567202015-09-23 14:07:59 -0700661 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800662 except build_artifact.ArtifactDownloadError as e:
663 return 'Cannot list the contents of staged artifacts. %s' % e
664 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700665 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800666 return image_dir_contents
667
668 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800669 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700670 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800671
Gabe Black3b567202015-09-23 14:07:59 -0700672 Downloads and caches build artifacts, possibly from a Google Storage URL,
673 or from Android's LaunchControl. Returns once these have been downloaded
674 on the devserver. A call to this will attempt to cache non-specified
675 artifacts in the background for the given from the given URL following
676 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800677 artifacts is explicitly defined in the build_artifact module.
678
679 These artifacts will then be available from the static/ sub-directory of
680 the devserver.
681
682 Args:
683 archive_url: Google Storage URL for the build.
Simran Basi4243a862014-12-12 12:48:33 -0800684 local_path: Local path for the build.
Dan Shif8eb0d12013-08-01 17:52:06 -0700685 async: True to return without waiting for download to complete.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700686 artifacts: Comma separated list of named artifacts to download.
687 These are defined in artifact_info and have their implementation
688 in build_artifact.py.
689 files: Comma separated list of files to stage. These
690 will be available as is in the corresponding static directory with no
691 custom post-processing.
Chris Sosa76e44b92013-01-31 12:11:38 -0800692
693 Example:
694 To download the autotest and test suites tarballs:
695 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
696 artifacts=autotest,test_suites
697 To download the full update payload:
698 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
699 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700700 To download just a file called blah.bin:
701 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
702 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800703
704 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700705 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800706
707 Note for this example, relative path is the archive_url stripped of its
708 basename i.e. path/ in the examples above. Specific example:
709
710 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
711
712 Will get staged to:
713
joychened64b222013-06-21 16:39:34 -0700714 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Chris Sosa76e44b92013-01-31 12:11:38 -0800715 """
Gabe Black3b567202015-09-23 14:07:59 -0700716 dl, factory = _get_downloader_and_factory(kwargs)
717
Dan Shi59ae7092013-06-04 14:37:27 -0700718 with DevServerRoot._staging_thread_count_lock:
719 DevServerRoot._staging_thread_count += 1
720 try:
Gabe Black3b567202015-09-23 14:07:59 -0700721 async = kwargs.get('async', False)
722 dl.Download(factory, async=async)
Dan Shi59ae7092013-06-04 14:37:27 -0700723 finally:
724 with DevServerRoot._staging_thread_count_lock:
725 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800726 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700727
728 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800729 def setup_telemetry(self, **kwargs):
730 """Extracts and sets up telemetry
731
732 This method goes through the telemetry deps packages, and stages them on
733 the devserver to be used by the drones and the telemetry tests.
734
735 Args:
736 archive_url: Google Storage URL for the build.
737
738 Returns:
739 Path to the source folder for the telemetry codebase once it is staged.
740 """
Gabe Black3b567202015-09-23 14:07:59 -0700741 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 13:39:18 -0800742
Gabe Black3b567202015-09-23 14:07:59 -0700743 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -0800744 deps_path = os.path.join(build_path, 'autotest/packages')
745 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
746 src_folder = os.path.join(telemetry_path, 'src')
747
748 with self._telemetry_lock_dict.lock(telemetry_path):
749 if os.path.exists(src_folder):
750 # Telemetry is already fully stage return
751 return src_folder
752
753 common_util.MkDirP(telemetry_path)
754
755 # Copy over the required deps tar balls to the telemetry directory.
756 for dep in TELEMETRY_DEPS:
757 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700758 if not os.path.exists(dep_path):
759 # This dep does not exist (could be new), do not extract it.
760 continue
Simran Basi4baad082013-02-14 13:39:18 -0800761 try:
762 common_util.ExtractTarball(dep_path, telemetry_path)
763 except common_util.CommonUtilError as e:
764 shutil.rmtree(telemetry_path)
765 raise DevServerError(str(e))
766
767 # By default all the tarballs extract to test_src but some parts of
768 # the telemetry code specifically hardcoded to exist inside of 'src'.
769 test_src = os.path.join(telemetry_path, 'test_src')
770 try:
771 shutil.move(test_src, src_folder)
772 except shutil.Error:
773 # This can occur if src_folder already exists. Remove and retry move.
774 shutil.rmtree(src_folder)
Gabe Black3b567202015-09-23 14:07:59 -0700775 raise DevServerError(
776 'Failure in telemetry setup for build %s. Appears that the '
777 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -0800778
779 return src_folder
780
781 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800782 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700783 """Symbolicates a minidump using pre-downloaded symbols, returns it.
784
785 Callers will need to POST to this URL with a body of MIME-type
786 "multipart/form-data".
787 The body should include a single argument, 'minidump', containing the
788 binary-formatted minidump to symbolicate.
789
Chris Masone816e38c2012-05-02 12:22:36 -0700790 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800791 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700792 minidump: The binary minidump file to symbolicate.
793 """
Gabe Black3b567202015-09-23 14:07:59 -0700794 kwargs['artifacts'] = 'symbols'
795 dl = _get_downloader(kwargs)
796
Chris Sosa76e44b92013-01-31 12:11:38 -0800797 # Ensure the symbols have been staged.
Gabe Black3b567202015-09-23 14:07:59 -0700798 if self.stage(**kwargs) != 'Success':
799 raise DevServerError('Failed to stage symbols for %s' %
800 dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -0800801
Chris Masone816e38c2012-05-02 12:22:36 -0700802 to_return = ''
803 with tempfile.NamedTemporaryFile() as local:
804 while True:
805 data = minidump.file.read(8192)
806 if not data:
807 break
808 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800809
Chris Masone816e38c2012-05-02 12:22:36 -0700810 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800811
Gabe Black3b567202015-09-23 14:07:59 -0700812 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -0800813
814 stackwalk = subprocess.Popen(
815 ['minidump_stackwalk', local.name, symbols_directory],
816 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
817
Chris Masone816e38c2012-05-02 12:22:36 -0700818 to_return, error_text = stackwalk.communicate()
819 if stackwalk.returncode != 0:
820 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
821 error_text, stackwalk.returncode))
822
823 return to_return
824
825 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800826 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -0400827 """Return a string representing the latest build for a given target.
828
829 Args:
830 target: The build target, typically a combination of the board and the
831 type of build e.g. x86-mario-release.
832 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
833 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -0800834
Scott Zawalski16954532012-03-20 15:31:36 -0400835 Returns:
836 A string representation of the latest build if one exists, i.e.
837 R19-1993.0.0-a1-b1480.
838 An empty string if no latest could be found.
839 """
Don Garrettf84631a2014-01-07 18:21:26 -0800840 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -0400841 return _PrintDocStringAsHTML(self.latestbuild)
842
Don Garrettf84631a2014-01-07 18:21:26 -0800843 if 'target' not in kwargs:
Chris Sosa4b951602014-04-09 20:26:07 -0700844 raise common_util.DevServerHTTPError(500, 'Error: target= is required!')
Scott Zawalski16954532012-03-20 15:31:36 -0400845 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700846 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-07 18:21:26 -0800847 updater.static_dir, kwargs['target'],
848 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700849 except common_util.CommonUtilError as errmsg:
Chris Sosa4b951602014-04-09 20:26:07 -0700850 raise common_util.DevServerHTTPError(500, str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -0400851
852 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800853 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500854 """Return a control file or a list of all known control files.
855
856 Example URL:
857 To List all control files:
beepsbd337242013-07-09 22:44:06 -0700858 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
859 To List all control files for, say, the bvt suite:
860 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500861 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500862 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 -0500863
864 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500865 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500866 control_path: If you want the contents of a control file set this
867 to the path. E.g. client/site_tests/sleeptest/control
868 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -0700869 suite_name: If control_path is not specified but a suite_name is
870 specified, list the control files belonging to that suite instead of
871 all control files. The empty string for suite_name will list all control
872 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -0800873
Scott Zawalski4647ce62012-01-03 17:17:28 -0500874 Returns:
875 Contents of a control file if control_path is provided.
876 A list of control files if no control_path is provided.
877 """
Don Garrettf84631a2014-01-07 18:21:26 -0800878 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500879 return _PrintDocStringAsHTML(self.controlfiles)
880
Don Garrettf84631a2014-01-07 18:21:26 -0800881 if 'build' not in kwargs:
Chris Sosa4b951602014-04-09 20:26:07 -0700882 raise common_util.DevServerHTTPError(500, 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500883
Don Garrettf84631a2014-01-07 18:21:26 -0800884 if 'control_path' not in kwargs:
885 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -0700886 return common_util.GetControlFileListForSuite(
Don Garrettf84631a2014-01-07 18:21:26 -0800887 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -0700888 else:
889 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-07 18:21:26 -0800890 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500891 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700892 return common_util.GetControlFile(
Don Garrettf84631a2014-01-07 18:21:26 -0800893 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800894
895 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -0700896 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700897 """Translates an xBuddy path to a real path to artifact if it exists.
898
899 Args:
Simran Basi99e63c02014-05-20 10:39:52 -0700900 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
901 Local searches the devserver's static directory. Remote searches a
902 Google Storage image archive.
903
904 Kwargs:
905 image_dir: Google Storage image archive to search in if requesting a
906 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700907
908 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -0700909 String in the format of build_id/artifact as stored on the local server
910 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700911 """
Simran Basi99e63c02014-05-20 10:39:52 -0700912 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -0700913 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700914 response = os.path.join(build_id, filename)
915 _Log('Path translation requested, returning: %s', response)
916 return response
917
918 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -0700919 def xbuddy(self, *args, **kwargs):
920 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -0700921
922 Args:
joycheneaf4cfc2013-07-02 08:38:57 -0700923 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -0700924 components of the path. The path can be understood as
925 "{local|remote}/build_id/artifact" where build_id is composed of
926 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -0700927
joychen121fc9b2013-08-02 14:30:30 -0700928 The first path element is optional, and can be "remote" or "local"
929 If local (the default), devserver will not attempt to access Google
930 Storage, and will only search the static directory for the files.
931 If remote, devserver will try to obtain the artifact off GS if it's
932 not found locally.
933 The board is the familiar board name, optionally suffixed.
934 The version can be the google storage version number, and may also be
935 any of a number of xBuddy defined version aliases that will be
936 translated into the latest built image that fits the description.
937 Defaults to latest.
938 The artifact is one of a number of image or artifact aliases used by
939 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -0700940
941 Kwargs:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800942 for_update: {true|false}
943 if true, pregenerates the update payloads for the image,
944 and returns the update uri to pass to the
945 update_engine_client.
joychen3cb228e2013-06-12 12:13:13 -0700946 return_dir: {true|false}
947 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800948 relative_path: {true|false}
949 if set to true, returns the relative path to the payload
950 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -0700951 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -0700952 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -0700953 or
joycheneaf4cfc2013-07-02 08:38:57 -0700954 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -0700955
956 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800957 If |for_update|, returns a redirect to the image or update file
958 on the devserver. E.g.,
959 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
960 chromium-test-image.bin
961 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
962 http://host:port/static/x86-generic-release/R26-4000.0.0/
963 If |relative_path| is true, return a relative path the folder where the
964 payloads are. E.g.,
965 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -0700966 """
Chris Sosa75490802013-09-30 17:21:45 -0700967 boolean_string = kwargs.get('for_update')
968 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800969 boolean_string = kwargs.get('return_dir')
970 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
971 boolean_string = kwargs.get('relative_path')
972 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -0700973
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800974 if return_dir and relative_path:
Chris Sosa4b951602014-04-09 20:26:07 -0700975 raise common_util.DevServerHTTPError(
976 500, 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -0700977
978 # For updates, we optimize downloading of test images.
979 file_name = None
980 build_id = None
981 if for_update:
982 try:
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700983 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-09-30 17:21:45 -0700984 except build_artifact.ArtifactDownloadError:
985 build_id = None
986
987 if not build_id:
988 build_id, file_name = self._xbuddy.Get(args)
989
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800990 if for_update:
991 _Log('Payload generation triggered by request')
992 # Forces payload to be in cache and symlinked into build_id dir.
Chris Sosa75490802013-09-30 17:21:45 -0700993 updater.GetUpdateForLabel(autoupdate.FORCED_UPDATE, build_id,
994 image_name=file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800995
996 response = None
997 if return_dir:
998 response = os.path.join(cherrypy.request.base, 'static', build_id)
999 _Log('Directory requested, returning: %s', response)
1000 elif relative_path:
1001 response = build_id
1002 _Log('Relative path requested, returning: %s', response)
1003 elif for_update:
1004 response = os.path.join(cherrypy.request.base, 'update', build_id)
1005 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001006 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001007 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001008 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001009 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001010 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001011
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001012 return response
1013
joychen3cb228e2013-06-12 12:13:13 -07001014 @cherrypy.expose
1015 def xbuddy_list(self):
1016 """Lists the currently available images & time since last access.
1017
Gilad Arnold452fd272014-02-04 11:09:28 -08001018 Returns:
1019 A string representation of a list of tuples [(build_id, time since last
1020 access),...]
joychen3cb228e2013-06-12 12:13:13 -07001021 """
1022 return self._xbuddy.List()
1023
1024 @cherrypy.expose
1025 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001026 """Returns the number of images cached by xBuddy."""
joychen3cb228e2013-06-12 12:13:13 -07001027 return self._xbuddy.Capacity()
1028
1029 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001030 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001031 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001032 return ('Welcome to the Dev Server!<br>\n'
1033 '<br>\n'
1034 'Here are the available methods, click for documentation:<br>\n'
1035 '<br>\n'
1036 '%s' %
1037 '<br>\n'.join(
1038 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001039 for name in _FindExposedMethods(
1040 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001041
1042 @cherrypy.expose
1043 def doc(self, *args):
1044 """Shows the documentation for available methods / URLs.
1045
1046 Example:
1047 http://myhost/doc/update
1048 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001049 name = '/'.join(args)
1050 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001051 if not method:
1052 raise DevServerError("No exposed method named `%s'" % name)
1053 if not method.__doc__:
1054 raise DevServerError("No documentation for exposed method `%s'" % name)
1055 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001056
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07001057 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001058 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001059 """Handles an update check from a Chrome OS client.
1060
1061 The HTTP request should contain the standard Omaha-style XML blob. The URL
1062 line may contain an additional intermediate path to the update payload.
1063
joychen121fc9b2013-08-02 14:30:30 -07001064 This request can be handled in one of 4 ways, depending on the devsever
1065 settings and intermediate path.
joychenb0dfe552013-07-30 10:02:06 -07001066
joychen121fc9b2013-08-02 14:30:30 -07001067 1. No intermediate path
1068 If no intermediate path is given, the default behavior is to generate an
1069 update payload from the latest test image locally built for the board
1070 specified in the xml. Devserver serves the generated payload.
1071
1072 2. Path explicitly invokes XBuddy
1073 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1074 with 'xbuddy'. This path is then used to acquire an image binary for the
1075 devserver to generate an update payload from. Devserver then serves this
1076 payload.
1077
1078 3. Path is left for the devserver to interpret.
1079 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1080 to generate a payload from the test image in that directory and serve it.
1081
1082 4. The devserver is in a 'forced' mode. TO BE DEPRECATED
1083 This comes from the usage of --forced_payload or --image when starting the
1084 devserver. No matter what path (or no path) gets passed in, devserver will
1085 serve the update payload (--forced_payload) or generate an update payload
1086 from the image (--image).
1087
1088 Examples:
1089 1. No intermediate path
1090 update_engine_client --omaha_url=http://myhost/update
1091 This generates an update payload from the latest test image locally built
1092 for the board specified in the xml.
1093
1094 2. Explicitly invoke xbuddy
1095 update_engine_client --omaha_url=
1096 http://myhost/update/xbuddy/remote/board/version/dev
1097 This would go to GS to download the dev image for the board, from which
1098 the devserver would generate a payload to serve.
1099
1100 3. Give a path for devserver to interpret
1101 update_engine_client --omaha_url=http://myhost/update/some/random/path
1102 This would attempt, in order to:
1103 a) Generate an update from a test image binary if found in
1104 static_dir/some/random/path.
1105 b) Serve an update payload found in static_dir/some/random/path.
1106 c) Hope that some/random/path takes the form "board/version" and
1107 and attempt to download an update payload for that board/version
1108 from GS.
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001109 """
joychen121fc9b2013-08-02 14:30:30 -07001110 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -08001111 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -07001112 data = cherrypy.request.rfile.read(body_length)
Chris Sosa7c931362010-10-11 19:49:01 -07001113
joychen121fc9b2013-08-02 14:30:30 -07001114 return updater.HandleUpdatePing(data, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001115
Dan Shiafd0e492015-05-27 14:23:51 -07001116 @require_psutil()
1117 def _get_io_stats(self):
1118 """Get the IO stats as a dictionary.
1119
Gabe Black3b567202015-09-23 14:07:59 -07001120 Returns:
1121 A dictionary of IO stats collected by psutil.
Dan Shiafd0e492015-05-27 14:23:51 -07001122
1123 """
1124 return {'disk_read_bytes_per_second': self.disk_read_bytes_per_sec,
1125 'disk_write_bytes_per_second': self.disk_write_bytes_per_sec,
1126 'disk_total_bytes_per_second': (self.disk_read_bytes_per_sec +
1127 self.disk_write_bytes_per_sec),
1128 'network_sent_bytes_per_second': self.network_sent_bytes_per_sec,
1129 'network_recv_bytes_per_second': self.network_recv_bytes_per_sec,
1130 'network_total_bytes_per_second': (self.network_sent_bytes_per_sec +
1131 self.network_recv_bytes_per_sec),
1132 'cpu_percent': psutil.cpu_percent(),}
1133
Dan Shif5ce2de2013-04-25 16:06:32 -07001134 @cherrypy.expose
1135 def check_health(self):
1136 """Collect the health status of devserver to see if it's ready for staging.
1137
Gilad Arnold452fd272014-02-04 11:09:28 -08001138 Returns:
1139 A JSON dictionary containing all or some of the following fields:
1140 free_disk (int): free disk space in GB
1141 staging_thread_count (int): number of devserver threads currently staging
1142 an image
Dan Shif5ce2de2013-04-25 16:06:32 -07001143 """
1144 # Get free disk space.
1145 stat = os.statvfs(updater.static_dir)
1146 free_disk = stat.f_bsize * stat.f_bavail / 1000000000
1147
Dan Shiafd0e492015-05-27 14:23:51 -07001148 health_data = {
Dan Shif5ce2de2013-04-25 16:06:32 -07001149 'free_disk': free_disk,
Dan Shiafd0e492015-05-27 14:23:51 -07001150 'staging_thread_count': DevServerRoot._staging_thread_count}
1151 health_data.update(self._get_io_stats() or {})
1152
1153 return json.dumps(health_data)
Dan Shif5ce2de2013-04-25 16:06:32 -07001154
1155
Chris Sosadbc20082012-12-10 13:39:11 -08001156def _CleanCache(cache_dir, wipe):
1157 """Wipes any excess cached items in the cache_dir.
1158
1159 Args:
1160 cache_dir: the directory we are wiping from.
1161 wipe: If True, wipe all the contents -- not just the excess.
1162 """
1163 if wipe:
1164 # Clear the cache and exit on error.
1165 cmd = 'rm -rf %s/*' % cache_dir
1166 if os.system(cmd) != 0:
1167 _Log('Failed to clear the cache with %s' % cmd)
1168 sys.exit(1)
1169 else:
1170 # Clear all but the last N cached updates
1171 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1172 (cache_dir, CACHED_ENTRIES))
1173 if os.system(cmd) != 0:
1174 _Log('Failed to clean up old delta cache files with %s' % cmd)
1175 sys.exit(1)
1176
1177
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001178def _AddTestingOptions(parser):
1179 group = optparse.OptionGroup(
1180 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1181 'developers writing integration tests utilizing the devserver. They are '
1182 'not intended to be really used outside the scope of someone '
1183 'knowledgable about the test.')
1184 group.add_option('--exit',
1185 action='store_true',
1186 help='do not start the server (yet pregenerate/clear cache)')
1187 group.add_option('--host_log',
1188 action='store_true', default=False,
1189 help='record history of host update events (/api/hostlog)')
1190 group.add_option('--max_updates',
Gabe Black3b567202015-09-23 14:07:59 -07001191 metavar='NUM', default=-1, type='int',
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001192 help='maximum number of update checks handled positively '
1193 '(default: unlimited)')
1194 group.add_option('--private_key',
1195 metavar='PATH', default=None,
1196 help='path to the private key in pem format. If this is set '
1197 'the devserver will generate update payloads that are '
1198 'signed with this key.')
David Zeuthen52ccd012013-10-31 12:58:26 -07001199 group.add_option('--private_key_for_metadata_hash_signature',
1200 metavar='PATH', default=None,
1201 help='path to the private key in pem format. If this is set '
1202 'the devserver will sign the metadata hash with the given '
1203 'key and transmit in the Omaha-style XML response.')
1204 group.add_option('--public_key',
1205 metavar='PATH', default=None,
1206 help='path to the public key in pem format. If this is set '
1207 'the devserver will transmit a base64 encoded version of '
1208 'the content in the Omaha-style XML response.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001209 group.add_option('--proxy_port',
1210 metavar='PORT', default=None, type='int',
1211 help='port to have the client connect to -- basically the '
1212 'devserver lies to the update to tell it to get the payload '
1213 'from a different port that will proxy the request back to '
1214 'the devserver. The proxy must be managed outside the '
1215 'devserver.')
1216 group.add_option('--remote_payload',
1217 action='store_true', default=False,
Chris Sosa4b951602014-04-09 20:26:07 -07001218 help='Payload is being served from a remote machine. With '
1219 'this setting enabled, this devserver instance serves as '
1220 'just an Omaha server instance. In this mode, the '
1221 'devserver enforces a few extra components of the Omaha '
Chris Sosafc715442014-04-09 20:45:23 -07001222 'protocol, such as hardware class, being sent.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001223 group.add_option('-u', '--urlbase',
1224 metavar='URL',
Gabe Black3b567202015-09-23 14:07:59 -07001225 help='base URL for update images, other than the '
1226 'devserver. Use in conjunction with remote_payload.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001227 parser.add_option_group(group)
1228
1229
1230def _AddUpdateOptions(parser):
1231 group = optparse.OptionGroup(
1232 parser, 'Autoupdate Options', 'These options can be used to change '
1233 'how the devserver either generates or serve update payloads. Please '
1234 'note that all of these option affect how a payload is generated and so '
1235 'do not work in archive-only mode.')
1236 group.add_option('--board',
1237 help='By default the devserver will create an update '
1238 'payload from the latest image built for the board '
1239 'a device that is requesting an update has. When we '
1240 'pre-generate an update (see below) and we do not specify '
1241 'another update_type option like image or payload, the '
1242 'devserver needs to know the board to generate the latest '
1243 'image for. This is that board.')
1244 group.add_option('--critical_update',
1245 action='store_true', default=False,
1246 help='Present update payload as critical')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001247 group.add_option('--image',
1248 metavar='FILE',
1249 help='Generate and serve an update using this image to any '
1250 'device that requests an update.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001251 group.add_option('--payload',
1252 metavar='PATH',
1253 help='use the update payload from specified directory '
1254 '(update.gz).')
1255 group.add_option('-p', '--pregenerate_update',
1256 action='store_true', default=False,
1257 help='pre-generate the update payload before accepting '
1258 'update requests. Useful to help debug payload generation '
1259 'issues quickly. Also if an update payload will take a '
1260 'long time to generate, a client may timeout if you do not'
1261 'pregenerate the update.')
1262 group.add_option('--src_image',
1263 metavar='PATH', default='',
1264 help='If specified, delta updates will be generated using '
1265 'this image as the source image. Delta updates are when '
1266 'you are updating from a "source image" to a another '
1267 'image.')
1268 parser.add_option_group(group)
1269
1270
1271def _AddProductionOptions(parser):
1272 group = optparse.OptionGroup(
1273 parser, 'Advanced Server Options', 'These options can be used to changed '
1274 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001275 group.add_option('--clear_cache',
1276 action='store_true', default=False,
1277 help='At startup, removes all cached entries from the'
1278 'devserver\'s cache.')
1279 group.add_option('--logfile',
1280 metavar='PATH',
1281 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001282 group.add_option('--pidfile',
1283 metavar='PATH',
1284 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001285 group.add_option('--portfile',
1286 metavar='PATH',
1287 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001288 group.add_option('--production',
1289 action='store_true', default=False,
1290 help='have the devserver use production values when '
1291 'starting up. This includes using more threads and '
1292 'performing less logging.')
1293 parser.add_option_group(group)
1294
1295
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001296def _MakeLogHandler(logfile):
1297 """Create a LogHandler instance used to log all messages."""
1298 hdlr_cls = handlers.TimedRotatingFileHandler
1299 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
1300 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001301 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001302 return hdlr
1303
1304
Chris Sosacde6bf42012-05-31 18:36:39 -07001305def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001306 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001307 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001308
1309 # get directory that the devserver is run from
1310 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001311 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001312 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001313 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001314 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001315 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001316 parser.add_option('--port',
1317 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001318 help=('port for the dev server to use; if zero, binds to '
1319 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001320 parser.add_option('-t', '--test_image',
1321 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001322 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001323 parser.add_option('-x', '--xbuddy_manage_builds',
1324 action='store_true',
1325 default=False,
1326 help='If set, allow xbuddy to manage images in'
1327 'build/images.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001328 _AddProductionOptions(parser)
1329 _AddUpdateOptions(parser)
1330 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001331 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001332
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001333 # Handle options that must be set globally in cherrypy. Do this
1334 # work up front, because calls to _Log() below depend on this
1335 # initialization.
1336 if options.production:
1337 cherrypy.config.update({'environment': 'production'})
1338 if not options.logfile:
1339 cherrypy.config.update({'log.screen': True})
1340 else:
1341 cherrypy.config.update({'log.error_file': '',
1342 'log.access_file': ''})
1343 hdlr = _MakeLogHandler(options.logfile)
1344 # Pylint can't seem to process these two calls properly
1345 # pylint: disable=E1101
1346 cherrypy.log.access_log.addHandler(hdlr)
1347 cherrypy.log.error_log.addHandler(hdlr)
1348 # pylint: enable=E1101
1349
joychened64b222013-06-21 16:39:34 -07001350 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001351 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001352
joychened64b222013-06-21 16:39:34 -07001353 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001354 # If our devserver is only supposed to serve payloads, we shouldn't be
1355 # mucking with the cache at all. If the devserver hadn't previously
1356 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001357 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001358 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001359 else:
1360 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001361
Chris Sosadbc20082012-12-10 13:39:11 -08001362 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001363 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001364
joychen121fc9b2013-08-02 14:30:30 -07001365 _xbuddy = xbuddy.XBuddy(options.xbuddy_manage_builds,
1366 options.board,
joychen121fc9b2013-08-02 14:30:30 -07001367 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001368 if options.clear_cache and options.xbuddy_manage_builds:
1369 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001370
Chris Sosa6a3697f2013-01-29 16:44:43 -08001371 # We allow global use here to share with cherrypy classes.
1372 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001373 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001374 updater = autoupdate.Autoupdate(
joychen121fc9b2013-08-02 14:30:30 -07001375 _xbuddy,
joychened64b222013-06-21 16:39:34 -07001376 static_dir=options.static_dir,
Andrew de los Reyes52620802010-04-12 13:40:07 -07001377 urlbase=options.urlbase,
Chris Sosa5d342a22010-09-28 16:54:41 -07001378 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001379 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001380 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -07001381 src_image=options.src_image,
Chris Sosa08d55a22011-01-19 16:08:02 -08001382 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001383 copy_to_static_root=not options.exit,
1384 private_key=options.private_key,
Gabe Black3b567202015-09-23 14:07:59 -07001385 private_key_for_metadata_hash_signature=(
1386 options.private_key_for_metadata_hash_signature),
David Zeuthen52ccd012013-10-31 12:58:26 -07001387 public_key=options.public_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001388 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001389 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001390 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001391 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001392 )
Chris Sosa7c931362010-10-11 19:49:01 -07001393
Chris Sosa6a3697f2013-01-29 16:44:43 -08001394 if options.pregenerate_update:
1395 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -07001396
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001397 if options.exit:
1398 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001399
joychen3cb228e2013-06-12 12:13:13 -07001400 dev_server = DevServerRoot(_xbuddy)
1401
Gilad Arnold11fbef42014-02-10 11:04:13 -08001402 # Patch CherryPy to support binding to any available port (--port=0).
1403 cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy)
1404
Chris Sosa855b8932013-08-21 13:24:55 -07001405 if options.pidfile:
1406 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1407
Gilad Arnold11fbef42014-02-10 11:04:13 -08001408 if options.portfile:
1409 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1410
joychen3cb228e2013-06-12 12:13:13 -07001411 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001412
1413
1414if __name__ == '__main__':
1415 main()