blob: b4bb0635f28dea538830451c1d0cd0ef5bf63336 [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
Dan Shiafd0e492015-05-27 14:23:51 -070053import time
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070054import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070055from logging import handlers
56
57import cherrypy
Chris Sosa855b8932013-08-21 13:24:55 -070058from cherrypy import _cplogging as cplogging
59from cherrypy.process import plugins
rtc@google.comded22402009-10-26 22:36:21 +000060
Chris Sosa0356d3b2010-09-16 15:46:22 -070061import autoupdate
Chris Sosa75490802013-09-30 17:21:45 -070062import build_artifact
Gilad Arnold11fbef42014-02-10 11:04:13 -080063import cherrypy_ext
Gilad Arnoldc65330c2012-09-20 15:17:48 -070064import common_util
Simran Basief83d6a2014-08-28 14:32:01 -070065import devserver_constants
Chris Sosa47a7d4e2012-03-28 11:26:55 -070066import downloader
Chris Sosa7cd23202013-10-15 17:22:57 -070067import gsutil_util
Gilad Arnoldc65330c2012-09-20 15:17:48 -070068import log_util
joychen3cb228e2013-06-12 12:13:13 -070069import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070070
Gilad Arnoldc65330c2012-09-20 15:17:48 -070071# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080072def _Log(message, *args):
73 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 15:46:22 -070074
Dan Shiafd0e492015-05-27 14:23:51 -070075try:
76 import psutil
77except ImportError:
78 # Ignore psutil import failure. This is for backwards compatibility, so
79 # "cros flash" can still update duts with build without psutil installed.
80 # The reason is that, during cros flash, local devserver code is copied over
81 # to DUT, and devserver will be running inside DUT to stage the build.
82 _Log('Python module psutil is not installed, devserver load data will not be '
83 'collected')
84 psutil = None
Dan Shi94dcbe82015-06-08 20:51:13 -070085except OSError as e:
86 # Ignore error like following. psutil may not work properly in builder. Ignore
87 # the error as load information of devserver is not used in builder.
88 # OSError: [Errno 2] No such file or directory: '/dev/pts/0'
89 _Log('psutil is failed to be imported, error: %s. devserver load data will '
90 'not be collected.', e)
91 psutil = None
92
Frank Farzan40160872011-12-12 18:39:18 -080093
Chris Sosa417e55d2011-01-25 16:40:48 -080094CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080095
Simran Basi4baad082013-02-14 13:39:18 -080096TELEMETRY_FOLDER = 'telemetry_src'
97TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
98 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070099 'dep-chrome_test.tar.bz2',
100 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -0800101
Chris Sosa0356d3b2010-09-16 15:46:22 -0700102# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +0000103updater = None
rtc@google.comded22402009-10-26 22:36:21 +0000104
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700105# Log rotation parameters. These settings correspond to once a week
J. Richard Barnette6dfa5342013-06-04 11:48:56 -0700106# at midnight between Friday and Saturday, with about three months
107# of old logs kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700108#
109# For more, see the documentation for
110# logging.handlers.TimedRotatingFileHandler
J. Richard Barnette6dfa5342013-06-04 11:48:56 -0700111_LOG_ROTATION_TIME = 'W4'
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700112_LOG_ROTATION_BACKUP = 13
113
Dan Shiafd0e492015-05-27 14:23:51 -0700114# Number of seconds between the collection of disk and network IO counters.
115STATS_INTERVAL = 10.0
Frank Farzan40160872011-12-12 18:39:18 -0800116
Chris Sosa9164ca32012-03-28 11:04:50 -0700117class DevServerError(Exception):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700118 """Exception class used by this module."""
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700119
120
Dan Shiafd0e492015-05-27 14:23:51 -0700121def require_psutil():
122 """Decorator for functions require psutil to run.
123 """
124 def deco_require_psutil(func):
125 """Wrapper of the decorator function.
126
127 @param func: function to be called.
128 """
129 def func_require_psutil(*args, **kwargs):
130 """Decorator for functions require psutil to run.
131
132 If psutil is not installed, skip calling the function.
133
134 @param args: arguments for function to be called.
135 @param kwargs: keyword arguments for function to be called.
136 """
137 if psutil:
138 return func(*args, **kwargs)
139 else:
140 _Log('Python module psutil is not installed. Function call %s is '
141 'skipped.' % func)
142 return func_require_psutil
143 return deco_require_psutil
144
145
Scott Zawalski4647ce62012-01-03 17:17:28 -0500146def _LeadingWhiteSpaceCount(string):
147 """Count the amount of leading whitespace in a string.
148
149 Args:
150 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800151
Scott Zawalski4647ce62012-01-03 17:17:28 -0500152 Returns:
153 number of white space chars before characters start.
154 """
155 matched = re.match('^\s+', string)
156 if matched:
157 return len(matched.group())
158
159 return 0
160
161
162def _PrintDocStringAsHTML(func):
163 """Make a functions docstring somewhat HTML style.
164
165 Args:
166 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800167
Scott Zawalski4647ce62012-01-03 17:17:28 -0500168 Returns:
169 A string that is somewhat formated for a web browser.
170 """
171 # TODO(scottz): Make this parse Args/Returns in a prettier way.
172 # Arguments could be bolded and indented etc.
173 html_doc = []
174 for line in func.__doc__.splitlines():
175 leading_space = _LeadingWhiteSpaceCount(line)
176 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700177 line = '&nbsp;' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500178
179 html_doc.append('<BR>%s' % line)
180
181 return '\n'.join(html_doc)
182
183
Simran Basief83d6a2014-08-28 14:32:01 -0700184def _GetUpdateTimestampHandler(static_dir):
185 """Returns a handler to update directory staged.timestamp.
186
187 This handler resets the stage.timestamp whenever static content is accessed.
188
189 Args:
190 static_dir: Directory from which static content is being staged.
191
192 Returns:
193 A cherrypy handler to update the timestamp of accessed content.
194 """
195 def UpdateTimestampHandler():
196 if not '404' in cherrypy.response.status:
197 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
198 cherrypy.request.path_info)
199 if build_match:
200 build_dir = os.path.join(static_dir, build_match.group('build'))
201 downloader.Downloader.TouchTimestampForStaged(build_dir)
202 return UpdateTimestampHandler
203
204
Chris Sosa7c931362010-10-11 19:49:01 -0700205def _GetConfig(options):
206 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800207
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800208 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800209 # Fall back to IPv4 when python is not configured with IPv6.
210 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800211 socket_host = '0.0.0.0'
212
Simran Basief83d6a2014-08-28 14:32:01 -0700213 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
214 # on the on_end_resource hook. This hook is called once processing is
215 # complete and the response is ready to be returned.
216 cherrypy.tools.update_timestamp = cherrypy.Tool(
217 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
218
Chris Sosa7c931362010-10-11 19:49:01 -0700219 base_config = { 'global':
220 { 'server.log_request_headers': True,
221 'server.protocol_version': 'HTTP/1.1',
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800222 'server.socket_host': socket_host,
Chris Sosa7c931362010-10-11 19:49:01 -0700223 'server.socket_port': int(options.port),
Chris Sosa374c62d2010-10-14 09:13:54 -0700224 'response.timeout': 6000,
Chris Sosa6fe23942012-07-02 15:44:46 -0700225 'request.show_tracebacks': True,
Chris Sosa72333d12012-06-13 11:28:05 -0700226 'server.socket_timeout': 60,
joychenecc02aa2013-07-17 18:27:35 -0700227 'server.thread_pool': 2,
Yu-Ju Hongaccb2e52014-05-01 11:24:22 -0700228 'engine.autoreload.on': False,
Chris Sosa7c931362010-10-11 19:49:01 -0700229 },
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700230 '/api':
231 {
232 # Gets rid of cherrypy parsing post file for args.
233 'request.process_request_body': False,
234 },
Chris Sosaa1ef0102010-10-21 16:22:35 -0700235 '/build':
236 {
237 'response.timeout': 100000,
238 },
Chris Sosa7c931362010-10-11 19:49:01 -0700239 '/update':
240 {
241 # Gets rid of cherrypy parsing post file for args.
242 'request.process_request_body': False,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700243 'response.timeout': 10000,
Chris Sosa7c931362010-10-11 19:49:01 -0700244 },
245 # Sets up the static dir for file hosting.
246 '/static':
joychened64b222013-06-21 16:39:34 -0700247 { 'tools.staticdir.dir': options.static_dir,
Chris Sosa7c931362010-10-11 19:49:01 -0700248 'tools.staticdir.on': True,
Chris Sosaf65f4b92010-10-21 15:57:51 -0700249 'response.timeout': 10000,
Simran Basief83d6a2014-08-28 14:32:01 -0700250 'tools.update_timestamp.on': True,
Chris Sosa7c931362010-10-11 19:49:01 -0700251 },
252 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700253 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700254 base_config['global'].update({'server.thread_pool': 150})
Chris Sosa7cd23202013-10-15 17:22:57 -0700255 # TODO(sosa): Do this more cleanly.
256 gsutil_util.GSUTIL_ATTEMPTS = 5
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500257
Chris Sosa7c931362010-10-11 19:49:01 -0700258 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000259
Darin Petkove17164a2010-08-11 13:24:41 -0700260
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700261def _GetRecursiveMemberObject(root, member_list):
262 """Returns an object corresponding to a nested member list.
263
264 Args:
265 root: the root object to search
266 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800267
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700268 Returns:
269 An object corresponding to the member name list; None otherwise.
270 """
271 for member in member_list:
272 next_root = root.__class__.__dict__.get(member)
273 if not next_root:
274 return None
275 root = next_root
276 return root
277
278
279def _IsExposed(name):
280 """Returns True iff |name| has an `exposed' attribute and it is set."""
281 return hasattr(name, 'exposed') and name.exposed
282
283
Gilad Arnold748c8322012-10-12 09:51:35 -0700284def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700285 """Returns a CherryPy-exposed method, if such exists.
286
287 Args:
288 root: the root object for searching
289 nested_member: a slash-joined path to the nested member
290 ignored: method paths to be ignored
Don Garrettf84631a2014-01-07 18:21:26 -0800291
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700292 Returns:
293 A function object corresponding to the path defined by |member_list| from
294 the |root| object, if the function is exposed and not ignored; None
295 otherwise.
296 """
Gilad Arnold748c8322012-10-12 09:51:35 -0700297 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700298 _GetRecursiveMemberObject(root, nested_member.split('/')))
299 if (method and type(method) == types.FunctionType and _IsExposed(method)):
300 return method
301
302
Gilad Arnold748c8322012-10-12 09:51:35 -0700303def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700304 """Finds exposed CherryPy methods.
305
306 Args:
307 root: the root object for searching
308 prefix: slash-joined chain of members leading to current object
309 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800310
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700311 Returns:
312 List of exposed URLs that are not unlisted.
313 """
314 method_list = []
315 for member in sorted(root.__class__.__dict__.keys()):
316 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700317 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700318 continue
319 member_obj = root.__class__.__dict__[member]
320 if _IsExposed(member_obj):
321 if type(member_obj) == types.FunctionType:
322 method_list.append(prefixed_member)
323 else:
324 method_list += _FindExposedMethods(
325 member_obj, prefixed_member, unlisted)
326 return method_list
327
328
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700329class ApiRoot(object):
330 """RESTful API for Dev Server information."""
331 exposed = True
332
333 @cherrypy.expose
334 def hostinfo(self, ip):
335 """Returns a JSON dictionary containing information about the given ip.
336
Gilad Arnold1b908392012-10-05 11:36:27 -0700337 Args:
338 ip: address of host whose info is requested
Don Garrettf84631a2014-01-07 18:21:26 -0800339
Gilad Arnold1b908392012-10-05 11:36:27 -0700340 Returns:
341 A JSON dictionary containing all or some of the following fields:
342 last_event_type (int): last update event type received
343 last_event_status (int): last update event status received
344 last_known_version (string): last known version reported in update ping
345 forced_update_label (string): update label to force next update ping to
346 use, set by setnextupdate
347 See the OmahaEvent class in update_engine/omaha_request_action.h for
348 event type and status code definitions. If the ip does not exist an empty
349 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700350
Gilad Arnold1b908392012-10-05 11:36:27 -0700351 Example URL:
352 http://myhost/api/hostinfo?ip=192.168.1.5
353 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700354 return updater.HandleHostInfoPing(ip)
355
356 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800357 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700358 """Returns a JSON object containing a log of host event.
359
360 Args:
361 ip: address of host whose event log is requested, or `all'
Don Garrettf84631a2014-01-07 18:21:26 -0800362
Gilad Arnold1b908392012-10-05 11:36:27 -0700363 Returns:
364 A JSON encoded list (log) of dictionaries (events), each of which
365 containing a `timestamp' and other event fields, as described under
366 /api/hostinfo.
367
368 Example URL:
369 http://myhost/api/hostlog?ip=192.168.1.5
370 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800371 return updater.HandleHostLogPing(ip)
372
373 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700374 def setnextupdate(self, ip):
375 """Allows the response to the next update ping from a host to be set.
376
377 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 11:36:27 -0700378 /update command.
379 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700380 body_length = int(cherrypy.request.headers['Content-Length'])
381 label = cherrypy.request.rfile.read(body_length)
382
383 if label:
384 label = label.strip()
385 if label:
386 return updater.HandleSetUpdatePing(ip, label)
Chris Sosa4b951602014-04-09 20:26:07 -0700387 raise common_util.DevServerHTTPError(400, 'No label provided.')
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700388
389
Gilad Arnold55a2a372012-10-02 09:46:32 -0700390 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800391 def fileinfo(self, *args):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700392 """Returns information about a given staged file.
393
394 Args:
Don Garrettf84631a2014-01-07 18:21:26 -0800395 args: path to the file inside the server's static staging directory
396
Gilad Arnold55a2a372012-10-02 09:46:32 -0700397 Returns:
398 A JSON encoded dictionary with information about the said file, which may
399 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700400 size (int): the file size in bytes
401 sha1 (string): a base64 encoded SHA1 hash
402 sha256 (string): a base64 encoded SHA256 hash
403
404 Example URL:
405 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700406 """
Don Garrettf84631a2014-01-07 18:21:26 -0800407 file_path = os.path.join(updater.static_dir, *args)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700408 if not os.path.exists(file_path):
409 raise DevServerError('file not found: %s' % file_path)
410 try:
411 file_size = os.path.getsize(file_path)
412 file_sha1 = common_util.GetFileSha1(file_path)
413 file_sha256 = common_util.GetFileSha256(file_path)
414 except os.error, e:
415 raise DevServerError('failed to get info for file %s: %s' %
Gilad Arnolde74b3812013-04-22 11:27:38 -0700416 (file_path, e))
417
418 is_delta = autoupdate.Autoupdate.IsDeltaFormatFile(file_path)
419
420 return json.dumps({
421 autoupdate.Autoupdate.SIZE_ATTR: file_size,
422 autoupdate.Autoupdate.SHA1_ATTR: file_sha1,
423 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
424 autoupdate.Autoupdate.ISDELTA_ATTR: is_delta
425 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700426
Chris Sosa76e44b92013-01-31 12:11:38 -0800427
David Rochberg7c79a812011-01-19 14:24:45 -0500428class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700429 """The Root Class for the Dev Server.
430
431 CherryPy works as follows:
432 For each method in this class, cherrpy interprets root/path
433 as a call to an instance of DevServerRoot->method_name. For example,
434 a call to http://myhost/build will call build. CherryPy automatically
435 parses http args and places them as keyword arguments in each method.
436 For paths http://myhost/update/dir1/dir2, you can use *args so that
437 cherrypy uses the update method and puts the extra paths in args.
438 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700439 # Method names that should not be listed on the index page.
440 _UNLISTED_METHODS = ['index', 'doc']
441
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700442 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700443
Dan Shi59ae7092013-06-04 14:37:27 -0700444 # Number of threads that devserver is staging images.
445 _staging_thread_count = 0
446 # Lock used to lock increasing/decreasing count.
447 _staging_thread_count_lock = threading.Lock()
448
Dan Shiafd0e492015-05-27 14:23:51 -0700449 @require_psutil()
450 def _refresh_io_stats(self):
451 """A call running in a thread to update IO stats periodically."""
452 prev_disk_io_counters = psutil.disk_io_counters()
453 prev_network_io_counters = psutil.net_io_counters()
454 prev_read_time = time.time()
455 while True:
456 time.sleep(STATS_INTERVAL)
457 now = time.time()
458 interval = now - prev_read_time
459 prev_read_time = now
460 # Disk IO is for all disks.
461 disk_io_counters = psutil.disk_io_counters()
462 network_io_counters = psutil.net_io_counters()
463
464 self.disk_read_bytes_per_sec = (
465 disk_io_counters.read_bytes -
466 prev_disk_io_counters.read_bytes)/interval
467 self.disk_write_bytes_per_sec = (
468 disk_io_counters.write_bytes -
469 prev_disk_io_counters.write_bytes)/interval
470 prev_disk_io_counters = disk_io_counters
471
472 self.network_sent_bytes_per_sec = (
473 network_io_counters.bytes_sent -
474 prev_network_io_counters.bytes_sent)/interval
475 self.network_recv_bytes_per_sec = (
476 network_io_counters.bytes_recv -
477 prev_network_io_counters.bytes_recv)/interval
478 prev_network_io_counters = network_io_counters
479
480 @require_psutil()
481 def _start_io_stat_thread(self):
482 """Start the thread to collect IO stats.
483 """
484 thread = threading.Thread(target=self._refresh_io_stats)
485 thread.daemon = True
486 thread.start()
487
joychen3cb228e2013-06-12 12:13:13 -0700488 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700489 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800490 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700491 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500492
Dan Shiafd0e492015-05-27 14:23:51 -0700493 # Cache of disk IO stats, a thread refresh the stats every 10 seconds.
494 # lock is not used for these variables as the only thread writes to these
495 # variables is _refresh_io_stats.
496 self.disk_read_bytes_per_sec = 0
497 self.disk_write_bytes_per_sec = 0
498 # Cache of network IO stats.
499 self.network_sent_bytes_per_sec = 0
500 self.network_recv_bytes_per_sec = 0
501 self._start_io_stat_thread()
502
Chris Sosa6b0c6172013-08-05 17:01:33 -0700503 @staticmethod
504 def _get_artifacts(kwargs):
505 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
506
Don Garrettf84631a2014-01-07 18:21:26 -0800507 Raises:
508 DevserverError if no artifacts would be returned.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700509 """
510 artifacts = kwargs.get('artifacts')
511 files = kwargs.get('files')
512 if not artifacts and not files:
513 raise DevServerError('No artifacts specified.')
514
Chris Sosafa86b482013-09-04 11:30:36 -0700515 # Note we NEED to coerce files to a string as we get raw unicode from
516 # cherrypy and we treat files as strings elsewhere in the code.
517 return (str(artifacts).split(',') if artifacts else [],
518 str(files).split(',') if files else [])
Chris Sosa6b0c6172013-08-05 17:01:33 -0700519
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700520 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500521 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700522 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700523 import builder
524 if self._builder is None:
525 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500526 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700527
Chris Sosacde6bf42012-05-31 18:36:39 -0700528 @staticmethod
529 def _canonicalize_archive_url(archive_url):
530 """Canonicalizes archive_url strings.
531
532 Raises:
533 DevserverError: if archive_url is not set.
534 """
535 if archive_url:
Chris Sosa76e44b92013-01-31 12:11:38 -0800536 if not archive_url.startswith('gs://'):
Don Garrett8ccab732013-08-30 09:13:59 -0700537 raise DevServerError("Archive URL isn't from Google Storage (%s) ." %
538 archive_url)
Chris Sosa76e44b92013-01-31 12:11:38 -0800539
Chris Sosacde6bf42012-05-31 18:36:39 -0700540 return archive_url.rstrip('/')
541 else:
542 raise DevServerError("Must specify an archive_url in the request")
543
Simran Basi4243a862014-12-12 12:48:33 -0800544 @staticmethod
545 def _canonicalize_local_path(local_path):
546 """Canonicalizes |local_path| strings.
547
548 Raises:
549 DevserverError: if |local_path| is not set.
550 """
551 # Restrict staging of local content to only files within the static
552 # directory.
553 local_path = os.path.abspath(local_path)
554 if not local_path.startswith(updater.static_dir):
555 raise DevServerError('Local path %s must be a subdirectory of the static'
556 ' directory: %s' % (local_path, updater.static_dir))
557
558 return local_path.rstrip('/')
559
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700560 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700561 def is_staged(self, **kwargs):
562 """Check if artifacts have been downloaded.
563
Chris Sosa6b0c6172013-08-05 17:01:33 -0700564 async: True to return without waiting for download to complete.
565 artifacts: Comma separated list of named artifacts to download.
566 These are defined in artifact_info and have their implementation
567 in build_artifact.py.
568 files: Comma separated list of file artifacts to stage. These
569 will be available as is in the corresponding static directory with no
570 custom post-processing.
571
572 returns: True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700573
574 Example:
575 To check if autotest and test_suites are staged:
576 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
577 artifacts=autotest,test_suites
578 """
579 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
Chris Sosa6b0c6172013-08-05 17:01:33 -0700580 artifacts, files = self._get_artifacts(kwargs)
Dan Shif8eb0d12013-08-01 17:52:06 -0700581 return str(downloader.Downloader(updater.static_dir, archive_url).IsStaged(
Chris Sosa6b0c6172013-08-05 17:01:33 -0700582 artifacts, files))
Dan Shi59ae7092013-06-04 14:37:27 -0700583
Chris Sosa76e44b92013-01-31 12:11:38 -0800584 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800585 def list_image_dir(self, **kwargs):
586 """Take an archive url and list the contents in its staged directory.
587
588 Args:
589 kwargs:
590 archive_url: Google Storage URL for the build.
591
592 Example:
593 To list the contents of where this devserver should have staged
594 gs://image-archive/<board>-release/<build> call:
595 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
596
597 Returns:
598 A string with information about the contents of the image directory.
599 """
600 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
601 download_helper = downloader.Downloader(updater.static_dir, archive_url)
602 try:
603 image_dir_contents = download_helper.ListBuildDir()
604 except build_artifact.ArtifactDownloadError as e:
605 return 'Cannot list the contents of staged artifacts. %s' % e
606 if not image_dir_contents:
607 return '%s has not been staged on this devserver.' % archive_url
608 return image_dir_contents
609
610 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800611 def stage(self, **kwargs):
612 """Downloads and caches the artifacts from Google Storage URL.
613
614 Downloads and caches the artifacts Google Storage URL. Returns once these
615 have been downloaded on the devserver. A call to this will attempt to cache
616 non-specified artifacts in the background for the given from the given URL
617 following the principle of spatial locality. Spatial locality of different
618 artifacts is explicitly defined in the build_artifact module.
619
620 These artifacts will then be available from the static/ sub-directory of
621 the devserver.
622
623 Args:
624 archive_url: Google Storage URL for the build.
Simran Basi4243a862014-12-12 12:48:33 -0800625 local_path: Local path for the build.
Dan Shif8eb0d12013-08-01 17:52:06 -0700626 async: True to return without waiting for download to complete.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700627 artifacts: Comma separated list of named artifacts to download.
628 These are defined in artifact_info and have their implementation
629 in build_artifact.py.
630 files: Comma separated list of files to stage. These
631 will be available as is in the corresponding static directory with no
632 custom post-processing.
Chris Sosa76e44b92013-01-31 12:11:38 -0800633
634 Example:
635 To download the autotest and test suites tarballs:
636 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
637 artifacts=autotest,test_suites
638 To download the full update payload:
639 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
640 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700641 To download just a file called blah.bin:
642 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
643 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800644
645 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700646 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800647
648 Note for this example, relative path is the archive_url stripped of its
649 basename i.e. path/ in the examples above. Specific example:
650
651 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
652
653 Will get staged to:
654
joychened64b222013-06-21 16:39:34 -0700655 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Chris Sosa76e44b92013-01-31 12:11:38 -0800656 """
Simran Basi4243a862014-12-12 12:48:33 -0800657 archive_url = kwargs.get('archive_url')
658 local_path = kwargs.get('local_path')
659 if not archive_url and not local_path:
660 raise DevServerError('Requires archive_url or local_path to be '
661 'specified.')
662 if archive_url and local_path:
663 raise DevServerError('archive_url and local_path can not both be '
664 'specified.')
665 if archive_url:
666 archive_url = self._canonicalize_archive_url(archive_url)
667 if local_path:
668 local_path = self._canonicalize_local_path(local_path)
Dan Shif8eb0d12013-08-01 17:52:06 -0700669 async = kwargs.get('async', False)
Chris Sosa6b0c6172013-08-05 17:01:33 -0700670 artifacts, files = self._get_artifacts(kwargs)
Dan Shi59ae7092013-06-04 14:37:27 -0700671 with DevServerRoot._staging_thread_count_lock:
672 DevServerRoot._staging_thread_count += 1
673 try:
Simran Basi4243a862014-12-12 12:48:33 -0800674 downloader.Downloader(
675 updater.static_dir, (archive_url or local_path)).Download(
676 artifacts, files, async=async)
Dan Shi59ae7092013-06-04 14:37:27 -0700677 finally:
678 with DevServerRoot._staging_thread_count_lock:
679 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800680 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700681
682 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800683 def setup_telemetry(self, **kwargs):
684 """Extracts and sets up telemetry
685
686 This method goes through the telemetry deps packages, and stages them on
687 the devserver to be used by the drones and the telemetry tests.
688
689 Args:
690 archive_url: Google Storage URL for the build.
691
692 Returns:
693 Path to the source folder for the telemetry codebase once it is staged.
694 """
695 archive_url = kwargs.get('archive_url')
Simran Basi4baad082013-02-14 13:39:18 -0800696
697 build = '/'.join(downloader.Downloader.ParseUrl(archive_url))
698 build_path = os.path.join(updater.static_dir, build)
699 deps_path = os.path.join(build_path, 'autotest/packages')
700 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
701 src_folder = os.path.join(telemetry_path, 'src')
702
703 with self._telemetry_lock_dict.lock(telemetry_path):
704 if os.path.exists(src_folder):
705 # Telemetry is already fully stage return
706 return src_folder
707
708 common_util.MkDirP(telemetry_path)
709
710 # Copy over the required deps tar balls to the telemetry directory.
711 for dep in TELEMETRY_DEPS:
712 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700713 if not os.path.exists(dep_path):
714 # This dep does not exist (could be new), do not extract it.
715 continue
Simran Basi4baad082013-02-14 13:39:18 -0800716 try:
717 common_util.ExtractTarball(dep_path, telemetry_path)
718 except common_util.CommonUtilError as e:
719 shutil.rmtree(telemetry_path)
720 raise DevServerError(str(e))
721
722 # By default all the tarballs extract to test_src but some parts of
723 # the telemetry code specifically hardcoded to exist inside of 'src'.
724 test_src = os.path.join(telemetry_path, 'test_src')
725 try:
726 shutil.move(test_src, src_folder)
727 except shutil.Error:
728 # This can occur if src_folder already exists. Remove and retry move.
729 shutil.rmtree(src_folder)
730 raise DevServerError('Failure in telemetry setup for build %s. Appears'
731 ' that the test_src to src move failed.' % build)
732
733 return src_folder
734
735 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800736 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700737 """Symbolicates a minidump using pre-downloaded symbols, returns it.
738
739 Callers will need to POST to this URL with a body of MIME-type
740 "multipart/form-data".
741 The body should include a single argument, 'minidump', containing the
742 binary-formatted minidump to symbolicate.
743
Chris Masone816e38c2012-05-02 12:22:36 -0700744 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800745 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700746 minidump: The binary minidump file to symbolicate.
747 """
Chris Sosa76e44b92013-01-31 12:11:38 -0800748 # Ensure the symbols have been staged.
749 archive_url = self._canonicalize_archive_url(kwargs.get('archive_url'))
750 if self.stage(archive_url=archive_url, artifacts='symbols') != 'Success':
751 raise DevServerError('Failed to stage symbols for %s' % archive_url)
752
Chris Masone816e38c2012-05-02 12:22:36 -0700753 to_return = ''
754 with tempfile.NamedTemporaryFile() as local:
755 while True:
756 data = minidump.file.read(8192)
757 if not data:
758 break
759 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800760
Chris Masone816e38c2012-05-02 12:22:36 -0700761 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800762
763 symbols_directory = os.path.join(downloader.Downloader.GetBuildDir(
764 updater.static_dir, archive_url), 'debug', 'breakpad')
765
766 stackwalk = subprocess.Popen(
767 ['minidump_stackwalk', local.name, symbols_directory],
768 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
769
Chris Masone816e38c2012-05-02 12:22:36 -0700770 to_return, error_text = stackwalk.communicate()
771 if stackwalk.returncode != 0:
772 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
773 error_text, stackwalk.returncode))
774
775 return to_return
776
777 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800778 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -0400779 """Return a string representing the latest build for a given target.
780
781 Args:
782 target: The build target, typically a combination of the board and the
783 type of build e.g. x86-mario-release.
784 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
785 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -0800786
Scott Zawalski16954532012-03-20 15:31:36 -0400787 Returns:
788 A string representation of the latest build if one exists, i.e.
789 R19-1993.0.0-a1-b1480.
790 An empty string if no latest could be found.
791 """
Don Garrettf84631a2014-01-07 18:21:26 -0800792 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -0400793 return _PrintDocStringAsHTML(self.latestbuild)
794
Don Garrettf84631a2014-01-07 18:21:26 -0800795 if 'target' not in kwargs:
Chris Sosa4b951602014-04-09 20:26:07 -0700796 raise common_util.DevServerHTTPError(500, 'Error: target= is required!')
Scott Zawalski16954532012-03-20 15:31:36 -0400797 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700798 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-07 18:21:26 -0800799 updater.static_dir, kwargs['target'],
800 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700801 except common_util.CommonUtilError as errmsg:
Chris Sosa4b951602014-04-09 20:26:07 -0700802 raise common_util.DevServerHTTPError(500, str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -0400803
804 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800805 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500806 """Return a control file or a list of all known control files.
807
808 Example URL:
809 To List all control files:
beepsbd337242013-07-09 22:44:06 -0700810 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
811 To List all control files for, say, the bvt suite:
812 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500813 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500814 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 -0500815
816 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500817 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500818 control_path: If you want the contents of a control file set this
819 to the path. E.g. client/site_tests/sleeptest/control
820 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -0700821 suite_name: If control_path is not specified but a suite_name is
822 specified, list the control files belonging to that suite instead of
823 all control files. The empty string for suite_name will list all control
824 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -0800825
Scott Zawalski4647ce62012-01-03 17:17:28 -0500826 Returns:
827 Contents of a control file if control_path is provided.
828 A list of control files if no control_path is provided.
829 """
Don Garrettf84631a2014-01-07 18:21:26 -0800830 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500831 return _PrintDocStringAsHTML(self.controlfiles)
832
Don Garrettf84631a2014-01-07 18:21:26 -0800833 if 'build' not in kwargs:
Chris Sosa4b951602014-04-09 20:26:07 -0700834 raise common_util.DevServerHTTPError(500, 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500835
Don Garrettf84631a2014-01-07 18:21:26 -0800836 if 'control_path' not in kwargs:
837 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -0700838 return common_util.GetControlFileListForSuite(
Don Garrettf84631a2014-01-07 18:21:26 -0800839 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -0700840 else:
841 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-07 18:21:26 -0800842 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500843 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700844 return common_util.GetControlFile(
Don Garrettf84631a2014-01-07 18:21:26 -0800845 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800846
847 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -0700848 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700849 """Translates an xBuddy path to a real path to artifact if it exists.
850
851 Args:
Simran Basi99e63c02014-05-20 10:39:52 -0700852 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
853 Local searches the devserver's static directory. Remote searches a
854 Google Storage image archive.
855
856 Kwargs:
857 image_dir: Google Storage image archive to search in if requesting a
858 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700859
860 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -0700861 String in the format of build_id/artifact as stored on the local server
862 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700863 """
Simran Basi99e63c02014-05-20 10:39:52 -0700864 build_id, filename = self._xbuddy.Translate(
865 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700866 response = os.path.join(build_id, filename)
867 _Log('Path translation requested, returning: %s', response)
868 return response
869
870 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -0700871 def xbuddy(self, *args, **kwargs):
872 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -0700873
874 Args:
joycheneaf4cfc2013-07-02 08:38:57 -0700875 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -0700876 components of the path. The path can be understood as
877 "{local|remote}/build_id/artifact" where build_id is composed of
878 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -0700879
joychen121fc9b2013-08-02 14:30:30 -0700880 The first path element is optional, and can be "remote" or "local"
881 If local (the default), devserver will not attempt to access Google
882 Storage, and will only search the static directory for the files.
883 If remote, devserver will try to obtain the artifact off GS if it's
884 not found locally.
885 The board is the familiar board name, optionally suffixed.
886 The version can be the google storage version number, and may also be
887 any of a number of xBuddy defined version aliases that will be
888 translated into the latest built image that fits the description.
889 Defaults to latest.
890 The artifact is one of a number of image or artifact aliases used by
891 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -0700892
893 Kwargs:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800894 for_update: {true|false}
895 if true, pregenerates the update payloads for the image,
896 and returns the update uri to pass to the
897 update_engine_client.
joychen3cb228e2013-06-12 12:13:13 -0700898 return_dir: {true|false}
899 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800900 relative_path: {true|false}
901 if set to true, returns the relative path to the payload
902 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -0700903 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -0700904 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -0700905 or
joycheneaf4cfc2013-07-02 08:38:57 -0700906 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -0700907
908 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800909 If |for_update|, returns a redirect to the image or update file
910 on the devserver. E.g.,
911 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
912 chromium-test-image.bin
913 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
914 http://host:port/static/x86-generic-release/R26-4000.0.0/
915 If |relative_path| is true, return a relative path the folder where the
916 payloads are. E.g.,
917 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -0700918 """
Chris Sosa75490802013-09-30 17:21:45 -0700919 boolean_string = kwargs.get('for_update')
920 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800921 boolean_string = kwargs.get('return_dir')
922 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
923 boolean_string = kwargs.get('relative_path')
924 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -0700925
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800926 if return_dir and relative_path:
Chris Sosa4b951602014-04-09 20:26:07 -0700927 raise common_util.DevServerHTTPError(
928 500, 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -0700929
930 # For updates, we optimize downloading of test images.
931 file_name = None
932 build_id = None
933 if for_update:
934 try:
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700935 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-09-30 17:21:45 -0700936 except build_artifact.ArtifactDownloadError:
937 build_id = None
938
939 if not build_id:
940 build_id, file_name = self._xbuddy.Get(args)
941
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800942 if for_update:
943 _Log('Payload generation triggered by request')
944 # Forces payload to be in cache and symlinked into build_id dir.
Chris Sosa75490802013-09-30 17:21:45 -0700945 updater.GetUpdateForLabel(autoupdate.FORCED_UPDATE, build_id,
946 image_name=file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800947
948 response = None
949 if return_dir:
950 response = os.path.join(cherrypy.request.base, 'static', build_id)
951 _Log('Directory requested, returning: %s', response)
952 elif relative_path:
953 response = build_id
954 _Log('Relative path requested, returning: %s', response)
955 elif for_update:
956 response = os.path.join(cherrypy.request.base, 'update', build_id)
957 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -0700958 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800959 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -0700960 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800961 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -0700962 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -0700963
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800964 return response
965
joychen3cb228e2013-06-12 12:13:13 -0700966 @cherrypy.expose
967 def xbuddy_list(self):
968 """Lists the currently available images & time since last access.
969
Gilad Arnold452fd272014-02-04 11:09:28 -0800970 Returns:
971 A string representation of a list of tuples [(build_id, time since last
972 access),...]
joychen3cb228e2013-06-12 12:13:13 -0700973 """
974 return self._xbuddy.List()
975
976 @cherrypy.expose
977 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -0800978 """Returns the number of images cached by xBuddy."""
joychen3cb228e2013-06-12 12:13:13 -0700979 return self._xbuddy.Capacity()
980
981 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -0700982 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700983 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700984 return ('Welcome to the Dev Server!<br>\n'
985 '<br>\n'
986 'Here are the available methods, click for documentation:<br>\n'
987 '<br>\n'
988 '%s' %
989 '<br>\n'.join(
990 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700991 for name in _FindExposedMethods(
992 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700993
994 @cherrypy.expose
995 def doc(self, *args):
996 """Shows the documentation for available methods / URLs.
997
998 Example:
999 http://myhost/doc/update
1000 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001001 name = '/'.join(args)
1002 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001003 if not method:
1004 raise DevServerError("No exposed method named `%s'" % name)
1005 if not method.__doc__:
1006 raise DevServerError("No documentation for exposed method `%s'" % name)
1007 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001008
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07001009 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001010 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001011 """Handles an update check from a Chrome OS client.
1012
1013 The HTTP request should contain the standard Omaha-style XML blob. The URL
1014 line may contain an additional intermediate path to the update payload.
1015
joychen121fc9b2013-08-02 14:30:30 -07001016 This request can be handled in one of 4 ways, depending on the devsever
1017 settings and intermediate path.
joychenb0dfe552013-07-30 10:02:06 -07001018
joychen121fc9b2013-08-02 14:30:30 -07001019 1. No intermediate path
1020 If no intermediate path is given, the default behavior is to generate an
1021 update payload from the latest test image locally built for the board
1022 specified in the xml. Devserver serves the generated payload.
1023
1024 2. Path explicitly invokes XBuddy
1025 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1026 with 'xbuddy'. This path is then used to acquire an image binary for the
1027 devserver to generate an update payload from. Devserver then serves this
1028 payload.
1029
1030 3. Path is left for the devserver to interpret.
1031 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1032 to generate a payload from the test image in that directory and serve it.
1033
1034 4. The devserver is in a 'forced' mode. TO BE DEPRECATED
1035 This comes from the usage of --forced_payload or --image when starting the
1036 devserver. No matter what path (or no path) gets passed in, devserver will
1037 serve the update payload (--forced_payload) or generate an update payload
1038 from the image (--image).
1039
1040 Examples:
1041 1. No intermediate path
1042 update_engine_client --omaha_url=http://myhost/update
1043 This generates an update payload from the latest test image locally built
1044 for the board specified in the xml.
1045
1046 2. Explicitly invoke xbuddy
1047 update_engine_client --omaha_url=
1048 http://myhost/update/xbuddy/remote/board/version/dev
1049 This would go to GS to download the dev image for the board, from which
1050 the devserver would generate a payload to serve.
1051
1052 3. Give a path for devserver to interpret
1053 update_engine_client --omaha_url=http://myhost/update/some/random/path
1054 This would attempt, in order to:
1055 a) Generate an update from a test image binary if found in
1056 static_dir/some/random/path.
1057 b) Serve an update payload found in static_dir/some/random/path.
1058 c) Hope that some/random/path takes the form "board/version" and
1059 and attempt to download an update payload for that board/version
1060 from GS.
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001061 """
joychen121fc9b2013-08-02 14:30:30 -07001062 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -08001063 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -07001064 data = cherrypy.request.rfile.read(body_length)
Chris Sosa7c931362010-10-11 19:49:01 -07001065
joychen121fc9b2013-08-02 14:30:30 -07001066 return updater.HandleUpdatePing(data, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001067
Dan Shiafd0e492015-05-27 14:23:51 -07001068 @require_psutil()
1069 def _get_io_stats(self):
1070 """Get the IO stats as a dictionary.
1071
1072 @return: A dictionary of IO stats collected by psutil.
1073
1074 """
1075 return {'disk_read_bytes_per_second': self.disk_read_bytes_per_sec,
1076 'disk_write_bytes_per_second': self.disk_write_bytes_per_sec,
1077 'disk_total_bytes_per_second': (self.disk_read_bytes_per_sec +
1078 self.disk_write_bytes_per_sec),
1079 'network_sent_bytes_per_second': self.network_sent_bytes_per_sec,
1080 'network_recv_bytes_per_second': self.network_recv_bytes_per_sec,
1081 'network_total_bytes_per_second': (self.network_sent_bytes_per_sec +
1082 self.network_recv_bytes_per_sec),
1083 'cpu_percent': psutil.cpu_percent(),}
1084
Dan Shif5ce2de2013-04-25 16:06:32 -07001085 @cherrypy.expose
1086 def check_health(self):
1087 """Collect the health status of devserver to see if it's ready for staging.
1088
Gilad Arnold452fd272014-02-04 11:09:28 -08001089 Returns:
1090 A JSON dictionary containing all or some of the following fields:
1091 free_disk (int): free disk space in GB
1092 staging_thread_count (int): number of devserver threads currently staging
1093 an image
Dan Shif5ce2de2013-04-25 16:06:32 -07001094 """
1095 # Get free disk space.
1096 stat = os.statvfs(updater.static_dir)
1097 free_disk = stat.f_bsize * stat.f_bavail / 1000000000
1098
Dan Shiafd0e492015-05-27 14:23:51 -07001099 health_data = {
Dan Shif5ce2de2013-04-25 16:06:32 -07001100 'free_disk': free_disk,
Dan Shiafd0e492015-05-27 14:23:51 -07001101 'staging_thread_count': DevServerRoot._staging_thread_count}
1102 health_data.update(self._get_io_stats() or {})
1103
1104 return json.dumps(health_data)
Dan Shif5ce2de2013-04-25 16:06:32 -07001105
1106
Chris Sosadbc20082012-12-10 13:39:11 -08001107def _CleanCache(cache_dir, wipe):
1108 """Wipes any excess cached items in the cache_dir.
1109
1110 Args:
1111 cache_dir: the directory we are wiping from.
1112 wipe: If True, wipe all the contents -- not just the excess.
1113 """
1114 if wipe:
1115 # Clear the cache and exit on error.
1116 cmd = 'rm -rf %s/*' % cache_dir
1117 if os.system(cmd) != 0:
1118 _Log('Failed to clear the cache with %s' % cmd)
1119 sys.exit(1)
1120 else:
1121 # Clear all but the last N cached updates
1122 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1123 (cache_dir, CACHED_ENTRIES))
1124 if os.system(cmd) != 0:
1125 _Log('Failed to clean up old delta cache files with %s' % cmd)
1126 sys.exit(1)
1127
1128
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001129def _AddTestingOptions(parser):
1130 group = optparse.OptionGroup(
1131 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1132 'developers writing integration tests utilizing the devserver. They are '
1133 'not intended to be really used outside the scope of someone '
1134 'knowledgable about the test.')
1135 group.add_option('--exit',
1136 action='store_true',
1137 help='do not start the server (yet pregenerate/clear cache)')
1138 group.add_option('--host_log',
1139 action='store_true', default=False,
1140 help='record history of host update events (/api/hostlog)')
1141 group.add_option('--max_updates',
1142 metavar='NUM', default= -1, type='int',
1143 help='maximum number of update checks handled positively '
1144 '(default: unlimited)')
1145 group.add_option('--private_key',
1146 metavar='PATH', default=None,
1147 help='path to the private key in pem format. If this is set '
1148 'the devserver will generate update payloads that are '
1149 'signed with this key.')
David Zeuthen52ccd012013-10-31 12:58:26 -07001150 group.add_option('--private_key_for_metadata_hash_signature',
1151 metavar='PATH', default=None,
1152 help='path to the private key in pem format. If this is set '
1153 'the devserver will sign the metadata hash with the given '
1154 'key and transmit in the Omaha-style XML response.')
1155 group.add_option('--public_key',
1156 metavar='PATH', default=None,
1157 help='path to the public key in pem format. If this is set '
1158 'the devserver will transmit a base64 encoded version of '
1159 'the content in the Omaha-style XML response.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001160 group.add_option('--proxy_port',
1161 metavar='PORT', default=None, type='int',
1162 help='port to have the client connect to -- basically the '
1163 'devserver lies to the update to tell it to get the payload '
1164 'from a different port that will proxy the request back to '
1165 'the devserver. The proxy must be managed outside the '
1166 'devserver.')
1167 group.add_option('--remote_payload',
1168 action='store_true', default=False,
Chris Sosa4b951602014-04-09 20:26:07 -07001169 help='Payload is being served from a remote machine. With '
1170 'this setting enabled, this devserver instance serves as '
1171 'just an Omaha server instance. In this mode, the '
1172 'devserver enforces a few extra components of the Omaha '
Chris Sosafc715442014-04-09 20:45:23 -07001173 'protocol, such as hardware class, being sent.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001174 group.add_option('-u', '--urlbase',
1175 metavar='URL',
1176 help='base URL for update images, other than the '
1177 'devserver. Use in conjunction with remote_payload.')
1178 parser.add_option_group(group)
1179
1180
1181def _AddUpdateOptions(parser):
1182 group = optparse.OptionGroup(
1183 parser, 'Autoupdate Options', 'These options can be used to change '
1184 'how the devserver either generates or serve update payloads. Please '
1185 'note that all of these option affect how a payload is generated and so '
1186 'do not work in archive-only mode.')
1187 group.add_option('--board',
1188 help='By default the devserver will create an update '
1189 'payload from the latest image built for the board '
1190 'a device that is requesting an update has. When we '
1191 'pre-generate an update (see below) and we do not specify '
1192 'another update_type option like image or payload, the '
1193 'devserver needs to know the board to generate the latest '
1194 'image for. This is that board.')
1195 group.add_option('--critical_update',
1196 action='store_true', default=False,
1197 help='Present update payload as critical')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001198 group.add_option('--image',
1199 metavar='FILE',
1200 help='Generate and serve an update using this image to any '
1201 'device that requests an update.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001202 group.add_option('--payload',
1203 metavar='PATH',
1204 help='use the update payload from specified directory '
1205 '(update.gz).')
1206 group.add_option('-p', '--pregenerate_update',
1207 action='store_true', default=False,
1208 help='pre-generate the update payload before accepting '
1209 'update requests. Useful to help debug payload generation '
1210 'issues quickly. Also if an update payload will take a '
1211 'long time to generate, a client may timeout if you do not'
1212 'pregenerate the update.')
1213 group.add_option('--src_image',
1214 metavar='PATH', default='',
1215 help='If specified, delta updates will be generated using '
1216 'this image as the source image. Delta updates are when '
1217 'you are updating from a "source image" to a another '
1218 'image.')
1219 parser.add_option_group(group)
1220
1221
1222def _AddProductionOptions(parser):
1223 group = optparse.OptionGroup(
1224 parser, 'Advanced Server Options', 'These options can be used to changed '
1225 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001226 group.add_option('--clear_cache',
1227 action='store_true', default=False,
1228 help='At startup, removes all cached entries from the'
1229 'devserver\'s cache.')
1230 group.add_option('--logfile',
1231 metavar='PATH',
1232 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001233 group.add_option('--pidfile',
1234 metavar='PATH',
1235 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001236 group.add_option('--portfile',
1237 metavar='PATH',
1238 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001239 group.add_option('--production',
1240 action='store_true', default=False,
1241 help='have the devserver use production values when '
1242 'starting up. This includes using more threads and '
1243 'performing less logging.')
1244 parser.add_option_group(group)
1245
1246
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001247def _MakeLogHandler(logfile):
1248 """Create a LogHandler instance used to log all messages."""
1249 hdlr_cls = handlers.TimedRotatingFileHandler
1250 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
1251 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001252 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001253 return hdlr
1254
1255
Chris Sosacde6bf42012-05-31 18:36:39 -07001256def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001257 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001258 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001259
1260 # get directory that the devserver is run from
1261 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001262 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001263 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001264 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001265 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001266 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001267 parser.add_option('--port',
1268 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001269 help=('port for the dev server to use; if zero, binds to '
1270 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001271 parser.add_option('-t', '--test_image',
1272 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001273 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001274 parser.add_option('-x', '--xbuddy_manage_builds',
1275 action='store_true',
1276 default=False,
1277 help='If set, allow xbuddy to manage images in'
1278 'build/images.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001279 _AddProductionOptions(parser)
1280 _AddUpdateOptions(parser)
1281 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001282 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001283
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001284 # Handle options that must be set globally in cherrypy. Do this
1285 # work up front, because calls to _Log() below depend on this
1286 # initialization.
1287 if options.production:
1288 cherrypy.config.update({'environment': 'production'})
1289 if not options.logfile:
1290 cherrypy.config.update({'log.screen': True})
1291 else:
1292 cherrypy.config.update({'log.error_file': '',
1293 'log.access_file': ''})
1294 hdlr = _MakeLogHandler(options.logfile)
1295 # Pylint can't seem to process these two calls properly
1296 # pylint: disable=E1101
1297 cherrypy.log.access_log.addHandler(hdlr)
1298 cherrypy.log.error_log.addHandler(hdlr)
1299 # pylint: enable=E1101
1300
joychened64b222013-06-21 16:39:34 -07001301 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001302 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001303
joychened64b222013-06-21 16:39:34 -07001304 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001305 # If our devserver is only supposed to serve payloads, we shouldn't be
1306 # mucking with the cache at all. If the devserver hadn't previously
1307 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001308 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001309 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001310 else:
1311 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001312
Chris Sosadbc20082012-12-10 13:39:11 -08001313 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001314 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001315
joychen121fc9b2013-08-02 14:30:30 -07001316 _xbuddy = xbuddy.XBuddy(options.xbuddy_manage_builds,
1317 options.board,
joychen121fc9b2013-08-02 14:30:30 -07001318 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001319 if options.clear_cache and options.xbuddy_manage_builds:
1320 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001321
Chris Sosa6a3697f2013-01-29 16:44:43 -08001322 # We allow global use here to share with cherrypy classes.
1323 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001324 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001325 updater = autoupdate.Autoupdate(
joychen121fc9b2013-08-02 14:30:30 -07001326 _xbuddy,
joychened64b222013-06-21 16:39:34 -07001327 static_dir=options.static_dir,
Andrew de los Reyes52620802010-04-12 13:40:07 -07001328 urlbase=options.urlbase,
Chris Sosa5d342a22010-09-28 16:54:41 -07001329 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001330 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001331 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -07001332 src_image=options.src_image,
Chris Sosa08d55a22011-01-19 16:08:02 -08001333 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001334 copy_to_static_root=not options.exit,
1335 private_key=options.private_key,
David Zeuthen52ccd012013-10-31 12:58:26 -07001336 private_key_for_metadata_hash_signature=
1337 options.private_key_for_metadata_hash_signature,
1338 public_key=options.public_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001339 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001340 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001341 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001342 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001343 )
Chris Sosa7c931362010-10-11 19:49:01 -07001344
Chris Sosa6a3697f2013-01-29 16:44:43 -08001345 if options.pregenerate_update:
1346 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -07001347
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001348 if options.exit:
1349 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001350
joychen3cb228e2013-06-12 12:13:13 -07001351 dev_server = DevServerRoot(_xbuddy)
1352
Gilad Arnold11fbef42014-02-10 11:04:13 -08001353 # Patch CherryPy to support binding to any available port (--port=0).
1354 cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy)
1355
Chris Sosa855b8932013-08-21 13:24:55 -07001356 if options.pidfile:
1357 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1358
Gilad Arnold11fbef42014-02-10 11:04:13 -08001359 if options.portfile:
1360 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1361
joychen3cb228e2013-06-12 12:13:13 -07001362 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001363
1364
1365if __name__ == '__main__':
1366 main()