blob: 8ab9d6887854befd8ef79f3183f0613eb3183306 [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
xixuan52c2fba2016-05-20 17:02:48 -070049import signal
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -080050import socket
Chris Masone816e38c2012-05-02 12:22:36 -070051import subprocess
J. Richard Barnette3d977b82013-04-23 11:05:19 -070052import sys
Chris Masone816e38c2012-05-02 12:22:36 -070053import tempfile
Dan Shi59ae7092013-06-04 14:37:27 -070054import threading
Dan Shiafd0e492015-05-27 14:23:51 -070055import time
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070056import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070057from logging import handlers
58
59import cherrypy
Chris Sosa855b8932013-08-21 13:24:55 -070060from cherrypy import _cplogging as cplogging
61from cherrypy.process import plugins
rtc@google.comded22402009-10-26 22:36:21 +000062
Chris Sosa0356d3b2010-09-16 15:46:22 -070063import autoupdate
Dan Shi2f136862016-02-11 15:38:38 -080064import artifact_info
Chris Sosa75490802013-09-30 17:21:45 -070065import build_artifact
Gilad Arnold11fbef42014-02-10 11:04:13 -080066import cherrypy_ext
Gilad Arnoldc65330c2012-09-20 15:17:48 -070067import common_util
Simran Basief83d6a2014-08-28 14:32:01 -070068import devserver_constants
Chris Sosa47a7d4e2012-03-28 11:26:55 -070069import downloader
Chris Sosa7cd23202013-10-15 17:22:57 -070070import gsutil_util
Gilad Arnoldc65330c2012-09-20 15:17:48 -070071import log_util
joychen3cb228e2013-06-12 12:13:13 -070072import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070073
Gilad Arnoldc65330c2012-09-20 15:17:48 -070074# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080075def _Log(message, *args):
76 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 15:46:22 -070077
Dan Shiafd0e492015-05-27 14:23:51 -070078try:
79 import psutil
80except ImportError:
81 # Ignore psutil import failure. This is for backwards compatibility, so
82 # "cros flash" can still update duts with build without psutil installed.
83 # The reason is that, during cros flash, local devserver code is copied over
84 # to DUT, and devserver will be running inside DUT to stage the build.
85 _Log('Python module psutil is not installed, devserver load data will not be '
86 'collected')
87 psutil = None
Dan Shi94dcbe82015-06-08 20:51:13 -070088except OSError as e:
89 # Ignore error like following. psutil may not work properly in builder. Ignore
90 # the error as load information of devserver is not used in builder.
91 # OSError: [Errno 2] No such file or directory: '/dev/pts/0'
92 _Log('psutil is failed to be imported, error: %s. devserver load data will '
93 'not be collected.', e)
94 psutil = None
95
xixuanac89ce82016-11-30 16:48:20 -080096# Use try-except to skip unneccesary import for simple use case, eg. running
97# devserver on host.
98try:
99 import cros_update
100 import cros_update_progress
101except ImportError as e:
102 _Log('cros_update cannot be imported: %r', e)
103 cros_update = None
104 cros_update_progress = None
105
106# only import setup_chromite before chromite import.
107import setup_chromite # pylint: disable=unused-import
108try:
109 from chromite.lib.paygen import gspaths
110except ImportError as e:
111 _Log('chromite cannot be imported: %r', e)
112 gspaths = None
113
Dan Shi72b16132015-10-08 12:10:33 -0700114try:
115 import android_build
116except ImportError as e:
117 # Ignore android_build import failure. This is to support devserver running
118 # inside a ChromeOS device triggered by cros flash. Most ChromeOS test images
119 # do not have google-api-python-client module and they don't need to support
120 # Android updating, therefore, ignore the import failure here.
Dan Shi72b16132015-10-08 12:10:33 -0700121 android_build = None
Frank Farzan40160872011-12-12 18:39:18 -0800122
Chris Sosa417e55d2011-01-25 16:40:48 -0800123CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -0800124
Simran Basi4baad082013-02-14 13:39:18 -0800125TELEMETRY_FOLDER = 'telemetry_src'
126TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
127 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -0700128 'dep-chrome_test.tar.bz2',
129 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -0800130
Chris Sosa0356d3b2010-09-16 15:46:22 -0700131# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +0000132updater = None
rtc@google.comded22402009-10-26 22:36:21 +0000133
xixuan3d48bff2017-01-30 19:00:09 -0800134# Log rotation parameters. These settings correspond to twice a day once
135# devserver is started, with about two weeks (28 backup files) of old logs
136# kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700137#
xixuan3d48bff2017-01-30 19:00:09 -0800138# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700139# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-30 19:00:09 -0800140_LOG_ROTATION_TIME = 'H'
141_LOG_ROTATION_INTERVAL = 12 # hours
142_LOG_ROTATION_BACKUP = 28 # backup counts
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700143
Dan Shiafd0e492015-05-27 14:23:51 -0700144# Number of seconds between the collection of disk and network IO counters.
145STATS_INTERVAL = 10.0
Frank Farzan40160872011-12-12 18:39:18 -0800146
xixuan52c2fba2016-05-20 17:02:48 -0700147# Auto-update parameters
148
149# Error msg for missing key in CrOS auto-update.
150KEY_ERROR_MSG = 'Key Error in cmd %s: %s= is required'
151
152# Command of running auto-update.
153AUTO_UPDATE_CMD = '/usr/bin/python -u %s -d %s -b %s --static_dir %s'
154
155
Chris Sosa9164ca32012-03-28 11:04:50 -0700156class DevServerError(Exception):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700157 """Exception class used by this module."""
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700158
159
Dan Shiafd0e492015-05-27 14:23:51 -0700160def require_psutil():
Gabe Black3b567202015-09-23 14:07:59 -0700161 """Decorator for functions require psutil to run."""
Dan Shiafd0e492015-05-27 14:23:51 -0700162 def deco_require_psutil(func):
163 """Wrapper of the decorator function.
164
Gabe Black3b567202015-09-23 14:07:59 -0700165 Args:
166 func: function to be called.
Dan Shiafd0e492015-05-27 14:23:51 -0700167 """
168 def func_require_psutil(*args, **kwargs):
169 """Decorator for functions require psutil to run.
170
171 If psutil is not installed, skip calling the function.
172
Gabe Black3b567202015-09-23 14:07:59 -0700173 Args:
174 *args: arguments for function to be called.
175 **kwargs: keyword arguments for function to be called.
Dan Shiafd0e492015-05-27 14:23:51 -0700176 """
177 if psutil:
178 return func(*args, **kwargs)
179 else:
180 _Log('Python module psutil is not installed. Function call %s is '
181 'skipped.' % func)
182 return func_require_psutil
183 return deco_require_psutil
184
185
Gabe Black3b567202015-09-23 14:07:59 -0700186def _canonicalize_archive_url(archive_url):
187 """Canonicalizes archive_url strings.
188
189 Raises:
190 DevserverError: if archive_url is not set.
191 """
192 if archive_url:
193 if not archive_url.startswith('gs://'):
194 raise DevServerError("Archive URL isn't from Google Storage (%s) ." %
195 archive_url)
196
197 return archive_url.rstrip('/')
198 else:
199 raise DevServerError("Must specify an archive_url in the request")
200
201
202def _canonicalize_local_path(local_path):
203 """Canonicalizes |local_path| strings.
204
205 Raises:
206 DevserverError: if |local_path| is not set.
207 """
208 # Restrict staging of local content to only files within the static
209 # directory.
210 local_path = os.path.abspath(local_path)
211 if not local_path.startswith(updater.static_dir):
212 raise DevServerError('Local path %s must be a subdirectory of the static'
213 ' directory: %s' % (local_path, updater.static_dir))
214
215 return local_path.rstrip('/')
216
217
218def _get_artifacts(kwargs):
219 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
220
221 Raises:
222 DevserverError if no artifacts would be returned.
223 """
224 artifacts = kwargs.get('artifacts')
225 files = kwargs.get('files')
226 if not artifacts and not files:
227 raise DevServerError('No artifacts specified.')
228
229 # Note we NEED to coerce files to a string as we get raw unicode from
230 # cherrypy and we treat files as strings elsewhere in the code.
231 return (str(artifacts).split(',') if artifacts else [],
232 str(files).split(',') if files else [])
233
234
Dan Shi61305df2015-10-26 16:52:35 -0700235def _is_android_build_request(kwargs):
236 """Check if a devserver call is for Android build, based on the arguments.
237
238 This method exams the request's arguments (os_type) to determine if the
239 request is for Android build. If os_type is set to `android`, returns True.
240 If os_type is not set or has other values, returns False.
241
242 Args:
243 kwargs: Keyword arguments for the request.
244
245 Returns:
246 True if the request is for Android build. False otherwise.
247 """
248 os_type = kwargs.get('os_type', None)
249 return os_type == 'android'
250
251
Gabe Black3b567202015-09-23 14:07:59 -0700252def _get_downloader(kwargs):
253 """Returns the downloader based on passed in arguments.
254
255 Args:
256 kwargs: Keyword arguments for the request.
257 """
258 local_path = kwargs.get('local_path')
259 if local_path:
260 local_path = _canonicalize_local_path(local_path)
261
262 dl = None
263 if local_path:
264 dl = downloader.LocalDownloader(updater.static_dir, local_path)
265
Dan Shi61305df2015-10-26 16:52:35 -0700266 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700267 archive_url = kwargs.get('archive_url')
268 if not archive_url and not local_path:
269 raise DevServerError('Requires archive_url or local_path to be '
270 'specified.')
271 if archive_url and local_path:
272 raise DevServerError('archive_url and local_path can not both be '
273 'specified.')
274 if not dl:
275 archive_url = _canonicalize_archive_url(archive_url)
276 dl = downloader.GoogleStorageDownloader(updater.static_dir, archive_url)
277 elif not dl:
278 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 12:10:33 -0700279 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 16:52:35 -0700280 build_id = kwargs.get('build_id', None)
281 if not target or not branch or not build_id:
Dan Shi72b16132015-10-08 12:10:33 -0700282 raise DevServerError(
Dan Shi61305df2015-10-26 16:52:35 -0700283 'target, branch, build ID must all be specified for downloading '
284 'Android build.')
Dan Shi72b16132015-10-08 12:10:33 -0700285 dl = downloader.AndroidBuildDownloader(updater.static_dir, branch, build_id,
286 target)
Gabe Black3b567202015-09-23 14:07:59 -0700287
288 return dl
289
290
291def _get_downloader_and_factory(kwargs):
292 """Returns the downloader and artifact factory based on passed in arguments.
293
294 Args:
295 kwargs: Keyword arguments for the request.
296 """
297 artifacts, files = _get_artifacts(kwargs)
298 dl = _get_downloader(kwargs)
299
300 if (isinstance(dl, downloader.GoogleStorageDownloader) or
301 isinstance(dl, downloader.LocalDownloader)):
302 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 12:10:33 -0700303 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 14:07:59 -0700304 factory_class = build_artifact.AndroidArtifactFactory
305 else:
306 raise DevServerError('Unrecognized value for downloader type: %s' %
307 type(dl))
308
309 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
310
311 return dl, factory
312
313
Scott Zawalski4647ce62012-01-03 17:17:28 -0500314def _LeadingWhiteSpaceCount(string):
315 """Count the amount of leading whitespace in a string.
316
317 Args:
318 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800319
Scott Zawalski4647ce62012-01-03 17:17:28 -0500320 Returns:
321 number of white space chars before characters start.
322 """
Gabe Black3b567202015-09-23 14:07:59 -0700323 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500324 if matched:
325 return len(matched.group())
326
327 return 0
328
329
330def _PrintDocStringAsHTML(func):
331 """Make a functions docstring somewhat HTML style.
332
333 Args:
334 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800335
Scott Zawalski4647ce62012-01-03 17:17:28 -0500336 Returns:
337 A string that is somewhat formated for a web browser.
338 """
339 # TODO(scottz): Make this parse Args/Returns in a prettier way.
340 # Arguments could be bolded and indented etc.
341 html_doc = []
342 for line in func.__doc__.splitlines():
343 leading_space = _LeadingWhiteSpaceCount(line)
344 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700345 line = '&nbsp;' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500346
347 html_doc.append('<BR>%s' % line)
348
349 return '\n'.join(html_doc)
350
351
Simran Basief83d6a2014-08-28 14:32:01 -0700352def _GetUpdateTimestampHandler(static_dir):
353 """Returns a handler to update directory staged.timestamp.
354
355 This handler resets the stage.timestamp whenever static content is accessed.
356
357 Args:
358 static_dir: Directory from which static content is being staged.
359
360 Returns:
361 A cherrypy handler to update the timestamp of accessed content.
362 """
363 def UpdateTimestampHandler():
364 if not '404' in cherrypy.response.status:
365 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
366 cherrypy.request.path_info)
367 if build_match:
368 build_dir = os.path.join(static_dir, build_match.group('build'))
369 downloader.Downloader.TouchTimestampForStaged(build_dir)
370 return UpdateTimestampHandler
371
372
Chris Sosa7c931362010-10-11 19:49:01 -0700373def _GetConfig(options):
374 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800375
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800376 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800377 # Fall back to IPv4 when python is not configured with IPv6.
378 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800379 socket_host = '0.0.0.0'
380
Simran Basief83d6a2014-08-28 14:32:01 -0700381 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
382 # on the on_end_resource hook. This hook is called once processing is
383 # complete and the response is ready to be returned.
384 cherrypy.tools.update_timestamp = cherrypy.Tool(
385 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
386
Gabe Black3b567202015-09-23 14:07:59 -0700387 base_config = {'global':
388 {'server.log_request_headers': True,
389 'server.protocol_version': 'HTTP/1.1',
390 'server.socket_host': socket_host,
391 'server.socket_port': int(options.port),
392 'response.timeout': 6000,
393 'request.show_tracebacks': True,
394 'server.socket_timeout': 60,
395 'server.thread_pool': 2,
396 'engine.autoreload.on': False,
397 },
398 '/api':
399 {
400 # Gets rid of cherrypy parsing post file for args.
401 'request.process_request_body': False,
402 },
403 '/build':
404 {'response.timeout': 100000,
405 },
406 '/update':
407 {
408 # Gets rid of cherrypy parsing post file for args.
409 'request.process_request_body': False,
410 'response.timeout': 10000,
411 },
412 # Sets up the static dir for file hosting.
413 '/static':
414 {'tools.staticdir.dir': options.static_dir,
415 'tools.staticdir.on': True,
416 'response.timeout': 10000,
417 'tools.update_timestamp.on': True,
418 },
419 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700420 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700421 base_config['global'].update({'server.thread_pool': 150})
Chris Sosa7cd23202013-10-15 17:22:57 -0700422 # TODO(sosa): Do this more cleanly.
423 gsutil_util.GSUTIL_ATTEMPTS = 5
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500424
Chris Sosa7c931362010-10-11 19:49:01 -0700425 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000426
Darin Petkove17164a2010-08-11 13:24:41 -0700427
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700428def _GetRecursiveMemberObject(root, member_list):
429 """Returns an object corresponding to a nested member list.
430
431 Args:
432 root: the root object to search
433 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800434
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700435 Returns:
436 An object corresponding to the member name list; None otherwise.
437 """
438 for member in member_list:
439 next_root = root.__class__.__dict__.get(member)
440 if not next_root:
441 return None
442 root = next_root
443 return root
444
445
446def _IsExposed(name):
447 """Returns True iff |name| has an `exposed' attribute and it is set."""
448 return hasattr(name, 'exposed') and name.exposed
449
450
Gilad Arnold748c8322012-10-12 09:51:35 -0700451def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700452 """Returns a CherryPy-exposed method, if such exists.
453
454 Args:
455 root: the root object for searching
456 nested_member: a slash-joined path to the nested member
457 ignored: method paths to be ignored
Don Garrettf84631a2014-01-07 18:21:26 -0800458
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700459 Returns:
460 A function object corresponding to the path defined by |member_list| from
461 the |root| object, if the function is exposed and not ignored; None
462 otherwise.
463 """
Gilad Arnold748c8322012-10-12 09:51:35 -0700464 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700465 _GetRecursiveMemberObject(root, nested_member.split('/')))
Gabe Black3b567202015-09-23 14:07:59 -0700466 if method and type(method) == types.FunctionType and _IsExposed(method):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700467 return method
468
469
Gilad Arnold748c8322012-10-12 09:51:35 -0700470def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700471 """Finds exposed CherryPy methods.
472
473 Args:
474 root: the root object for searching
475 prefix: slash-joined chain of members leading to current object
476 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800477
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700478 Returns:
479 List of exposed URLs that are not unlisted.
480 """
481 method_list = []
482 for member in sorted(root.__class__.__dict__.keys()):
483 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700484 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700485 continue
486 member_obj = root.__class__.__dict__[member]
487 if _IsExposed(member_obj):
488 if type(member_obj) == types.FunctionType:
489 method_list.append(prefixed_member)
490 else:
491 method_list += _FindExposedMethods(
492 member_obj, prefixed_member, unlisted)
493 return method_list
494
495
xixuan52c2fba2016-05-20 17:02:48 -0700496def _check_base_args_for_auto_update(kwargs):
xixuanac89ce82016-11-30 16:48:20 -0800497 """Check basic args required for auto-update.
498
499 Args:
500 kwargs: the parameters to be checked.
501
502 Raises:
503 DevServerHTTPError if required parameters don't exist in kwargs.
504 """
xixuan52c2fba2016-05-20 17:02:48 -0700505 if 'host_name' not in kwargs:
506 raise common_util.DevServerHTTPError(KEY_ERROR_MSG % 'host_name')
507
508 if 'build_name' not in kwargs:
509 raise common_util.DevServerHTTPError(KEY_ERROR_MSG % 'build_name')
510
511
512def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-11-30 16:48:20 -0800513 """Parse boolean arg from kwargs.
514
515 Args:
516 kwargs: the parameters to be checked.
517 key: the key to be parsed.
518
519 Returns:
520 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
521
522 Raises:
523 DevServerHTTPError if kwargs[key] is not a boolean variable.
524 """
xixuan52c2fba2016-05-20 17:02:48 -0700525 if key in kwargs:
526 if kwargs[key] == 'True':
527 return True
528 elif kwargs[key] == 'False':
529 return False
530 else:
531 raise common_util.DevServerHTTPError(
532 'The value for key %s is not boolean.' % key)
533 else:
534 return False
535
xixuanac89ce82016-11-30 16:48:20 -0800536def _parse_string_arg(kwargs, key):
537 """Parse string arg from kwargs.
538
539 Args:
540 kwargs: the parameters to be checked.
541 key: the key to be parsed.
542
543 Returns:
544 The string value of kwargs[key], or None if key doesn't exist in kwargs.
545 """
546 if key in kwargs:
547 return kwargs[key]
548 else:
549 return None
550
551def _build_uri_from_build_name(build_name):
552 """Get build url from a given build name.
553
554 Args:
555 build_name: the build name to be parsed, whose format is
556 'board/release_version'.
557
558 Returns:
559 The release_archive_url on Google Storage for this build name.
560 """
561 return gspaths.ChromeosReleases.BuildUri(
562 cros_update.STABLE_BUILD_CHANNEL, build_name.split('/')[0],
563 build_name.split('/')[1])
xixuan52c2fba2016-05-20 17:02:48 -0700564
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700565class ApiRoot(object):
566 """RESTful API for Dev Server information."""
567 exposed = True
568
569 @cherrypy.expose
570 def hostinfo(self, ip):
571 """Returns a JSON dictionary containing information about the given ip.
572
Gilad Arnold1b908392012-10-05 11:36:27 -0700573 Args:
574 ip: address of host whose info is requested
Don Garrettf84631a2014-01-07 18:21:26 -0800575
Gilad Arnold1b908392012-10-05 11:36:27 -0700576 Returns:
577 A JSON dictionary containing all or some of the following fields:
578 last_event_type (int): last update event type received
579 last_event_status (int): last update event status received
580 last_known_version (string): last known version reported in update ping
581 forced_update_label (string): update label to force next update ping to
582 use, set by setnextupdate
583 See the OmahaEvent class in update_engine/omaha_request_action.h for
584 event type and status code definitions. If the ip does not exist an empty
585 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700586
Gilad Arnold1b908392012-10-05 11:36:27 -0700587 Example URL:
588 http://myhost/api/hostinfo?ip=192.168.1.5
589 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700590 return updater.HandleHostInfoPing(ip)
591
592 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800593 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700594 """Returns a JSON object containing a log of host event.
595
596 Args:
597 ip: address of host whose event log is requested, or `all'
Don Garrettf84631a2014-01-07 18:21:26 -0800598
Gilad Arnold1b908392012-10-05 11:36:27 -0700599 Returns:
600 A JSON encoded list (log) of dictionaries (events), each of which
601 containing a `timestamp' and other event fields, as described under
602 /api/hostinfo.
603
604 Example URL:
605 http://myhost/api/hostlog?ip=192.168.1.5
606 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800607 return updater.HandleHostLogPing(ip)
608
609 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700610 def setnextupdate(self, ip):
611 """Allows the response to the next update ping from a host to be set.
612
613 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 11:36:27 -0700614 /update command.
615 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700616 body_length = int(cherrypy.request.headers['Content-Length'])
617 label = cherrypy.request.rfile.read(body_length)
618
619 if label:
620 label = label.strip()
621 if label:
622 return updater.HandleSetUpdatePing(ip, label)
Chris Sosa4b951602014-04-09 20:26:07 -0700623 raise common_util.DevServerHTTPError(400, 'No label provided.')
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700624
625
Gilad Arnold55a2a372012-10-02 09:46:32 -0700626 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800627 def fileinfo(self, *args):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700628 """Returns information about a given staged file.
629
630 Args:
Don Garrettf84631a2014-01-07 18:21:26 -0800631 args: path to the file inside the server's static staging directory
632
Gilad Arnold55a2a372012-10-02 09:46:32 -0700633 Returns:
634 A JSON encoded dictionary with information about the said file, which may
635 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700636 size (int): the file size in bytes
637 sha1 (string): a base64 encoded SHA1 hash
638 sha256 (string): a base64 encoded SHA256 hash
639
640 Example URL:
641 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700642 """
Don Garrettf84631a2014-01-07 18:21:26 -0800643 file_path = os.path.join(updater.static_dir, *args)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700644 if not os.path.exists(file_path):
645 raise DevServerError('file not found: %s' % file_path)
646 try:
647 file_size = os.path.getsize(file_path)
648 file_sha1 = common_util.GetFileSha1(file_path)
649 file_sha256 = common_util.GetFileSha256(file_path)
650 except os.error, e:
651 raise DevServerError('failed to get info for file %s: %s' %
Gilad Arnolde74b3812013-04-22 11:27:38 -0700652 (file_path, e))
653
654 is_delta = autoupdate.Autoupdate.IsDeltaFormatFile(file_path)
655
656 return json.dumps({
657 autoupdate.Autoupdate.SIZE_ATTR: file_size,
658 autoupdate.Autoupdate.SHA1_ATTR: file_sha1,
659 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
660 autoupdate.Autoupdate.ISDELTA_ATTR: is_delta
661 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700662
Chris Sosa76e44b92013-01-31 12:11:38 -0800663
David Rochberg7c79a812011-01-19 14:24:45 -0500664class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700665 """The Root Class for the Dev Server.
666
667 CherryPy works as follows:
668 For each method in this class, cherrpy interprets root/path
669 as a call to an instance of DevServerRoot->method_name. For example,
670 a call to http://myhost/build will call build. CherryPy automatically
671 parses http args and places them as keyword arguments in each method.
672 For paths http://myhost/update/dir1/dir2, you can use *args so that
673 cherrypy uses the update method and puts the extra paths in args.
674 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700675 # Method names that should not be listed on the index page.
676 _UNLISTED_METHODS = ['index', 'doc']
677
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700678 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700679
Dan Shi59ae7092013-06-04 14:37:27 -0700680 # Number of threads that devserver is staging images.
681 _staging_thread_count = 0
682 # Lock used to lock increasing/decreasing count.
683 _staging_thread_count_lock = threading.Lock()
684
Dan Shiafd0e492015-05-27 14:23:51 -0700685 @require_psutil()
686 def _refresh_io_stats(self):
687 """A call running in a thread to update IO stats periodically."""
688 prev_disk_io_counters = psutil.disk_io_counters()
689 prev_network_io_counters = psutil.net_io_counters()
690 prev_read_time = time.time()
691 while True:
692 time.sleep(STATS_INTERVAL)
693 now = time.time()
694 interval = now - prev_read_time
695 prev_read_time = now
696 # Disk IO is for all disks.
697 disk_io_counters = psutil.disk_io_counters()
698 network_io_counters = psutil.net_io_counters()
699
700 self.disk_read_bytes_per_sec = (
701 disk_io_counters.read_bytes -
702 prev_disk_io_counters.read_bytes)/interval
703 self.disk_write_bytes_per_sec = (
704 disk_io_counters.write_bytes -
705 prev_disk_io_counters.write_bytes)/interval
706 prev_disk_io_counters = disk_io_counters
707
708 self.network_sent_bytes_per_sec = (
709 network_io_counters.bytes_sent -
710 prev_network_io_counters.bytes_sent)/interval
711 self.network_recv_bytes_per_sec = (
712 network_io_counters.bytes_recv -
713 prev_network_io_counters.bytes_recv)/interval
714 prev_network_io_counters = network_io_counters
715
716 @require_psutil()
717 def _start_io_stat_thread(self):
Gabe Black3b567202015-09-23 14:07:59 -0700718 """Start the thread to collect IO stats."""
Dan Shiafd0e492015-05-27 14:23:51 -0700719 thread = threading.Thread(target=self._refresh_io_stats)
720 thread.daemon = True
721 thread.start()
722
joychen3cb228e2013-06-12 12:13:13 -0700723 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700724 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800725 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700726 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500727
Dan Shiafd0e492015-05-27 14:23:51 -0700728 # Cache of disk IO stats, a thread refresh the stats every 10 seconds.
729 # lock is not used for these variables as the only thread writes to these
730 # variables is _refresh_io_stats.
731 self.disk_read_bytes_per_sec = 0
732 self.disk_write_bytes_per_sec = 0
733 # Cache of network IO stats.
734 self.network_sent_bytes_per_sec = 0
735 self.network_recv_bytes_per_sec = 0
736 self._start_io_stat_thread()
737
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700738 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500739 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700740 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700741 import builder
742 if self._builder is None:
743 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500744 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700745
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700746 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700747 def is_staged(self, **kwargs):
748 """Check if artifacts have been downloaded.
749
Chris Sosa6b0c6172013-08-05 17:01:33 -0700750 async: True to return without waiting for download to complete.
751 artifacts: Comma separated list of named artifacts to download.
752 These are defined in artifact_info and have their implementation
753 in build_artifact.py.
754 files: Comma separated list of file artifacts to stage. These
755 will be available as is in the corresponding static directory with no
756 custom post-processing.
757
758 returns: True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700759
760 Example:
761 To check if autotest and test_suites are staged:
762 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
763 artifacts=autotest,test_suites
764 """
Gabe Black3b567202015-09-23 14:07:59 -0700765 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700766 response = str(dl.IsStaged(factory))
767 _Log('Responding to is_staged %s request with %r', kwargs, response)
768 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700769
Chris Sosa76e44b92013-01-31 12:11:38 -0800770 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800771 def list_image_dir(self, **kwargs):
772 """Take an archive url and list the contents in its staged directory.
773
774 Args:
775 kwargs:
776 archive_url: Google Storage URL for the build.
777
778 Example:
779 To list the contents of where this devserver should have staged
780 gs://image-archive/<board>-release/<build> call:
781 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
782
783 Returns:
784 A string with information about the contents of the image directory.
785 """
Gabe Black3b567202015-09-23 14:07:59 -0700786 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800787 try:
Gabe Black3b567202015-09-23 14:07:59 -0700788 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800789 except build_artifact.ArtifactDownloadError as e:
790 return 'Cannot list the contents of staged artifacts. %s' % e
791 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700792 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800793 return image_dir_contents
794
795 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800796 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700797 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800798
Gabe Black3b567202015-09-23 14:07:59 -0700799 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700800 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700801 on the devserver. A call to this will attempt to cache non-specified
802 artifacts in the background for the given from the given URL following
803 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800804 artifacts is explicitly defined in the build_artifact module.
805
806 These artifacts will then be available from the static/ sub-directory of
807 the devserver.
808
809 Args:
810 archive_url: Google Storage URL for the build.
Simran Basi4243a862014-12-12 12:48:33 -0800811 local_path: Local path for the build.
Dan Shif8eb0d12013-08-01 17:52:06 -0700812 async: True to return without waiting for download to complete.
Chris Sosa6b0c6172013-08-05 17:01:33 -0700813 artifacts: Comma separated list of named artifacts to download.
814 These are defined in artifact_info and have their implementation
815 in build_artifact.py.
816 files: Comma separated list of files to stage. These
817 will be available as is in the corresponding static directory with no
818 custom post-processing.
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800819 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800820
821 Example:
822 To download the autotest and test suites tarballs:
823 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
824 artifacts=autotest,test_suites
825 To download the full update payload:
826 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
827 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700828 To download just a file called blah.bin:
829 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
830 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800831
832 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700833 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800834
835 Note for this example, relative path is the archive_url stripped of its
836 basename i.e. path/ in the examples above. Specific example:
837
838 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
839
840 Will get staged to:
841
joychened64b222013-06-21 16:39:34 -0700842 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Chris Sosa76e44b92013-01-31 12:11:38 -0800843 """
Gabe Black3b567202015-09-23 14:07:59 -0700844 dl, factory = _get_downloader_and_factory(kwargs)
845
Dan Shi59ae7092013-06-04 14:37:27 -0700846 with DevServerRoot._staging_thread_count_lock:
847 DevServerRoot._staging_thread_count += 1
848 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800849 boolean_string = kwargs.get('clean')
850 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
851 if clean and os.path.exists(dl.GetBuildDir()):
852 _Log('Removing %s' % dl.GetBuildDir())
853 shutil.rmtree(dl.GetBuildDir())
Gabe Black3b567202015-09-23 14:07:59 -0700854 async = kwargs.get('async', False)
855 dl.Download(factory, async=async)
Dan Shi59ae7092013-06-04 14:37:27 -0700856 finally:
857 with DevServerRoot._staging_thread_count_lock:
858 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800859 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700860
861 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700862 def cros_au(self, **kwargs):
863 """Auto-update a CrOS DUT.
864
865 Args:
866 kwargs:
867 host_name: the hostname of the DUT to auto-update.
868 build_name: the build name for update the DUT.
869 force_update: Force an update even if the version installed is the
870 same. Default: False.
871 full_update: If True, do not run stateful update, directly force a full
872 reimage. If False, try stateful update first if the dut is already
873 installed with the same version.
874 async: Whether the auto_update function is ran in the background.
875
876 Returns:
877 A tuple includes two elements:
878 a boolean variable represents whether the auto-update process is
879 successfully started.
880 an integer represents the background auto-update process id.
881 """
882 _check_base_args_for_auto_update(kwargs)
883
884 host_name = kwargs['host_name']
885 build_name = kwargs['build_name']
886 force_update = _parse_boolean_arg(kwargs, 'force_update')
887 full_update = _parse_boolean_arg(kwargs, 'full_update')
888 async = _parse_boolean_arg(kwargs, 'async')
xixuanac89ce82016-11-30 16:48:20 -0800889 original_build = _parse_string_arg(kwargs, 'original_build')
xixuan52c2fba2016-05-20 17:02:48 -0700890
891 if async:
892 path = os.path.dirname(os.path.abspath(__file__))
893 execute_file = os.path.join(path, 'cros_update.py')
894 args = (AUTO_UPDATE_CMD % (execute_file, host_name, build_name,
895 updater.static_dir))
xixuanac89ce82016-11-30 16:48:20 -0800896
897 # The original_build's format is like: link/3428.210.0
898 # The corresponding release_archive_url's format is like:
899 # gs://chromeos-releases/stable-channel/link/3428.210.0
900 if original_build:
901 release_archive_url = _build_uri_from_build_name(original_build)
902 # First staging the stateful.tgz synchronousely.
903 self.stage(files='stateful.tgz', async=False,
904 archive_url=release_archive_url)
905 args = ('%s --original_build %s' % (args, original_build))
906
xixuan52c2fba2016-05-20 17:02:48 -0700907 if force_update:
908 args = ('%s --force_update' % args)
909
910 if full_update:
911 args = ('%s --full_update' % args)
912
xixuan2a0970a2016-08-10 12:12:44 -0700913 p = subprocess.Popen([args], shell=True, preexec_fn=os.setsid)
914 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-20 17:02:48 -0700915
916 # Pre-write status in the track_status_file before the first call of
917 # 'get_au_status' to make sure that the track_status_file exists.
xixuan2a0970a2016-08-10 12:12:44 -0700918 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700919 progress_tracker.WriteStatus('CrOS update is just started.')
920
xixuan2a0970a2016-08-10 12:12:44 -0700921 return json.dumps((True, pid))
xixuan52c2fba2016-05-20 17:02:48 -0700922 else:
923 cros_update_trigger = cros_update.CrOSUpdateTrigger(
xixuanac89ce82016-11-30 16:48:20 -0800924 host_name, build_name, updater.static_dir, force_update=force_update,
925 full_update=full_update, original_build=original_build)
xixuan52c2fba2016-05-20 17:02:48 -0700926 cros_update_trigger.TriggerAU()
927
928 @cherrypy.expose
929 def get_au_status(self, **kwargs):
930 """Check if the auto-update task is finished.
931
932 It handles 4 cases:
933 1. If an error exists in the track_status_file, delete the track file and
934 raise it.
935 2. If cros-update process is finished, delete the file and return the
936 success result.
937 3. If the process is not running, delete the track file and raise an error
938 about 'the process is terminated due to unknown reason'.
939 4. If the track_status_file does not exist, kill the process if it exists,
940 and raise the IOError.
941
942 Args:
943 kwargs:
944 host_name: the hostname of the DUT to auto-update.
945 pid: the background process id of cros-update.
946
947 Returns:
xixuan28d99072016-10-06 12:24:16 -0700948 A dict with three elements:
xixuan52c2fba2016-05-20 17:02:48 -0700949 a boolean variable represents whether the auto-update process is
950 finished.
951 a string represents the current auto-update process status.
952 For example, 'Transfer Devserver/Stateful Update Package'.
xixuan28d99072016-10-06 12:24:16 -0700953 a detailed error message paragraph if there exists an Auto-Update
954 error, in which the last line shows the main exception. Empty
955 string otherwise.
xixuan52c2fba2016-05-20 17:02:48 -0700956 """
957 if 'host_name' not in kwargs:
958 raise common_util.DevServerHTTPError((KEY_ERROR_MSG % 'host_name'))
959
960 if 'pid' not in kwargs:
961 raise common_util.DevServerHTTPError((KEY_ERROR_MSG % 'pid'))
962
963 host_name = kwargs['host_name']
964 pid = kwargs['pid']
965 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
966
xixuan28d99072016-10-06 12:24:16 -0700967 result_dict = {'finished': False, 'status': '', 'detailed_error_msg': ''}
xixuan52c2fba2016-05-20 17:02:48 -0700968 try:
969 result = progress_tracker.ReadStatus()
970 if result.startswith(cros_update_progress.ERROR_TAG):
xixuan28d99072016-10-06 12:24:16 -0700971 result_dict['detailed_error_msg'] = result[len(
972 cros_update_progress.ERROR_TAG):]
xixuan28681fd2016-11-23 11:13:56 -0800973 elif result == cros_update_progress.FINISHED:
xixuan28d99072016-10-06 12:24:16 -0700974 result_dict['finished'] = True
975 result_dict['status'] = result
xixuan28681fd2016-11-23 11:13:56 -0800976 elif not cros_update_progress.IsProcessAlive(pid):
xixuan28d99072016-10-06 12:24:16 -0700977 result_dict['detailed_error_msg'] = (
978 'Cros_update process terminated midway due to unknown reason. '
979 'Last update status was %s' % result)
xixuan28681fd2016-11-23 11:13:56 -0800980 else:
981 result_dict['status'] = result
982 except IOError as e:
983 if pid and cros_update_progress.IsProcessAlive(pid):
xixuan2a0970a2016-08-10 12:12:44 -0700984 os.killpg(int(pid), signal.SIGKILL)
xixuan52c2fba2016-05-20 17:02:48 -0700985
xixuan28681fd2016-11-23 11:13:56 -0800986 result_dict['detailed_error_msg'] = str(e)
987
988 return json.dumps(result_dict)
xixuan52c2fba2016-05-20 17:02:48 -0700989
990 @cherrypy.expose
991 def handler_cleanup(self, **kwargs):
xixuan3bc974e2016-10-18 17:21:43 -0700992 """Clean track status log and temp directory for CrOS auto-update process.
xixuan52c2fba2016-05-20 17:02:48 -0700993
994 Args:
995 kwargs:
996 host_name: the hostname of the DUT to auto-update.
997 pid: the background process id of cros-update.
998 """
999 if 'host_name' not in kwargs:
1000 raise common_util.DevServerHTTPError((KEY_ERROR_MSG % 'host_name'))
1001
1002 if 'pid' not in kwargs:
1003 raise common_util.DevServerHTTPError((KEY_ERROR_MSG % 'pid'))
1004
1005 host_name = kwargs['host_name']
1006 pid = kwargs['pid']
1007 cros_update_progress.DelTrackStatusFile(host_name, pid)
xixuan3bc974e2016-10-18 17:21:43 -07001008 cros_update_progress.DelAUTempDirectory(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -07001009
1010 @cherrypy.expose
1011 def kill_au_proc(self, **kwargs):
1012 """Kill CrOS auto-update process using given process id.
1013
1014 Args:
1015 kwargs:
1016 host_name: Kill all the CrOS auto-update process of this host.
1017
1018 Returns:
1019 True if all processes are killed properly.
1020 """
1021 if 'host_name' not in kwargs:
1022 raise common_util.DevServerHTTPError((KEY_ERROR_MSG % 'host_name'))
1023
1024 host_name = kwargs['host_name']
xixuan3bc974e2016-10-18 17:21:43 -07001025 track_log_list = cros_update_progress.GetAllTrackStatusFileByHostName(
1026 host_name)
xixuan52c2fba2016-05-20 17:02:48 -07001027 for log in track_log_list:
1028 # The track log's full path is: path/host_name_pid.log
1029 # Use splitext to remove file extension, then parse pid from the
1030 # filename.
1031 pid = os.path.splitext(os.path.basename(log))[0][len(host_name)+1:]
1032 if cros_update_progress.IsProcessAlive(pid):
xixuan2a0970a2016-08-10 12:12:44 -07001033 os.killpg(int(pid), signal.SIGKILL)
xixuan52c2fba2016-05-20 17:02:48 -07001034
1035 cros_update_progress.DelTrackStatusFile(host_name, pid)
xixuan1bbfaba2016-10-13 17:53:22 -07001036 cros_update_progress.DelExecuteLogFile(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -07001037
1038 return 'True'
1039
1040 @cherrypy.expose
1041 def collect_cros_au_log(self, **kwargs):
1042 """Collect CrOS auto-update log.
1043
1044 Args:
1045 kwargs:
1046 host_name: the hostname of the DUT to auto-update.
1047 pid: the background process id of cros-update.
1048
1049 Returns:
1050 A string contains the whole content of the execute log file.
1051 """
1052 if 'host_name' not in kwargs:
1053 raise common_util.DevServerHTTPError((KEY_ERROR_MSG % 'host_name'))
1054
1055 if 'pid' not in kwargs:
1056 raise common_util.DevServerHTTPError((KEY_ERROR_MSG % 'pid'))
1057
1058 host_name = kwargs['host_name']
1059 pid = kwargs['pid']
xixuan3bc974e2016-10-18 17:21:43 -07001060
1061 # Fetch the execute log recorded by cros_update_progress.
xixuan1bbfaba2016-10-13 17:53:22 -07001062 au_log = cros_update_progress.ReadExecuteLogFile(host_name, pid)
1063 cros_update_progress.DelExecuteLogFile(host_name, pid)
1064 return au_log
1065
xixuan52c2fba2016-05-20 17:02:48 -07001066 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -08001067 def locate_file(self, **kwargs):
1068 """Get the path to the given file name.
1069
1070 This method looks up the given file name inside specified build artifacts.
1071 One use case is to help caller to locate an apk file inside a build
1072 artifact. The location of the apk file could be different based on the
1073 branch and target.
1074
1075 Args:
1076 file_name: Name of the file to look for.
1077 artifacts: A list of artifact names to search for the file.
1078
1079 Returns:
1080 Path to the file with the given name. It's relative to the folder for the
1081 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -08001082 """
1083 dl, _ = _get_downloader_and_factory(kwargs)
1084 try:
1085 file_name = kwargs['file_name'].lower()
1086 artifacts = kwargs['artifacts']
1087 except KeyError:
1088 raise DevServerError('`file_name` and `artifacts` are required to search '
1089 'for a file in build artifacts.')
1090 build_path = dl.GetBuildDir()
1091 for artifact in artifacts:
1092 # Get the unzipped folder of the artifact. If it's not defined in
1093 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
1094 # directory directly.
1095 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
1096 artifact_path = os.path.join(build_path, folder)
1097 for root, _, filenames in os.walk(artifact_path):
1098 if file_name in set([f.lower() for f in filenames]):
1099 return os.path.relpath(os.path.join(root, file_name), build_path)
1100 raise DevServerError('File `%s` can not be found in artifacts: %s' %
1101 (file_name, artifacts))
1102
1103 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -08001104 def setup_telemetry(self, **kwargs):
1105 """Extracts and sets up telemetry
1106
1107 This method goes through the telemetry deps packages, and stages them on
1108 the devserver to be used by the drones and the telemetry tests.
1109
1110 Args:
1111 archive_url: Google Storage URL for the build.
1112
1113 Returns:
1114 Path to the source folder for the telemetry codebase once it is staged.
1115 """
Gabe Black3b567202015-09-23 14:07:59 -07001116 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 13:39:18 -08001117
Gabe Black3b567202015-09-23 14:07:59 -07001118 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -08001119 deps_path = os.path.join(build_path, 'autotest/packages')
1120 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
1121 src_folder = os.path.join(telemetry_path, 'src')
1122
1123 with self._telemetry_lock_dict.lock(telemetry_path):
1124 if os.path.exists(src_folder):
1125 # Telemetry is already fully stage return
1126 return src_folder
1127
1128 common_util.MkDirP(telemetry_path)
1129
1130 # Copy over the required deps tar balls to the telemetry directory.
1131 for dep in TELEMETRY_DEPS:
1132 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -07001133 if not os.path.exists(dep_path):
1134 # This dep does not exist (could be new), do not extract it.
1135 continue
Simran Basi4baad082013-02-14 13:39:18 -08001136 try:
1137 common_util.ExtractTarball(dep_path, telemetry_path)
1138 except common_util.CommonUtilError as e:
1139 shutil.rmtree(telemetry_path)
1140 raise DevServerError(str(e))
1141
1142 # By default all the tarballs extract to test_src but some parts of
1143 # the telemetry code specifically hardcoded to exist inside of 'src'.
1144 test_src = os.path.join(telemetry_path, 'test_src')
1145 try:
1146 shutil.move(test_src, src_folder)
1147 except shutil.Error:
1148 # This can occur if src_folder already exists. Remove and retry move.
1149 shutil.rmtree(src_folder)
Gabe Black3b567202015-09-23 14:07:59 -07001150 raise DevServerError(
1151 'Failure in telemetry setup for build %s. Appears that the '
1152 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -08001153
1154 return src_folder
1155
1156 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -08001157 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -07001158 """Symbolicates a minidump using pre-downloaded symbols, returns it.
1159
1160 Callers will need to POST to this URL with a body of MIME-type
1161 "multipart/form-data".
1162 The body should include a single argument, 'minidump', containing the
1163 binary-formatted minidump to symbolicate.
1164
Chris Masone816e38c2012-05-02 12:22:36 -07001165 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -08001166 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -07001167 minidump: The binary minidump file to symbolicate.
1168 """
Chris Sosa76e44b92013-01-31 12:11:38 -08001169 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -07001170 # Try debug.tar.xz first, then debug.tgz
1171 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
1172 kwargs['artifacts'] = artifact
1173 dl = _get_downloader(kwargs)
1174
1175 try:
1176 if self.stage(**kwargs) == 'Success':
1177 break
1178 except build_artifact.ArtifactDownloadError:
1179 continue
1180 else:
Gabe Black3b567202015-09-23 14:07:59 -07001181 raise DevServerError('Failed to stage symbols for %s' %
1182 dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -08001183
Chris Masone816e38c2012-05-02 12:22:36 -07001184 to_return = ''
1185 with tempfile.NamedTemporaryFile() as local:
1186 while True:
1187 data = minidump.file.read(8192)
1188 if not data:
1189 break
1190 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -08001191
Chris Masone816e38c2012-05-02 12:22:36 -07001192 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -08001193
Gabe Black3b567202015-09-23 14:07:59 -07001194 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -08001195
1196 stackwalk = subprocess.Popen(
1197 ['minidump_stackwalk', local.name, symbols_directory],
1198 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1199
Chris Masone816e38c2012-05-02 12:22:36 -07001200 to_return, error_text = stackwalk.communicate()
1201 if stackwalk.returncode != 0:
1202 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
1203 error_text, stackwalk.returncode))
1204
1205 return to_return
1206
1207 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001208 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -04001209 """Return a string representing the latest build for a given target.
1210
1211 Args:
1212 target: The build target, typically a combination of the board and the
1213 type of build e.g. x86-mario-release.
1214 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
1215 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -08001216
Scott Zawalski16954532012-03-20 15:31:36 -04001217 Returns:
1218 A string representation of the latest build if one exists, i.e.
1219 R19-1993.0.0-a1-b1480.
1220 An empty string if no latest could be found.
1221 """
Don Garrettf84631a2014-01-07 18:21:26 -08001222 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -04001223 return _PrintDocStringAsHTML(self.latestbuild)
1224
Don Garrettf84631a2014-01-07 18:21:26 -08001225 if 'target' not in kwargs:
Chris Sosa4b951602014-04-09 20:26:07 -07001226 raise common_util.DevServerHTTPError(500, 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -07001227
1228 if _is_android_build_request(kwargs):
1229 branch = kwargs.get('branch', None)
1230 target = kwargs.get('target', None)
1231 if not target or not branch:
1232 raise DevServerError(
xixuan52c2fba2016-05-20 17:02:48 -07001233 'Both target and branch must be specified to query for the latest '
1234 'Android build.')
Dan Shi61305df2015-10-26 16:52:35 -07001235 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
1236
Scott Zawalski16954532012-03-20 15:31:36 -04001237 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001238 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-07 18:21:26 -08001239 updater.static_dir, kwargs['target'],
1240 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -07001241 except common_util.CommonUtilError as errmsg:
Chris Sosa4b951602014-04-09 20:26:07 -07001242 raise common_util.DevServerHTTPError(500, str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -04001243
1244 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -07001245 def list_suite_controls(self, **kwargs):
1246 """Return a list of contents of all known control files.
1247
1248 Example URL:
1249 To List all control files' content:
1250 http://dev-server/list_suite_controls?suite_name=bvt&
1251 build=daisy_spring-release/R29-4279.0.0
1252
1253 Args:
1254 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
1255 suite_name: List the control files belonging to that suite.
1256
1257 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -07001258 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -07001259 """
1260 if not kwargs:
1261 return _PrintDocStringAsHTML(self.controlfiles)
1262
1263 if 'build' not in kwargs:
1264 raise common_util.DevServerHTTPError(500, 'Error: build= is required!')
1265
1266 if 'suite_name' not in kwargs:
Dan Shia1cd6522016-04-18 16:07:21 -07001267 raise common_util.DevServerHTTPError(500,
1268 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001269
1270 control_file_list = [
1271 line.rstrip() for line in common_util.GetControlFileListForSuite(
1272 updater.static_dir, kwargs['build'],
1273 kwargs['suite_name']).splitlines()]
1274
Dan Shia1cd6522016-04-18 16:07:21 -07001275 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -07001276 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -07001277 control_file_content_dict[control_path] = (common_util.GetControlFile(
xixuan7efd0002016-04-14 15:34:01 -07001278 updater.static_dir, kwargs['build'], control_path))
1279
Dan Shia1cd6522016-04-18 16:07:21 -07001280 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -07001281
1282 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001283 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -05001284 """Return a control file or a list of all known control files.
1285
1286 Example URL:
1287 To List all control files:
beepsbd337242013-07-09 22:44:06 -07001288 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
1289 To List all control files for, say, the bvt suite:
1290 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -05001291 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001292 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 -05001293
1294 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001295 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -05001296 control_path: If you want the contents of a control file set this
1297 to the path. E.g. client/site_tests/sleeptest/control
1298 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -07001299 suite_name: If control_path is not specified but a suite_name is
1300 specified, list the control files belonging to that suite instead of
1301 all control files. The empty string for suite_name will list all control
1302 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -08001303
Scott Zawalski4647ce62012-01-03 17:17:28 -05001304 Returns:
1305 Contents of a control file if control_path is provided.
1306 A list of control files if no control_path is provided.
1307 """
Don Garrettf84631a2014-01-07 18:21:26 -08001308 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -05001309 return _PrintDocStringAsHTML(self.controlfiles)
1310
Don Garrettf84631a2014-01-07 18:21:26 -08001311 if 'build' not in kwargs:
Chris Sosa4b951602014-04-09 20:26:07 -07001312 raise common_util.DevServerHTTPError(500, 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -05001313
Don Garrettf84631a2014-01-07 18:21:26 -08001314 if 'control_path' not in kwargs:
1315 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -07001316 return common_util.GetControlFileListForSuite(
Don Garrettf84631a2014-01-07 18:21:26 -08001317 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -07001318 else:
1319 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-07 18:21:26 -08001320 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -05001321 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001322 return common_util.GetControlFile(
Don Garrettf84631a2014-01-07 18:21:26 -08001323 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -08001324
1325 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -07001326 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001327 """Translates an xBuddy path to a real path to artifact if it exists.
1328
1329 Args:
Simran Basi99e63c02014-05-20 10:39:52 -07001330 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
1331 Local searches the devserver's static directory. Remote searches a
1332 Google Storage image archive.
1333
1334 Kwargs:
1335 image_dir: Google Storage image archive to search in if requesting a
1336 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001337
1338 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -07001339 String in the format of build_id/artifact as stored on the local server
1340 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001341 """
Simran Basi99e63c02014-05-20 10:39:52 -07001342 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -07001343 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001344 response = os.path.join(build_id, filename)
1345 _Log('Path translation requested, returning: %s', response)
1346 return response
1347
1348 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -07001349 def xbuddy(self, *args, **kwargs):
1350 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -07001351
1352 Args:
joycheneaf4cfc2013-07-02 08:38:57 -07001353 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -07001354 components of the path. The path can be understood as
1355 "{local|remote}/build_id/artifact" where build_id is composed of
1356 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -07001357
joychen121fc9b2013-08-02 14:30:30 -07001358 The first path element is optional, and can be "remote" or "local"
1359 If local (the default), devserver will not attempt to access Google
1360 Storage, and will only search the static directory for the files.
1361 If remote, devserver will try to obtain the artifact off GS if it's
1362 not found locally.
1363 The board is the familiar board name, optionally suffixed.
1364 The version can be the google storage version number, and may also be
1365 any of a number of xBuddy defined version aliases that will be
1366 translated into the latest built image that fits the description.
1367 Defaults to latest.
1368 The artifact is one of a number of image or artifact aliases used by
1369 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -07001370
1371 Kwargs:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001372 for_update: {true|false}
1373 if true, pregenerates the update payloads for the image,
1374 and returns the update uri to pass to the
1375 update_engine_client.
joychen3cb228e2013-06-12 12:13:13 -07001376 return_dir: {true|false}
1377 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001378 relative_path: {true|false}
1379 if set to true, returns the relative path to the payload
1380 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -07001381 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -07001382 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -07001383 or
joycheneaf4cfc2013-07-02 08:38:57 -07001384 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -07001385
1386 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001387 If |for_update|, returns a redirect to the image or update file
1388 on the devserver. E.g.,
1389 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
1390 chromium-test-image.bin
1391 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1392 http://host:port/static/x86-generic-release/R26-4000.0.0/
1393 If |relative_path| is true, return a relative path the folder where the
1394 payloads are. E.g.,
1395 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -07001396 """
Chris Sosa75490802013-09-30 17:21:45 -07001397 boolean_string = kwargs.get('for_update')
1398 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001399 boolean_string = kwargs.get('return_dir')
1400 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1401 boolean_string = kwargs.get('relative_path')
1402 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001403
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001404 if return_dir and relative_path:
Chris Sosa4b951602014-04-09 20:26:07 -07001405 raise common_util.DevServerHTTPError(
1406 500, 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001407
1408 # For updates, we optimize downloading of test images.
1409 file_name = None
1410 build_id = None
1411 if for_update:
1412 try:
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001413 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-09-30 17:21:45 -07001414 except build_artifact.ArtifactDownloadError:
1415 build_id = None
1416
1417 if not build_id:
1418 build_id, file_name = self._xbuddy.Get(args)
1419
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001420 if for_update:
1421 _Log('Payload generation triggered by request')
1422 # Forces payload to be in cache and symlinked into build_id dir.
Chris Sosa75490802013-09-30 17:21:45 -07001423 updater.GetUpdateForLabel(autoupdate.FORCED_UPDATE, build_id,
1424 image_name=file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001425
1426 response = None
1427 if return_dir:
1428 response = os.path.join(cherrypy.request.base, 'static', build_id)
1429 _Log('Directory requested, returning: %s', response)
1430 elif relative_path:
1431 response = build_id
1432 _Log('Relative path requested, returning: %s', response)
1433 elif for_update:
1434 response = os.path.join(cherrypy.request.base, 'update', build_id)
1435 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001436 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001437 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001438 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001439 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001440 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001441
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001442 return response
1443
joychen3cb228e2013-06-12 12:13:13 -07001444 @cherrypy.expose
1445 def xbuddy_list(self):
1446 """Lists the currently available images & time since last access.
1447
Gilad Arnold452fd272014-02-04 11:09:28 -08001448 Returns:
1449 A string representation of a list of tuples [(build_id, time since last
1450 access),...]
joychen3cb228e2013-06-12 12:13:13 -07001451 """
1452 return self._xbuddy.List()
1453
1454 @cherrypy.expose
1455 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001456 """Returns the number of images cached by xBuddy."""
joychen3cb228e2013-06-12 12:13:13 -07001457 return self._xbuddy.Capacity()
1458
1459 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001460 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001461 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001462 return ('Welcome to the Dev Server!<br>\n'
1463 '<br>\n'
1464 'Here are the available methods, click for documentation:<br>\n'
1465 '<br>\n'
1466 '%s' %
1467 '<br>\n'.join(
1468 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001469 for name in _FindExposedMethods(
1470 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001471
1472 @cherrypy.expose
1473 def doc(self, *args):
1474 """Shows the documentation for available methods / URLs.
1475
1476 Example:
1477 http://myhost/doc/update
1478 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001479 name = '/'.join(args)
1480 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001481 if not method:
1482 raise DevServerError("No exposed method named `%s'" % name)
1483 if not method.__doc__:
1484 raise DevServerError("No documentation for exposed method `%s'" % name)
1485 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001486
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07001487 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001488 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001489 """Handles an update check from a Chrome OS client.
1490
1491 The HTTP request should contain the standard Omaha-style XML blob. The URL
1492 line may contain an additional intermediate path to the update payload.
1493
joychen121fc9b2013-08-02 14:30:30 -07001494 This request can be handled in one of 4 ways, depending on the devsever
1495 settings and intermediate path.
joychenb0dfe552013-07-30 10:02:06 -07001496
joychen121fc9b2013-08-02 14:30:30 -07001497 1. No intermediate path
1498 If no intermediate path is given, the default behavior is to generate an
1499 update payload from the latest test image locally built for the board
1500 specified in the xml. Devserver serves the generated payload.
1501
1502 2. Path explicitly invokes XBuddy
1503 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1504 with 'xbuddy'. This path is then used to acquire an image binary for the
1505 devserver to generate an update payload from. Devserver then serves this
1506 payload.
1507
1508 3. Path is left for the devserver to interpret.
1509 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1510 to generate a payload from the test image in that directory and serve it.
1511
1512 4. The devserver is in a 'forced' mode. TO BE DEPRECATED
1513 This comes from the usage of --forced_payload or --image when starting the
1514 devserver. No matter what path (or no path) gets passed in, devserver will
1515 serve the update payload (--forced_payload) or generate an update payload
1516 from the image (--image).
1517
1518 Examples:
1519 1. No intermediate path
1520 update_engine_client --omaha_url=http://myhost/update
1521 This generates an update payload from the latest test image locally built
1522 for the board specified in the xml.
1523
1524 2. Explicitly invoke xbuddy
1525 update_engine_client --omaha_url=
1526 http://myhost/update/xbuddy/remote/board/version/dev
1527 This would go to GS to download the dev image for the board, from which
1528 the devserver would generate a payload to serve.
1529
1530 3. Give a path for devserver to interpret
1531 update_engine_client --omaha_url=http://myhost/update/some/random/path
1532 This would attempt, in order to:
1533 a) Generate an update from a test image binary if found in
1534 static_dir/some/random/path.
1535 b) Serve an update payload found in static_dir/some/random/path.
1536 c) Hope that some/random/path takes the form "board/version" and
1537 and attempt to download an update payload for that board/version
1538 from GS.
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001539 """
joychen121fc9b2013-08-02 14:30:30 -07001540 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -08001541 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -07001542 data = cherrypy.request.rfile.read(body_length)
Chris Sosa7c931362010-10-11 19:49:01 -07001543
joychen121fc9b2013-08-02 14:30:30 -07001544 return updater.HandleUpdatePing(data, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001545
Dan Shiafd0e492015-05-27 14:23:51 -07001546 @require_psutil()
1547 def _get_io_stats(self):
1548 """Get the IO stats as a dictionary.
1549
Gabe Black3b567202015-09-23 14:07:59 -07001550 Returns:
1551 A dictionary of IO stats collected by psutil.
Dan Shiafd0e492015-05-27 14:23:51 -07001552 """
1553 return {'disk_read_bytes_per_second': self.disk_read_bytes_per_sec,
1554 'disk_write_bytes_per_second': self.disk_write_bytes_per_sec,
1555 'disk_total_bytes_per_second': (self.disk_read_bytes_per_sec +
1556 self.disk_write_bytes_per_sec),
1557 'network_sent_bytes_per_second': self.network_sent_bytes_per_sec,
1558 'network_recv_bytes_per_second': self.network_recv_bytes_per_sec,
1559 'network_total_bytes_per_second': (self.network_sent_bytes_per_sec +
1560 self.network_recv_bytes_per_sec),
1561 'cpu_percent': psutil.cpu_percent(),}
1562
Dan Shi7247f9c2016-06-01 09:19:09 -07001563
1564 def _get_process_count(self, process_cmd_pattern):
1565 """Get the count of processes that match the given command pattern.
1566
1567 Args:
1568 process_cmd_pattern: The regex pattern of process command to match.
1569
1570 Returns:
1571 The count of processes that match the given command pattern.
1572 """
1573 try:
xixuanac89ce82016-11-30 16:48:20 -08001574 # Use Popen instead of check_output since the latter cannot run with old
1575 # python version (less than 2.7)
1576 proc = subprocess.Popen(
1577 'pgrep -fc "%s"' % process_cmd_pattern,
1578 stdout=subprocess.PIPE,
1579 stderr=subprocess.PIPE,
1580 shell=True)
1581 cmd_output, cmd_error = proc.communicate()
1582 if cmd_error:
1583 _Log('Error happened when getting process count: %s' % cmd_error)
1584
1585 return int(cmd_output)
Dan Shi7247f9c2016-06-01 09:19:09 -07001586 except subprocess.CalledProcessError:
1587 return 0
1588
1589
Dan Shif5ce2de2013-04-25 16:06:32 -07001590 @cherrypy.expose
1591 def check_health(self):
1592 """Collect the health status of devserver to see if it's ready for staging.
1593
Gilad Arnold452fd272014-02-04 11:09:28 -08001594 Returns:
1595 A JSON dictionary containing all or some of the following fields:
1596 free_disk (int): free disk space in GB
1597 staging_thread_count (int): number of devserver threads currently staging
1598 an image
Dan Shi7247f9c2016-06-01 09:19:09 -07001599 apache_client_count (int): count of Apache processes.
1600 telemetry_test_count (int): count of telemetry tests.
1601 gsutil_count (int): count of gsutil processes.
Dan Shif5ce2de2013-04-25 16:06:32 -07001602 """
1603 # Get free disk space.
1604 stat = os.statvfs(updater.static_dir)
1605 free_disk = stat.f_bsize * stat.f_bavail / 1000000000
Dan Shi7247f9c2016-06-01 09:19:09 -07001606 apache_client_count = self._get_process_count('apache')
1607 telemetry_test_count = self._get_process_count('python.*telemetry')
1608 gsutil_count = self._get_process_count('gsutil')
Dan Shif5ce2de2013-04-25 16:06:32 -07001609
Dan Shiafd0e492015-05-27 14:23:51 -07001610 health_data = {
Dan Shif5ce2de2013-04-25 16:06:32 -07001611 'free_disk': free_disk,
Dan Shid76e6bb2016-01-28 22:28:51 -08001612 'staging_thread_count': DevServerRoot._staging_thread_count,
1613 'apache_client_count': apache_client_count,
Dan Shi7247f9c2016-06-01 09:19:09 -07001614 'telemetry_test_count': telemetry_test_count,
1615 'gsutil_count': gsutil_count}
Dan Shiafd0e492015-05-27 14:23:51 -07001616 health_data.update(self._get_io_stats() or {})
1617
1618 return json.dumps(health_data)
Dan Shif5ce2de2013-04-25 16:06:32 -07001619
1620
Chris Sosadbc20082012-12-10 13:39:11 -08001621def _CleanCache(cache_dir, wipe):
1622 """Wipes any excess cached items in the cache_dir.
1623
1624 Args:
1625 cache_dir: the directory we are wiping from.
1626 wipe: If True, wipe all the contents -- not just the excess.
1627 """
1628 if wipe:
1629 # Clear the cache and exit on error.
1630 cmd = 'rm -rf %s/*' % cache_dir
1631 if os.system(cmd) != 0:
1632 _Log('Failed to clear the cache with %s' % cmd)
1633 sys.exit(1)
1634 else:
1635 # Clear all but the last N cached updates
1636 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1637 (cache_dir, CACHED_ENTRIES))
1638 if os.system(cmd) != 0:
1639 _Log('Failed to clean up old delta cache files with %s' % cmd)
1640 sys.exit(1)
1641
1642
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001643def _AddTestingOptions(parser):
1644 group = optparse.OptionGroup(
1645 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1646 'developers writing integration tests utilizing the devserver. They are '
1647 'not intended to be really used outside the scope of someone '
1648 'knowledgable about the test.')
1649 group.add_option('--exit',
1650 action='store_true',
1651 help='do not start the server (yet pregenerate/clear cache)')
1652 group.add_option('--host_log',
1653 action='store_true', default=False,
1654 help='record history of host update events (/api/hostlog)')
1655 group.add_option('--max_updates',
Gabe Black3b567202015-09-23 14:07:59 -07001656 metavar='NUM', default=-1, type='int',
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001657 help='maximum number of update checks handled positively '
1658 '(default: unlimited)')
1659 group.add_option('--private_key',
1660 metavar='PATH', default=None,
1661 help='path to the private key in pem format. If this is set '
1662 'the devserver will generate update payloads that are '
1663 'signed with this key.')
David Zeuthen52ccd012013-10-31 12:58:26 -07001664 group.add_option('--private_key_for_metadata_hash_signature',
1665 metavar='PATH', default=None,
1666 help='path to the private key in pem format. If this is set '
1667 'the devserver will sign the metadata hash with the given '
1668 'key and transmit in the Omaha-style XML response.')
1669 group.add_option('--public_key',
1670 metavar='PATH', default=None,
1671 help='path to the public key in pem format. If this is set '
1672 'the devserver will transmit a base64 encoded version of '
1673 'the content in the Omaha-style XML response.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001674 group.add_option('--proxy_port',
1675 metavar='PORT', default=None, type='int',
1676 help='port to have the client connect to -- basically the '
1677 'devserver lies to the update to tell it to get the payload '
1678 'from a different port that will proxy the request back to '
1679 'the devserver. The proxy must be managed outside the '
1680 'devserver.')
1681 group.add_option('--remote_payload',
1682 action='store_true', default=False,
Chris Sosa4b951602014-04-09 20:26:07 -07001683 help='Payload is being served from a remote machine. With '
1684 'this setting enabled, this devserver instance serves as '
1685 'just an Omaha server instance. In this mode, the '
1686 'devserver enforces a few extra components of the Omaha '
Chris Sosafc715442014-04-09 20:45:23 -07001687 'protocol, such as hardware class, being sent.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001688 group.add_option('-u', '--urlbase',
1689 metavar='URL',
Gabe Black3b567202015-09-23 14:07:59 -07001690 help='base URL for update images, other than the '
1691 'devserver. Use in conjunction with remote_payload.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001692 parser.add_option_group(group)
1693
1694
1695def _AddUpdateOptions(parser):
1696 group = optparse.OptionGroup(
1697 parser, 'Autoupdate Options', 'These options can be used to change '
1698 'how the devserver either generates or serve update payloads. Please '
1699 'note that all of these option affect how a payload is generated and so '
1700 'do not work in archive-only mode.')
1701 group.add_option('--board',
1702 help='By default the devserver will create an update '
1703 'payload from the latest image built for the board '
1704 'a device that is requesting an update has. When we '
1705 'pre-generate an update (see below) and we do not specify '
1706 'another update_type option like image or payload, the '
1707 'devserver needs to know the board to generate the latest '
1708 'image for. This is that board.')
1709 group.add_option('--critical_update',
1710 action='store_true', default=False,
1711 help='Present update payload as critical')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001712 group.add_option('--image',
1713 metavar='FILE',
1714 help='Generate and serve an update using this image to any '
1715 'device that requests an update.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001716 group.add_option('--payload',
1717 metavar='PATH',
1718 help='use the update payload from specified directory '
1719 '(update.gz).')
1720 group.add_option('-p', '--pregenerate_update',
1721 action='store_true', default=False,
1722 help='pre-generate the update payload before accepting '
1723 'update requests. Useful to help debug payload generation '
1724 'issues quickly. Also if an update payload will take a '
1725 'long time to generate, a client may timeout if you do not'
1726 'pregenerate the update.')
1727 group.add_option('--src_image',
1728 metavar='PATH', default='',
1729 help='If specified, delta updates will be generated using '
1730 'this image as the source image. Delta updates are when '
1731 'you are updating from a "source image" to a another '
1732 'image.')
1733 parser.add_option_group(group)
1734
1735
1736def _AddProductionOptions(parser):
1737 group = optparse.OptionGroup(
1738 parser, 'Advanced Server Options', 'These options can be used to changed '
1739 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001740 group.add_option('--clear_cache',
1741 action='store_true', default=False,
1742 help='At startup, removes all cached entries from the'
1743 'devserver\'s cache.')
1744 group.add_option('--logfile',
1745 metavar='PATH',
1746 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001747 group.add_option('--pidfile',
1748 metavar='PATH',
1749 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001750 group.add_option('--portfile',
1751 metavar='PATH',
1752 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001753 group.add_option('--production',
1754 action='store_true', default=False,
1755 help='have the devserver use production values when '
1756 'starting up. This includes using more threads and '
1757 'performing less logging.')
1758 parser.add_option_group(group)
1759
1760
Paul Hobbsef4e0702016-06-27 17:01:42 -07001761def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001762 """Create a LogHandler instance used to log all messages."""
1763 hdlr_cls = handlers.TimedRotatingFileHandler
1764 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001765 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001766 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001767 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001768 return hdlr
1769
1770
Chris Sosacde6bf42012-05-31 18:36:39 -07001771def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001772 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001773 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001774
1775 # get directory that the devserver is run from
1776 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001777 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001778 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001779 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001780 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001781 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001782 parser.add_option('--port',
1783 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001784 help=('port for the dev server to use; if zero, binds to '
1785 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001786 parser.add_option('-t', '--test_image',
1787 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001788 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001789 parser.add_option('-x', '--xbuddy_manage_builds',
1790 action='store_true',
1791 default=False,
1792 help='If set, allow xbuddy to manage images in'
1793 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001794 parser.add_option('-a', '--android_build_credential',
1795 default=None,
1796 help='Path to a json file which contains the credential '
1797 'needed to access Android builds.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001798 _AddProductionOptions(parser)
1799 _AddUpdateOptions(parser)
1800 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001801 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001802
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001803 # Handle options that must be set globally in cherrypy. Do this
1804 # work up front, because calls to _Log() below depend on this
1805 # initialization.
1806 if options.production:
1807 cherrypy.config.update({'environment': 'production'})
1808 if not options.logfile:
1809 cherrypy.config.update({'log.screen': True})
1810 else:
1811 cherrypy.config.update({'log.error_file': '',
1812 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001813 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001814 # Pylint can't seem to process these two calls properly
1815 # pylint: disable=E1101
1816 cherrypy.log.access_log.addHandler(hdlr)
1817 cherrypy.log.error_log.addHandler(hdlr)
1818 # pylint: enable=E1101
1819
joychened64b222013-06-21 16:39:34 -07001820 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001821 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001822
joychened64b222013-06-21 16:39:34 -07001823 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001824 # If our devserver is only supposed to serve payloads, we shouldn't be
1825 # mucking with the cache at all. If the devserver hadn't previously
1826 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001827 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001828 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001829 else:
1830 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001831
Chris Sosadbc20082012-12-10 13:39:11 -08001832 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001833 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001834
joychen121fc9b2013-08-02 14:30:30 -07001835 _xbuddy = xbuddy.XBuddy(options.xbuddy_manage_builds,
1836 options.board,
joychen121fc9b2013-08-02 14:30:30 -07001837 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001838 if options.clear_cache and options.xbuddy_manage_builds:
1839 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001840
Chris Sosa6a3697f2013-01-29 16:44:43 -08001841 # We allow global use here to share with cherrypy classes.
1842 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001843 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001844 updater = autoupdate.Autoupdate(
joychen121fc9b2013-08-02 14:30:30 -07001845 _xbuddy,
joychened64b222013-06-21 16:39:34 -07001846 static_dir=options.static_dir,
Andrew de los Reyes52620802010-04-12 13:40:07 -07001847 urlbase=options.urlbase,
Chris Sosa5d342a22010-09-28 16:54:41 -07001848 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001849 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001850 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -07001851 src_image=options.src_image,
Chris Sosa08d55a22011-01-19 16:08:02 -08001852 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001853 copy_to_static_root=not options.exit,
1854 private_key=options.private_key,
Gabe Black3b567202015-09-23 14:07:59 -07001855 private_key_for_metadata_hash_signature=(
1856 options.private_key_for_metadata_hash_signature),
David Zeuthen52ccd012013-10-31 12:58:26 -07001857 public_key=options.public_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001858 critical_update=options.critical_update,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001859 remote_payload=options.remote_payload,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001860 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001861 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001862 )
Chris Sosa7c931362010-10-11 19:49:01 -07001863
Chris Sosa6a3697f2013-01-29 16:44:43 -08001864 if options.pregenerate_update:
1865 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -07001866
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001867 if options.exit:
1868 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001869
joychen3cb228e2013-06-12 12:13:13 -07001870 dev_server = DevServerRoot(_xbuddy)
1871
Gilad Arnold11fbef42014-02-10 11:04:13 -08001872 # Patch CherryPy to support binding to any available port (--port=0).
1873 cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy)
1874
Chris Sosa855b8932013-08-21 13:24:55 -07001875 if options.pidfile:
1876 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1877
Gilad Arnold11fbef42014-02-10 11:04:13 -08001878 if options.portfile:
1879 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1880
Dan Shiafd5c6c2016-01-07 10:27:03 -08001881 if (options.android_build_credential and
1882 os.path.exists(options.android_build_credential)):
1883 try:
1884 with open(options.android_build_credential) as f:
1885 android_build.BuildAccessor.credential_info = json.load(f)
1886 except ValueError as e:
1887 _Log('Failed to load the android build credential: %s. Error: %s.' %
1888 (options.android_build_credential, e))
joychen3cb228e2013-06-12 12:13:13 -07001889 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001890
1891
1892if __name__ == '__main__':
1893 main()