blob: 72dc9e2459d69ab47e114b3b90d4e95720b0e568 [file] [log] [blame]
David Riley2fcb0122017-11-02 18:25:391#!/usr/bin/env python2
Luis Hector Chavezdca9dd72018-06-12 19:56:302# -*- coding: utf-8 -*-
Chris Sosa781ba6d2012-04-11 19:44:433# Copyright (c) 2009-2012 The Chromium OS Authors. All rights reserved.
[email protected]ded22402009-10-26 22:36:214# 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 18:47:007"""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
Amin Hassanie9ffb862019-09-26 00:10:4011systems.
Chris Sosa3ae4dc12013-03-29 18:47:0012
Amin Hassanie9ffb862019-09-26 00:10:4013The devserver is configured to stage and
Chris Sosa3ae4dc12013-03-29 18:47:0014serve 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
Chris Sosa3ae4dc12013-03-29 18:47:0021For autoupdates, there are many more advanced options that can help specify
22how to update and which payload to give to a requester.
23"""
24
Gabe Black3b567202015-09-23 21:07:5925from __future__ import print_function
Chris Sosa7c931362010-10-12 02:49:0126
Amin Hassanic5af4262019-11-13 21:37:2027import distutils.version # pylint: disable=import-error,no-name-in-module
Gilad Arnold55a2a372012-10-02 16:46:3228import json
David Riley2fcb0122017-11-02 18:25:3929import optparse # pylint: disable=deprecated-module
[email protected]ded22402009-10-26 22:36:2130import os
Scott Zawalski4647ce62012-01-03 22:17:2831import re
Simran Basi4baad082013-02-14 21:39:1832import shutil
xixuan52c2fba2016-05-21 00:02:4833import signal
Mandeep Singh Baines38dcdda2012-12-08 01:55:3334import socket
Chris Masone816e38c2012-05-02 19:22:3635import subprocess
J. Richard Barnette3d977b82013-04-23 18:05:1936import sys
Chris Masone816e38c2012-05-02 19:22:3637import tempfile
Dan Shi59ae7092013-06-04 21:37:2738import threading
Gilad Arnoldd5ebaaa2012-10-02 18:52:3839import types
J. Richard Barnette3d977b82013-04-23 18:05:1940from logging import handlers
41
Amin Hassanid4e35392019-10-03 18:02:4442from six.moves import http_client
43
44# pylint: disable=no-name-in-module, import-error
J. Richard Barnette3d977b82013-04-23 18:05:1945import cherrypy
Chris Sosa855b8932013-08-21 20:24:5546from cherrypy import _cplogging as cplogging
Amin Hassanid4e35392019-10-03 18:02:4447from cherrypy.process import plugins
48# pylint: enable=no-name-in-module, import-error
[email protected]ded22402009-10-26 22:36:2149
Achuith Bhandarkar662fb722019-10-31 23:12:4950import autoupdate
51import cherrypy_ext
Achuith Bhandarkar662fb722019-10-31 23:12:4952import health_checker
53
Richard Barnettedf35c322017-08-19 00:02:1354# This must happen before any local modules get a chance to import
55# anything from chromite. Otherwise, really bad things will happen, and
56# you will _not_ understand why.
Congbin Guo3afae6c2019-08-13 23:29:4257import setup_chromite # pylint: disable=unused-import
Amin Hassanie427e212019-10-28 18:04:2758from chromite.lib import cros_update_progress
Achuith Bhandarkar662fb722019-10-31 23:12:4959from chromite.lib.xbuddy import android_build
60from chromite.lib.xbuddy import artifact_info
61from chromite.lib.xbuddy import build_artifact
62from chromite.lib.xbuddy import cherrypy_log_util
63from chromite.lib.xbuddy import common_util
64from chromite.lib.xbuddy import devserver_constants
65from chromite.lib.xbuddy import downloader
66from chromite.lib.xbuddy import xbuddy
Amin Hassanie427e212019-10-28 18:04:2767from chromite.scripts import cros_update
Gilad Arnoldc65330c2012-09-20 22:17:4868
Gilad Arnoldc65330c2012-09-20 22:17:4869# Module-local log function.
Chris Sosa6a3697f2013-01-30 00:44:4370def _Log(message, *args):
Achuith Bhandarkar662fb722019-10-31 23:12:4971 return cherrypy_log_util.LogWithTag('DEVSERVER', message, *args)
Frank Farzan40160872011-12-13 02:39:1872
Chris Sosa417e55d2011-01-26 00:40:4873CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-17 01:36:1474
Simran Basi4baad082013-02-14 21:39:1875TELEMETRY_FOLDER = 'telemetry_src'
76TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
77 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 23:40:0478 'dep-chrome_test.tar.bz2',
79 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 21:39:1880
Chris Sosa0356d3b2010-09-16 22:46:2281# Sets up global to share between classes.
[email protected]21a5ca32009-11-04 18:23:2382updater = None
[email protected]ded22402009-10-26 22:36:2183
xixuan3d48bff2017-01-31 03:00:0984# Log rotation parameters. These settings correspond to twice a day once
85# devserver is started, with about two weeks (28 backup files) of old logs
86# kept for backup.
J. Richard Barnette3d977b82013-04-23 18:05:1987#
xixuan3d48bff2017-01-31 03:00:0988# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 18:05:1989# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-31 03:00:0990_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 23:29:4291_LOG_ROTATION_INTERVAL = 12 # hours
92_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-13 02:39:1893
xixuan52c2fba2016-05-21 00:02:4894# Auto-update parameters
95
96# Error msg for missing key in CrOS auto-update.
Xixuan Wu32af9f12017-11-13 22:11:4497KEY_ERROR_MSG = 'Key Error in RPC: %s= is required'
xixuan52c2fba2016-05-21 00:02:4898
Sanika Kulkarnid4496fd2020-02-05 01:26:2599# Error msg for deprecated RPC usage.
100DEPRECATED_RPC_ERROR_MSG = ('The %s RPC has been deprecated. Usage of this '
101 'RPC is discouraged. Please go to '
102 'go/devserver-deprecation for more information.')
103
xixuan52c2fba2016-05-21 00:02:48104
Amin Hassanid4e35392019-10-03 18:02:44105class DevServerError(Exception):
106 """Exception class used by DevServer."""
107
108
Sanika Kulkarnid4496fd2020-02-05 01:26:25109class DeprecatedRPCError(DevServerError):
110 """Exception class used when an RPC is deprecated but is still being used."""
111
112 def __init__(self, rpc_name):
113 """Constructor for DeprecatedRPCError class.
114
115 :param rpc_name: (str) name of the RPC that has been deprecated.
116 """
117 super(DeprecatedRPCError, self).__init__(DEPRECATED_RPC_ERROR_MSG % rpc_name)
118 self.rpc_name = rpc_name
119
120
Amin Hassani722e0962019-11-15 23:45:31121class DevServerHTTPError(cherrypy.HTTPError):
122 """Exception class to log the HTTPResponse before routing it to cherrypy."""
123 def __init__(self, status, message):
124 """CherryPy error with logging.
125
126 Args:
127 status: HTTPResponse status.
128 message: Message associated with the response.
129 """
130 cherrypy.HTTPError.__init__(self, status, message)
131 _Log('HTTPError status: %s message: %s', status, message)
132
133
Gabe Black3b567202015-09-23 21:07:59134def _canonicalize_archive_url(archive_url):
135 """Canonicalizes archive_url strings.
136
137 Raises:
138 DevserverError: if archive_url is not set.
139 """
140 if archive_url:
141 if not archive_url.startswith('gs://'):
Amin Hassanid4e35392019-10-03 18:02:44142 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14143 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 21:07:59144
145 return archive_url.rstrip('/')
146 else:
Amin Hassanid4e35392019-10-03 18:02:44147 raise DevServerError('Must specify an archive_url in the request')
Gabe Black3b567202015-09-23 21:07:59148
149
150def _canonicalize_local_path(local_path):
151 """Canonicalizes |local_path| strings.
152
153 Raises:
154 DevserverError: if |local_path| is not set.
155 """
156 # Restrict staging of local content to only files within the static
157 # directory.
158 local_path = os.path.abspath(local_path)
159 if not local_path.startswith(updater.static_dir):
Amin Hassanid4e35392019-10-03 18:02:44160 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14161 'Local path %s must be a subdirectory of the static'
162 ' directory: %s' % (local_path, updater.static_dir))
Gabe Black3b567202015-09-23 21:07:59163
164 return local_path.rstrip('/')
165
166
167def _get_artifacts(kwargs):
168 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
169
170 Raises:
171 DevserverError if no artifacts would be returned.
172 """
173 artifacts = kwargs.get('artifacts')
174 files = kwargs.get('files')
175 if not artifacts and not files:
Amin Hassanid4e35392019-10-03 18:02:44176 raise DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 21:07:59177
178 # Note we NEED to coerce files to a string as we get raw unicode from
179 # cherrypy and we treat files as strings elsewhere in the code.
180 return (str(artifacts).split(',') if artifacts else [],
181 str(files).split(',') if files else [])
182
183
Dan Shi61305df2015-10-26 23:52:35184def _is_android_build_request(kwargs):
185 """Check if a devserver call is for Android build, based on the arguments.
186
187 This method exams the request's arguments (os_type) to determine if the
188 request is for Android build. If os_type is set to `android`, returns True.
189 If os_type is not set or has other values, returns False.
190
191 Args:
192 kwargs: Keyword arguments for the request.
193
194 Returns:
195 True if the request is for Android build. False otherwise.
196 """
197 os_type = kwargs.get('os_type', None)
198 return os_type == 'android'
199
200
Gabe Black3b567202015-09-23 21:07:59201def _get_downloader(kwargs):
202 """Returns the downloader based on passed in arguments.
203
204 Args:
Amin Hassani08e42d22019-06-03 07:31:30205 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 21:07:59206 """
207 local_path = kwargs.get('local_path')
208 if local_path:
209 local_path = _canonicalize_local_path(local_path)
210
211 dl = None
212 if local_path:
Prathmesh Prabhu58d08932018-01-19 23:08:19213 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
214 dl = downloader.LocalDownloader(updater.static_dir, local_path,
215 delete_source=delete_source)
Gabe Black3b567202015-09-23 21:07:59216
Dan Shi61305df2015-10-26 23:52:35217 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 21:07:59218 archive_url = kwargs.get('archive_url')
219 if not archive_url and not local_path:
Amin Hassanid4e35392019-10-03 18:02:44220 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14221 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 21:07:59222 if archive_url and local_path:
Amin Hassanid4e35392019-10-03 18:02:44223 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14224 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 21:07:59225 if not dl:
226 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 19:56:30227 dl = downloader.GoogleStorageDownloader(
228 updater.static_dir, archive_url,
229 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
230 archive_url))
Gabe Black3b567202015-09-23 21:07:59231 elif not dl:
232 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 19:10:33233 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 23:52:35234 build_id = kwargs.get('build_id', None)
235 if not target or not branch or not build_id:
Amin Hassanid4e35392019-10-03 18:02:44236 raise DevServerError('target, branch, build ID must all be specified for '
237 'downloading Android build.')
Dan Shi72b16132015-10-08 19:10:33238 dl = downloader.AndroidBuildDownloader(updater.static_dir, branch, build_id,
239 target)
Gabe Black3b567202015-09-23 21:07:59240
241 return dl
242
243
244def _get_downloader_and_factory(kwargs):
245 """Returns the downloader and artifact factory based on passed in arguments.
246
247 Args:
Amin Hassani08e42d22019-06-03 07:31:30248 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 21:07:59249 """
250 artifacts, files = _get_artifacts(kwargs)
251 dl = _get_downloader(kwargs)
252
Achuith Bhandarkar2a1fcd82019-10-18 00:45:58253 if (isinstance(dl, (downloader.GoogleStorageDownloader,
254 downloader.LocalDownloader))):
Gabe Black3b567202015-09-23 21:07:59255 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 19:10:33256 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 21:07:59257 factory_class = build_artifact.AndroidArtifactFactory
258 else:
Amin Hassanid4e35392019-10-03 18:02:44259 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14260 'Unrecognized value for downloader type: %s' % type(dl))
Gabe Black3b567202015-09-23 21:07:59261
262 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
263
264 return dl, factory
265
266
Scott Zawalski4647ce62012-01-03 22:17:28267def _LeadingWhiteSpaceCount(string):
268 """Count the amount of leading whitespace in a string.
269
270 Args:
271 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-08 02:21:26272
Scott Zawalski4647ce62012-01-03 22:17:28273 Returns:
274 number of white space chars before characters start.
275 """
Gabe Black3b567202015-09-23 21:07:59276 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 22:17:28277 if matched:
278 return len(matched.group())
279
280 return 0
281
282
283def _PrintDocStringAsHTML(func):
284 """Make a functions docstring somewhat HTML style.
285
286 Args:
287 func: The function to return the docstring from.
Don Garrettf84631a2014-01-08 02:21:26288
Scott Zawalski4647ce62012-01-03 22:17:28289 Returns:
290 A string that is somewhat formated for a web browser.
291 """
292 # TODO(scottz): Make this parse Args/Returns in a prettier way.
293 # Arguments could be bolded and indented etc.
294 html_doc = []
295 for line in func.__doc__.splitlines():
296 leading_space = _LeadingWhiteSpaceCount(line)
297 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 18:26:55298 line = ' ' * leading_space + line
Scott Zawalski4647ce62012-01-03 22:17:28299
300 html_doc.append('<BR>%s' % line)
301
302 return '\n'.join(html_doc)
303
304
Simran Basief83d6a2014-08-28 21:32:01305def _GetUpdateTimestampHandler(static_dir):
306 """Returns a handler to update directory staged.timestamp.
307
308 This handler resets the stage.timestamp whenever static content is accessed.
309
310 Args:
311 static_dir: Directory from which static content is being staged.
312
313 Returns:
Amin Hassani08e42d22019-06-03 07:31:30314 A cherrypy handler to update the timestamp of accessed content.
Simran Basief83d6a2014-08-28 21:32:01315 """
316 def UpdateTimestampHandler():
317 if not '404' in cherrypy.response.status:
318 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
319 cherrypy.request.path_info)
320 if build_match:
321 build_dir = os.path.join(static_dir, build_match.group('build'))
322 downloader.Downloader.TouchTimestampForStaged(build_dir)
323 return UpdateTimestampHandler
324
325
Chris Sosa7c931362010-10-12 02:49:01326def _GetConfig(options):
327 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-08 01:55:33328
Mandeep Singh Baines38dcdda2012-12-08 01:55:33329 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 23:14:26330 # Fall back to IPv4 when python is not configured with IPv6.
331 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-08 01:55:33332 socket_host = '0.0.0.0'
333
Simran Basief83d6a2014-08-28 21:32:01334 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
335 # on the on_end_resource hook. This hook is called once processing is
336 # complete and the response is ready to be returned.
337 cherrypy.tools.update_timestamp = cherrypy.Tool(
338 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
339
David Riley2fcb0122017-11-02 18:25:39340 base_config = {
341 'global': {
342 'server.log_request_headers': True,
343 'server.protocol_version': 'HTTP/1.1',
344 'server.socket_host': socket_host,
345 'server.socket_port': int(options.port),
346 'response.timeout': 6000,
347 'request.show_tracebacks': True,
348 'server.socket_timeout': 60,
349 'server.thread_pool': 2,
350 'engine.autoreload.on': False,
351 },
352 '/api': {
353 # Gets rid of cherrypy parsing post file for args.
354 'request.process_request_body': False,
355 },
356 '/build': {
357 'response.timeout': 100000,
358 },
359 '/update': {
360 # Gets rid of cherrypy parsing post file for args.
361 'request.process_request_body': False,
362 'response.timeout': 10000,
363 },
364 # Sets up the static dir for file hosting.
365 '/static': {
366 'tools.staticdir.dir': options.static_dir,
367 'tools.staticdir.on': True,
368 'response.timeout': 10000,
369 'tools.update_timestamp.on': True,
370 },
371 }
Chris Sosa5f118ef2012-07-12 18:37:50372 if options.production:
Alex Miller93beca52013-07-31 02:25:09373 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 18:12:52374
Chris Sosa7c931362010-10-12 02:49:01375 return base_config
[email protected]64244662009-11-12 00:52:08376
Darin Petkove17164a2010-08-11 20:24:41377
Gilad Arnoldd5ebaaa2012-10-02 18:52:38378def _GetRecursiveMemberObject(root, member_list):
379 """Returns an object corresponding to a nested member list.
380
381 Args:
382 root: the root object to search
383 member_list: list of nested members to search
Don Garrettf84631a2014-01-08 02:21:26384
Gilad Arnoldd5ebaaa2012-10-02 18:52:38385 Returns:
386 An object corresponding to the member name list; None otherwise.
387 """
388 for member in member_list:
389 next_root = root.__class__.__dict__.get(member)
390 if not next_root:
391 return None
392 root = next_root
393 return root
394
395
396def _IsExposed(name):
397 """Returns True iff |name| has an `exposed' attribute and it is set."""
398 return hasattr(name, 'exposed') and name.exposed
399
400
Congbin Guo6bc32182019-08-21 00:54:30401def _GetExposedMethod(nested_member):
Gilad Arnoldd5ebaaa2012-10-02 18:52:38402 """Returns a CherryPy-exposed method, if such exists.
403
404 Args:
Gilad Arnoldd5ebaaa2012-10-02 18:52:38405 nested_member: a slash-joined path to the nested member
Don Garrettf84631a2014-01-08 02:21:26406
Gilad Arnoldd5ebaaa2012-10-02 18:52:38407 Returns:
Congbin Guo6bc32182019-08-21 00:54:30408 A function object corresponding to the path defined by |nested_member| from
409 the app root object registered, if the function is exposed; None otherwise.
Gilad Arnoldd5ebaaa2012-10-02 18:52:38410 """
Congbin Guo6bc32182019-08-21 00:54:30411 for app in cherrypy.tree.apps.values():
412 # Use the 'index' function doc as the doc of the app.
413 if nested_member == app.script_name.lstrip('/'):
414 nested_member = 'index'
415
416 method = _GetRecursiveMemberObject(app.root, nested_member.split('/'))
417 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
418 return method
Gilad Arnoldd5ebaaa2012-10-02 18:52:38419
420
Gilad Arnold748c8322012-10-12 16:51:35421def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 18:52:38422 """Finds exposed CherryPy methods.
423
424 Args:
425 root: the root object for searching
426 prefix: slash-joined chain of members leading to current object
427 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-08 02:21:26428
Gilad Arnoldd5ebaaa2012-10-02 18:52:38429 Returns:
430 List of exposed URLs that are not unlisted.
431 """
432 method_list = []
Congbin Guo6bc32182019-08-21 00:54:30433 for member in root.__class__.__dict__.keys():
Gilad Arnoldd5ebaaa2012-10-02 18:52:38434 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 16:51:35435 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 18:52:38436 continue
437 member_obj = root.__class__.__dict__[member]
438 if _IsExposed(member_obj):
Amin Hassani08e42d22019-06-03 07:31:30439 if isinstance(member_obj, types.FunctionType):
Congbin Guo6bc32182019-08-21 00:54:30440 # Regard the app name as exposed "method" name if it exposed 'index'
441 # function.
442 if prefix and member == 'index':
443 method_list.append(prefix)
444 else:
445 method_list.append(prefixed_member)
Gilad Arnoldd5ebaaa2012-10-02 18:52:38446 else:
447 method_list += _FindExposedMethods(
448 member_obj, prefixed_member, unlisted)
449 return method_list
450
451
xixuan52c2fba2016-05-21 00:02:48452def _check_base_args_for_auto_update(kwargs):
xixuanac89ce82016-12-01 00:48:20453 """Check basic args required for auto-update.
454
455 Args:
456 kwargs: the parameters to be checked.
457
458 Raises:
459 DevServerHTTPError if required parameters don't exist in kwargs.
460 """
xixuan52c2fba2016-05-21 00:02:48461 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31462 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
463 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-21 00:02:48464
465 if 'build_name' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31466 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
467 KEY_ERROR_MSG % 'build_name')
xixuan52c2fba2016-05-21 00:02:48468
469
470def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-12-01 00:48:20471 """Parse boolean arg from kwargs.
472
473 Args:
474 kwargs: the parameters to be checked.
475 key: the key to be parsed.
476
477 Returns:
478 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
479
480 Raises:
481 DevServerHTTPError if kwargs[key] is not a boolean variable.
482 """
xixuan52c2fba2016-05-21 00:02:48483 if key in kwargs:
484 if kwargs[key] == 'True':
485 return True
486 elif kwargs[key] == 'False':
487 return False
488 else:
Amin Hassani722e0962019-11-15 23:45:31489 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
490 'The value for key %s is not boolean.' % key)
xixuan52c2fba2016-05-21 00:02:48491 else:
492 return False
493
xixuan447ad9d2017-02-28 22:46:20494
xixuanac89ce82016-12-01 00:48:20495def _parse_string_arg(kwargs, key):
496 """Parse string arg from kwargs.
497
498 Args:
499 kwargs: the parameters to be checked.
500 key: the key to be parsed.
501
502 Returns:
503 The string value of kwargs[key], or None if key doesn't exist in kwargs.
504 """
505 if key in kwargs:
506 return kwargs[key]
507 else:
508 return None
509
xixuan447ad9d2017-02-28 22:46:20510
xixuanac89ce82016-12-01 00:48:20511def _build_uri_from_build_name(build_name):
512 """Get build url from a given build name.
513
514 Args:
515 build_name: the build name to be parsed, whose format is
516 'board/release_version'.
517
518 Returns:
519 The release_archive_url on Google Storage for this build name.
520 """
Amin Hassani08e42d22019-06-03 07:31:30521 # TODO(ahassani): This function doesn't seem to be used anywhere since its
522 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
523 # causing any runtime issues. So deprecate this in the future.
524 tokens = build_name.split('/')
525 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-21 00:02:48526
xixuan447ad9d2017-02-28 22:46:20527
528def _clear_process(host_name, pid):
529 """Clear AU process for given hostname and pid.
530
531 This clear includes:
532 1. kill process if it's alive.
533 2. delete the track status file of this process.
534 3. delete the executing log file of this process.
535
536 Args:
537 host_name: the host to execute auto-update.
538 pid: the background auto-update process id.
539 """
540 if cros_update_progress.IsProcessAlive(pid):
541 os.killpg(int(pid), signal.SIGKILL)
542
543 cros_update_progress.DelTrackStatusFile(host_name, pid)
544 cros_update_progress.DelExecuteLogFile(host_name, pid)
545
546
Sanika Kulkarnid4496fd2020-02-05 01:26:25547def is_deprecated_server():
548 """Gets whether the devserver has deprecated RPCs."""
549 return cherrypy.config.get('infra_removal', False)
550
551
Dale Curtisc9aaf3a2011-08-09 22:47:40552class ApiRoot(object):
553 """RESTful API for Dev Server information."""
554 exposed = True
555
556 @cherrypy.expose
Gilad Arnold286a0062012-01-12 21:47:02557 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 18:36:27558 """Returns a JSON object containing a log of host event.
559
560 Args:
561 ip: address of host whose event log is requested, or `all'
Don Garrettf84631a2014-01-08 02:21:26562
Gilad Arnold1b908392012-10-05 18:36:27563 Returns:
Amin Hassani7c447852019-09-26 22:01:48564 A JSON dictionary containing all or some of the following fields:
Amin Hassanie7ead902019-10-11 23:42:43565 version: The Chromium OS version the device is running.
566 track: The channel the device is running on.
567 board: The device's board.
568 event_result: The event result of Omaha request.
569 event_type: The event type of Omaha request.
570 previous_version: The Chromium OS version we updated and rebooted from.
571 timestamp: The timestamp the event was received.
Amin Hassani7c447852019-09-26 22:01:48572 See the OmahaEvent class in update_engine/omaha_request_action.h for
573 event type and status code definitions. If the ip does not exist an empty
574 string is returned.
Gilad Arnold1b908392012-10-05 18:36:27575
576 Example URL:
577 http://myhost/api/hostlog?ip=192.168.1.5
578 """
Sanika Kulkarnid4496fd2020-02-05 01:26:25579 if is_deprecated_server():
580 raise DeprecatedRPCError('hostlog')
Gilad Arnold286a0062012-01-12 21:47:02581 return updater.HandleHostLogPing(ip)
582
583 @cherrypy.expose
Don Garrettf84631a2014-01-08 02:21:26584 def fileinfo(self, *args):
Gilad Arnold55a2a372012-10-02 16:46:32585 """Returns information about a given staged file.
586
587 Args:
Don Garrettf84631a2014-01-08 02:21:26588 args: path to the file inside the server's static staging directory
589
Gilad Arnold55a2a372012-10-02 16:46:32590 Returns:
591 A JSON encoded dictionary with information about the said file, which may
592 contain the following keys/values:
Gilad Arnold1b908392012-10-05 18:36:27593 size (int): the file size in bytes
Gilad Arnold1b908392012-10-05 18:36:27594 sha256 (string): a base64 encoded SHA256 hash
595
596 Example URL:
597 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 16:46:32598 """
Sanika Kulkarnid4496fd2020-02-05 01:26:25599 if is_deprecated_server():
600 raise DeprecatedRPCError('fileinfo')
601
Amin Hassani28df4212019-10-28 17:16:50602 # TODO(ahassani): A better way of doing this is to just return the the
603 # content of the payloads' property file instead. That has all this info
604 # except that the key for sha256 is 'sha256_hex', but still base64 encdoed.
605
Don Garrettf84631a2014-01-08 02:21:26606 file_path = os.path.join(updater.static_dir, *args)
Gilad Arnold55a2a372012-10-02 16:46:32607 if not os.path.exists(file_path):
Amin Hassanid4e35392019-10-03 18:02:44608 raise DevServerError('file not found: %s' % file_path)
Gilad Arnold55a2a372012-10-02 16:46:32609 try:
610 file_size = os.path.getsize(file_path)
Gilad Arnold55a2a372012-10-02 16:46:32611 file_sha256 = common_util.GetFileSha256(file_path)
Amin Hassani469f5702019-10-21 22:35:06612 except os.error as e:
Amin Hassanid4e35392019-10-03 18:02:44613 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14614 'failed to get info for file %s: %s' % (file_path, e))
Gilad Arnolde74b3812013-04-22 18:27:38615
Gilad Arnolde74b3812013-04-22 18:27:38616 return json.dumps({
Amin Hassani28df4212019-10-28 17:16:50617 'size': file_size,
618 'sha256': file_sha256,
619 }, sort_keys=True)
Gilad Arnold55a2a372012-10-02 16:46:32620
Chris Sosa76e44b92013-01-31 20:11:38621
David Rochberg7c79a812011-01-19 19:24:45622class DevServerRoot(object):
Chris Sosa7c931362010-10-12 02:49:01623 """The Root Class for the Dev Server.
624
625 CherryPy works as follows:
626 For each method in this class, cherrpy interprets root/path
627 as a call to an instance of DevServerRoot->method_name. For example,
628 a call to http://myhost/build will call build. CherryPy automatically
629 parses http args and places them as keyword arguments in each method.
630 For paths http://myhost/update/dir1/dir2, you can use *args so that
631 cherrypy uses the update method and puts the extra paths in args.
632 """
Gilad Arnoldf8f769f2012-09-24 15:43:01633 # Method names that should not be listed on the index page.
634 _UNLISTED_METHODS = ['index', 'doc']
635
Dale Curtisc9aaf3a2011-08-09 22:47:40636 api = ApiRoot()
Chris Sosa7c931362010-10-12 02:49:01637
Dan Shi59ae7092013-06-04 21:37:27638 # Number of threads that devserver is staging images.
639 _staging_thread_count = 0
640 # Lock used to lock increasing/decreasing count.
641 _staging_thread_count_lock = threading.Lock()
642
joychen3cb228e2013-06-12 19:13:13643 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 22:20:41644 self._builder = None
Simran Basi4baad082013-02-14 21:39:18645 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 19:13:13646 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 19:24:45647
Congbin Guo3afae6c2019-08-13 23:29:42648 @property
649 def staging_thread_count(self):
650 """Get the staging thread count."""
651 return self._staging_thread_count
Dan Shiafd0e492015-05-27 21:23:51652
Dale Curtisc9aaf3a2011-08-09 22:47:40653 @cherrypy.expose
David Rochberg7c79a812011-01-19 19:24:45654 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-12 02:49:01655 """Builds the package specified."""
Sanika Kulkarnid4496fd2020-02-05 01:26:25656 if is_deprecated_server():
657 raise DeprecatedRPCError('build')
658
Nick Sanders7dcaa2e2011-08-04 22:20:41659 import builder
660 if self._builder is None:
661 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 19:24:45662 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-12 02:49:01663
Dale Curtisc9aaf3a2011-08-09 22:47:40664 @cherrypy.expose
Dan Shif8eb0d12013-08-02 00:52:06665 def is_staged(self, **kwargs):
666 """Check if artifacts have been downloaded.
667
Congbin Guo3afae6c2019-08-13 23:29:42668 Examples:
669 To check if autotest and test_suites are staged:
670 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
671 artifacts=autotest,test_suites
672
Amin Hassani08e42d22019-06-03 07:31:30673 Args:
Chris Sosa6b0c6172013-08-06 00:01:33674 async: True to return without waiting for download to complete.
675 artifacts: Comma separated list of named artifacts to download.
676 These are defined in artifact_info and have their implementation
677 in build_artifact.py.
678 files: Comma separated list of file artifacts to stage. These
679 will be available as is in the corresponding static directory with no
680 custom post-processing.
681
Congbin Guo3afae6c2019-08-13 23:29:42682 Returns:
683 True of all artifacts are staged.
Dan Shif8eb0d12013-08-02 00:52:06684 """
Gabe Black3b567202015-09-23 21:07:59685 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-19 03:39:09686 response = str(dl.IsStaged(factory))
687 _Log('Responding to is_staged %s request with %r', kwargs, response)
688 return response
Dan Shi59ae7092013-06-04 21:37:27689
Chris Sosa76e44b92013-01-31 20:11:38690 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 23:35:19691 def list_image_dir(self, **kwargs):
692 """Take an archive url and list the contents in its staged directory.
693
Amin Hassani08e42d22019-06-03 07:31:30694 Examples:
Prashanth Ba06d2d22014-03-07 23:35:19695 To list the contents of where this devserver should have staged
696 gs://image-archive/<board>-release/<build> call:
697 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
698
Congbin Guo3afae6c2019-08-13 23:29:42699 Args:
700 archive_url: Google Storage URL for the build.
701
Prashanth Ba06d2d22014-03-07 23:35:19702 Returns:
703 A string with information about the contents of the image directory.
704 """
Gabe Black3b567202015-09-23 21:07:59705 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 23:35:19706 try:
Gabe Black3b567202015-09-23 21:07:59707 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 23:35:19708 except build_artifact.ArtifactDownloadError as e:
709 return 'Cannot list the contents of staged artifacts. %s' % e
710 if not image_dir_contents:
Gabe Black3b567202015-09-23 21:07:59711 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 23:35:19712 return image_dir_contents
713
714 @cherrypy.expose
Chris Sosa76e44b92013-01-31 20:11:38715 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 21:07:59716 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 20:11:38717
Gabe Black3b567202015-09-23 21:07:59718 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 19:10:33719 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 21:07:59720 on the devserver. A call to this will attempt to cache non-specified
721 artifacts in the background for the given from the given URL following
722 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 20:11:38723 artifacts is explicitly defined in the build_artifact module.
724
725 These artifacts will then be available from the static/ sub-directory of
726 the devserver.
727
Amin Hassani08e42d22019-06-03 07:31:30728 Examples:
Chris Sosa76e44b92013-01-31 20:11:38729 To download the autotest and test suites tarballs:
730 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
731 artifacts=autotest,test_suites
732 To download the full update payload:
733 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
734 artifacts=full_payload
Chris Sosa6b0c6172013-08-06 00:01:33735 To download just a file called blah.bin:
736 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
737 files=blah.bin
Chris Sosa76e44b92013-01-31 20:11:38738
739 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 23:39:34740 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 20:11:38741
742 Note for this example, relative path is the archive_url stripped of its
743 basename i.e. path/ in the examples above. Specific example:
744
745 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
746
747 Will get staged to:
748
joychened64b222013-06-21 23:39:34749 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 23:29:42750
751 Args:
752 archive_url: Google Storage URL for the build.
753 local_path: Local path for the build.
754 delete_source: Only meaningful with local_path. bool to indicate if the
755 source files should be deleted. This is especially useful when staging
756 a file locally in resource constrained environments as it allows us to
757 move the relevant files locally instead of copying them.
758 async: True to return without waiting for download to complete.
759 artifacts: Comma separated list of named artifacts to download.
760 These are defined in artifact_info and have their implementation
761 in build_artifact.py.
762 files: Comma separated list of files to stage. These
763 will be available as is in the corresponding static directory with no
764 custom post-processing.
765 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 20:11:38766 """
Gabe Black3b567202015-09-23 21:07:59767 dl, factory = _get_downloader_and_factory(kwargs)
768
Dan Shi59ae7092013-06-04 21:37:27769 with DevServerRoot._staging_thread_count_lock:
770 DevServerRoot._staging_thread_count += 1
771 try:
Laurence Goodbyf5c958d2016-01-15 02:23:56772 boolean_string = kwargs.get('clean')
773 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
774 if clean and os.path.exists(dl.GetBuildDir()):
775 _Log('Removing %s' % dl.GetBuildDir())
776 shutil.rmtree(dl.GetBuildDir())
Achuith Bhandarkar2a1fcd82019-10-18 00:45:58777 is_async = kwargs.get('async', False)
778 dl.Download(factory, is_async=is_async)
Dan Shi59ae7092013-06-04 21:37:27779 finally:
780 with DevServerRoot._staging_thread_count_lock:
781 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 20:11:38782 return 'Success'
Chris Sosacde6bf42012-06-01 01:36:39783
784 @cherrypy.expose
xixuan52c2fba2016-05-21 00:02:48785 def cros_au(self, **kwargs):
786 """Auto-update a CrOS DUT.
787
788 Args:
789 kwargs:
790 host_name: the hostname of the DUT to auto-update.
791 build_name: the build name for update the DUT.
792 force_update: Force an update even if the version installed is the
793 same. Default: False.
794 full_update: If True, do not run stateful update, directly force a full
795 reimage. If False, try stateful update first if the dut is already
796 installed with the same version.
797 async: Whether the auto_update function is ran in the background.
David Rileyee75de22017-11-02 17:48:15798 quick_provision: Whether the quick provision path is attempted first.
xixuan52c2fba2016-05-21 00:02:48799
800 Returns:
801 A tuple includes two elements:
802 a boolean variable represents whether the auto-update process is
803 successfully started.
804 an integer represents the background auto-update process id.
805 """
806 _check_base_args_for_auto_update(kwargs)
807
808 host_name = kwargs['host_name']
809 build_name = kwargs['build_name']
810 force_update = _parse_boolean_arg(kwargs, 'force_update')
811 full_update = _parse_boolean_arg(kwargs, 'full_update')
Achuith Bhandarkar2a1fcd82019-10-18 00:45:58812 is_async = _parse_boolean_arg(kwargs, 'async')
xixuanac89ce82016-12-01 00:48:20813 original_build = _parse_string_arg(kwargs, 'original_build')
David Haddock90e49442017-04-08 02:14:09814 payload_filename = _parse_string_arg(kwargs, 'payload_filename')
David Haddock20559612017-06-29 05:15:08815 clobber_stateful = _parse_boolean_arg(kwargs, 'clobber_stateful')
David Rileyee75de22017-11-02 17:48:15816 quick_provision = _parse_boolean_arg(kwargs, 'quick_provision')
817
818 devserver_url = updater.GetDevserverUrl()
819 static_url = updater.GetStaticUrl()
xixuan52c2fba2016-05-21 00:02:48820
Achuith Bhandarkar2a1fcd82019-10-18 00:45:58821 if is_async:
Amin Hassani469f5702019-10-21 22:35:06822 # Command of running auto-update.
Amin Hassanie427e212019-10-28 18:04:27823 cmd = ['cros_update', '--hostname', host_name, '-b', build_name,
824 '--static_dir', updater.static_dir]
xixuanac89ce82016-12-01 00:48:20825
826 # The original_build's format is like: link/3428.210.0
827 # The corresponding release_archive_url's format is like:
828 # gs://chromeos-releases/stable-channel/link/3428.210.0
829 if original_build:
830 release_archive_url = _build_uri_from_build_name(original_build)
831 # First staging the stateful.tgz synchronousely.
Amin Hassani469f5702019-10-21 22:35:06832 self.stage(files='stateful.tgz', is_async=False,
xixuanac89ce82016-12-01 00:48:20833 archive_url=release_archive_url)
Amin Hassani469f5702019-10-21 22:35:06834 cmd += ['--original_build', original_build]
xixuanac89ce82016-12-01 00:48:20835
xixuan52c2fba2016-05-21 00:02:48836 if force_update:
Amin Hassani469f5702019-10-21 22:35:06837 cmd += ['--force_update']
xixuan52c2fba2016-05-21 00:02:48838
839 if full_update:
Amin Hassani469f5702019-10-21 22:35:06840 cmd += ['--full_update']
xixuan52c2fba2016-05-21 00:02:48841
David Haddock90e49442017-04-08 02:14:09842 if payload_filename:
Amin Hassani469f5702019-10-21 22:35:06843 cmd += ['--payload_filename', payload_filename]
David Haddock90e49442017-04-08 02:14:09844
David Haddock20559612017-06-29 05:15:08845 if clobber_stateful:
Amin Hassani469f5702019-10-21 22:35:06846 cmd += ['--clobber_stateful']
David Haddock20559612017-06-29 05:15:08847
David Rileyee75de22017-11-02 17:48:15848 if quick_provision:
Amin Hassani469f5702019-10-21 22:35:06849 cmd += ['--quick_provision']
David Rileyee75de22017-11-02 17:48:15850
851 if devserver_url:
Amin Hassani469f5702019-10-21 22:35:06852 cmd += ['--devserver_url', devserver_url]
David Rileyee75de22017-11-02 17:48:15853
854 if static_url:
Amin Hassani469f5702019-10-21 22:35:06855 cmd += ['--static_url', static_url]
David Rileyee75de22017-11-02 17:48:15856
Amin Hassani78520ae2019-10-29 20:26:51857 p = subprocess.Popen(cmd, preexec_fn=os.setsid)
xixuan2a0970a2016-08-10 19:12:44858 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-21 00:02:48859
860 # Pre-write status in the track_status_file before the first call of
861 # 'get_au_status' to make sure that the track_status_file exists.
xixuan2a0970a2016-08-10 19:12:44862 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
xixuan52c2fba2016-05-21 00:02:48863 progress_tracker.WriteStatus('CrOS update is just started.')
864
xixuan2a0970a2016-08-10 19:12:44865 return json.dumps((True, pid))
xixuan52c2fba2016-05-21 00:02:48866 else:
867 cros_update_trigger = cros_update.CrOSUpdateTrigger(
xixuanac89ce82016-12-01 00:48:20868 host_name, build_name, updater.static_dir, force_update=force_update,
David Rileyee75de22017-11-02 17:48:15869 full_update=full_update, original_build=original_build,
Amin Hassani78520ae2019-10-29 20:26:51870 payload_filename=payload_filename, quick_provision=quick_provision,
871 devserver_url=devserver_url, static_url=static_url)
xixuan52c2fba2016-05-21 00:02:48872 cros_update_trigger.TriggerAU()
xixuan27d50442017-08-09 17:38:25873 return json.dumps((True, -1))
xixuan52c2fba2016-05-21 00:02:48874
875 @cherrypy.expose
876 def get_au_status(self, **kwargs):
877 """Check if the auto-update task is finished.
878
879 It handles 4 cases:
880 1. If an error exists in the track_status_file, delete the track file and
881 raise it.
882 2. If cros-update process is finished, delete the file and return the
883 success result.
884 3. If the process is not running, delete the track file and raise an error
885 about 'the process is terminated due to unknown reason'.
886 4. If the track_status_file does not exist, kill the process if it exists,
887 and raise the IOError.
888
889 Args:
890 kwargs:
891 host_name: the hostname of the DUT to auto-update.
892 pid: the background process id of cros-update.
893
894 Returns:
xixuan28d99072016-10-06 19:24:16895 A dict with three elements:
xixuan52c2fba2016-05-21 00:02:48896 a boolean variable represents whether the auto-update process is
897 finished.
898 a string represents the current auto-update process status.
899 For example, 'Transfer Devserver/Stateful Update Package'.
xixuan28d99072016-10-06 19:24:16900 a detailed error message paragraph if there exists an Auto-Update
901 error, in which the last line shows the main exception. Empty
902 string otherwise.
xixuan52c2fba2016-05-21 00:02:48903 """
904 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31905 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
906 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-21 00:02:48907
908 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31909 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
910 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-21 00:02:48911
912 host_name = kwargs['host_name']
913 pid = kwargs['pid']
914 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
915
xixuan28d99072016-10-06 19:24:16916 result_dict = {'finished': False, 'status': '', 'detailed_error_msg': ''}
xixuan52c2fba2016-05-21 00:02:48917 try:
918 result = progress_tracker.ReadStatus()
919 if result.startswith(cros_update_progress.ERROR_TAG):
xixuan28d99072016-10-06 19:24:16920 result_dict['detailed_error_msg'] = result[len(
921 cros_update_progress.ERROR_TAG):]
xixuan28681fd2016-11-23 19:13:56922 elif result == cros_update_progress.FINISHED:
xixuan28d99072016-10-06 19:24:16923 result_dict['finished'] = True
924 result_dict['status'] = result
xixuan28681fd2016-11-23 19:13:56925 elif not cros_update_progress.IsProcessAlive(pid):
xixuan28d99072016-10-06 19:24:16926 result_dict['detailed_error_msg'] = (
927 'Cros_update process terminated midway due to unknown reason. '
928 'Last update status was %s' % result)
xixuan28681fd2016-11-23 19:13:56929 else:
930 result_dict['status'] = result
931 except IOError as e:
932 if pid and cros_update_progress.IsProcessAlive(pid):
xixuan2a0970a2016-08-10 19:12:44933 os.killpg(int(pid), signal.SIGKILL)
xixuan52c2fba2016-05-21 00:02:48934
xixuan28681fd2016-11-23 19:13:56935 result_dict['detailed_error_msg'] = str(e)
936
937 return json.dumps(result_dict)
xixuan52c2fba2016-05-21 00:02:48938
939 @cherrypy.expose
David Riley6d5fca02017-10-31 17:35:47940 def post_au_status(self, status, **kwargs):
941 """Updates the status of an auto-update task.
942
943 Callers will need to POST to this URL with a body of MIME-type
944 "multipart/form-data".
945 The body should include a single argument, 'status', containing the
946 AU status to record.
947
948 Args:
949 status: The updated status.
950 kwargs:
951 host_name: the hostname of the DUT to auto-update.
952 pid: the background process id of cros-update.
953 """
954 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31955 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
956 KEY_ERROR_MSG % 'host_name')
David Riley6d5fca02017-10-31 17:35:47957
958 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31959 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
960 KEY_ERROR_MSG % 'pid')
David Riley6d5fca02017-10-31 17:35:47961
962 host_name = kwargs['host_name']
963 pid = kwargs['pid']
David Riley3cea2582017-11-25 06:03:01964 status = status.rstrip()
965 _Log('Recording status for %s (%s): %s' % (host_name, pid, status))
David Riley6d5fca02017-10-31 17:35:47966 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
967
David Riley3cea2582017-11-25 06:03:01968 progress_tracker.WriteStatus(status)
David Riley6d5fca02017-10-31 17:35:47969
970 return 'True'
971
972 @cherrypy.expose
xixuan52c2fba2016-05-21 00:02:48973 def handler_cleanup(self, **kwargs):
xixuan3bc974e2016-10-19 00:21:43974 """Clean track status log and temp directory for CrOS auto-update process.
xixuan52c2fba2016-05-21 00:02:48975
976 Args:
977 kwargs:
978 host_name: the hostname of the DUT to auto-update.
979 pid: the background process id of cros-update.
980 """
981 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31982 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
983 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-21 00:02:48984
985 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31986 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
987 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-21 00:02:48988
989 host_name = kwargs['host_name']
990 pid = kwargs['pid']
991 cros_update_progress.DelTrackStatusFile(host_name, pid)
xixuan3bc974e2016-10-19 00:21:43992 cros_update_progress.DelAUTempDirectory(host_name, pid)
xixuan52c2fba2016-05-21 00:02:48993
994 @cherrypy.expose
995 def kill_au_proc(self, **kwargs):
996 """Kill CrOS auto-update process using given process id.
997
998 Args:
999 kwargs:
1000 host_name: Kill all the CrOS auto-update process of this host.
1001
1002 Returns:
1003 True if all processes are killed properly.
1004 """
1005 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:311006 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1007 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-21 00:02:481008
xixuan447ad9d2017-02-28 22:46:201009 cur_pid = kwargs.get('pid')
1010
xixuan52c2fba2016-05-21 00:02:481011 host_name = kwargs['host_name']
xixuan3bc974e2016-10-19 00:21:431012 track_log_list = cros_update_progress.GetAllTrackStatusFileByHostName(
1013 host_name)
xixuan52c2fba2016-05-21 00:02:481014 for log in track_log_list:
1015 # The track log's full path is: path/host_name_pid.log
1016 # Use splitext to remove file extension, then parse pid from the
1017 # filename.
Congbin Guo3afae6c2019-08-13 23:29:421018 pid = os.path.splitext(os.path.basename(log))[0][len(host_name) + 1:]
xixuan447ad9d2017-02-28 22:46:201019 _clear_process(host_name, pid)
xixuan52c2fba2016-05-21 00:02:481020
xixuan447ad9d2017-02-28 22:46:201021 if cur_pid:
1022 _clear_process(host_name, cur_pid)
xixuan52c2fba2016-05-21 00:02:481023
1024 return 'True'
1025
1026 @cherrypy.expose
1027 def collect_cros_au_log(self, **kwargs):
1028 """Collect CrOS auto-update log.
1029
1030 Args:
1031 kwargs:
1032 host_name: the hostname of the DUT to auto-update.
1033 pid: the background process id of cros-update.
1034
1035 Returns:
David Haddock9f459632017-05-11 21:45:461036 A dictionary containing the execute log file and any hostlog files.
xixuan52c2fba2016-05-21 00:02:481037 """
1038 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:311039 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1040 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-21 00:02:481041
1042 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:311043 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1044 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-21 00:02:481045
1046 host_name = kwargs['host_name']
1047 pid = kwargs['pid']
xixuan3bc974e2016-10-19 00:21:431048
1049 # Fetch the execute log recorded by cros_update_progress.
xixuan1bbfaba2016-10-14 00:53:221050 au_log = cros_update_progress.ReadExecuteLogFile(host_name, pid)
1051 cros_update_progress.DelExecuteLogFile(host_name, pid)
David Haddock9f459632017-05-11 21:45:461052 # Fetch the cros_au host_logs if they exist
1053 au_hostlogs = cros_update_progress.ReadAUHostLogFiles(host_name, pid)
1054 return json.dumps({'cros_au_log': au_log, 'host_logs': au_hostlogs})
xixuan1bbfaba2016-10-14 00:53:221055
xixuan52c2fba2016-05-21 00:02:481056 @cherrypy.expose
Dan Shi2f136862016-02-11 23:38:381057 def locate_file(self, **kwargs):
1058 """Get the path to the given file name.
1059
1060 This method looks up the given file name inside specified build artifacts.
1061 One use case is to help caller to locate an apk file inside a build
1062 artifact. The location of the apk file could be different based on the
1063 branch and target.
1064
1065 Args:
1066 file_name: Name of the file to look for.
1067 artifacts: A list of artifact names to search for the file.
1068
1069 Returns:
1070 Path to the file with the given name. It's relative to the folder for the
1071 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 23:38:381072 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251073 if is_deprecated_server():
1074 raise DeprecatedRPCError('locate_file')
1075
Dan Shi2f136862016-02-11 23:38:381076 dl, _ = _get_downloader_and_factory(kwargs)
1077 try:
Joe Brennan1691f8e2017-03-15 22:53:361078 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 23:38:381079 artifacts = kwargs['artifacts']
1080 except KeyError:
Amin Hassanid4e35392019-10-03 18:02:441081 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:141082 '`file_name` and `artifacts` are required to search '
1083 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 23:38:381084 build_path = dl.GetBuildDir()
1085 for artifact in artifacts:
1086 # Get the unzipped folder of the artifact. If it's not defined in
1087 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
1088 # directory directly.
1089 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
1090 artifact_path = os.path.join(build_path, folder)
1091 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 22:53:361092 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 23:38:381093 return os.path.relpath(os.path.join(root, file_name), build_path)
Amin Hassanid4e35392019-10-03 18:02:441094 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:141095 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 23:38:381096
1097 @cherrypy.expose
Simran Basi4baad082013-02-14 21:39:181098 def setup_telemetry(self, **kwargs):
1099 """Extracts and sets up telemetry
1100
1101 This method goes through the telemetry deps packages, and stages them on
1102 the devserver to be used by the drones and the telemetry tests.
1103
1104 Args:
1105 archive_url: Google Storage URL for the build.
1106
1107 Returns:
1108 Path to the source folder for the telemetry codebase once it is staged.
1109 """
Gabe Black3b567202015-09-23 21:07:591110 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 21:39:181111
Gabe Black3b567202015-09-23 21:07:591112 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 21:39:181113 deps_path = os.path.join(build_path, 'autotest/packages')
1114 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
1115 src_folder = os.path.join(telemetry_path, 'src')
1116
1117 with self._telemetry_lock_dict.lock(telemetry_path):
1118 if os.path.exists(src_folder):
1119 # Telemetry is already fully stage return
1120 return src_folder
1121
1122 common_util.MkDirP(telemetry_path)
1123
1124 # Copy over the required deps tar balls to the telemetry directory.
1125 for dep in TELEMETRY_DEPS:
1126 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 23:40:041127 if not os.path.exists(dep_path):
1128 # This dep does not exist (could be new), do not extract it.
1129 continue
Simran Basi4baad082013-02-14 21:39:181130 try:
1131 common_util.ExtractTarball(dep_path, telemetry_path)
1132 except common_util.CommonUtilError as e:
1133 shutil.rmtree(telemetry_path)
Amin Hassanid4e35392019-10-03 18:02:441134 raise DevServerError(str(e))
Simran Basi4baad082013-02-14 21:39:181135
1136 # By default all the tarballs extract to test_src but some parts of
1137 # the telemetry code specifically hardcoded to exist inside of 'src'.
1138 test_src = os.path.join(telemetry_path, 'test_src')
1139 try:
1140 shutil.move(test_src, src_folder)
1141 except shutil.Error:
1142 # This can occur if src_folder already exists. Remove and retry move.
1143 shutil.rmtree(src_folder)
Amin Hassanid4e35392019-10-03 18:02:441144 raise DevServerError(
Gabe Black3b567202015-09-23 21:07:591145 'Failure in telemetry setup for build %s. Appears that the '
1146 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 21:39:181147
1148 return src_folder
1149
1150 @cherrypy.expose
Chris Sosa76e44b92013-01-31 20:11:381151 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 19:22:361152 """Symbolicates a minidump using pre-downloaded symbols, returns it.
1153
1154 Callers will need to POST to this URL with a body of MIME-type
1155 "multipart/form-data".
1156 The body should include a single argument, 'minidump', containing the
1157 binary-formatted minidump to symbolicate.
1158
Chris Masone816e38c2012-05-02 19:22:361159 Args:
Chris Sosa76e44b92013-01-31 20:11:381160 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 19:22:361161 minidump: The binary minidump file to symbolicate.
1162 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251163 if is_deprecated_server():
1164 raise DeprecatedRPCError('symbolicate_dump')
1165
Chris Sosa76e44b92013-01-31 20:11:381166 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 21:39:251167 # Try debug.tar.xz first, then debug.tgz
1168 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
1169 kwargs['artifacts'] = artifact
1170 dl = _get_downloader(kwargs)
1171
1172 try:
1173 if self.stage(**kwargs) == 'Success':
1174 break
1175 except build_artifact.ArtifactDownloadError:
1176 continue
1177 else:
Amin Hassanid4e35392019-10-03 18:02:441178 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:141179 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 20:11:381180
Chris Masone816e38c2012-05-02 19:22:361181 to_return = ''
1182 with tempfile.NamedTemporaryFile() as local:
1183 while True:
1184 data = minidump.file.read(8192)
1185 if not data:
1186 break
1187 local.write(data)
Chris Sosa76e44b92013-01-31 20:11:381188
Chris Masone816e38c2012-05-02 19:22:361189 local.flush()
Chris Sosa76e44b92013-01-31 20:11:381190
Gabe Black3b567202015-09-23 21:07:591191 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 20:11:381192
xixuanab744382017-04-27 17:41:271193 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 20:11:381194 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 17:41:271195 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 20:11:381196 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1197
Chris Masone816e38c2012-05-02 19:22:361198 to_return, error_text = stackwalk.communicate()
1199 if stackwalk.returncode != 0:
Amin Hassanid4e35392019-10-03 18:02:441200 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:141201 "Can't generate stack trace: %s (rc=%d)" % (error_text,
1202 stackwalk.returncode))
Chris Masone816e38c2012-05-02 19:22:361203
1204 return to_return
1205
1206 @cherrypy.expose
Don Garrettf84631a2014-01-08 02:21:261207 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 19:31:361208 """Return a string representing the latest build for a given target.
1209
1210 Args:
1211 target: The build target, typically a combination of the board and the
1212 type of build e.g. x86-mario-release.
1213 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
1214 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-08 02:21:261215
Scott Zawalski16954532012-03-20 19:31:361216 Returns:
1217 A string representation of the latest build if one exists, i.e.
1218 R19-1993.0.0-a1-b1480.
1219 An empty string if no latest could be found.
1220 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251221 if is_deprecated_server():
1222 raise DeprecatedRPCError('latestbuild')
1223
Don Garrettf84631a2014-01-08 02:21:261224 if not kwargs:
Scott Zawalski16954532012-03-20 19:31:361225 return _PrintDocStringAsHTML(self.latestbuild)
1226
Don Garrettf84631a2014-01-08 02:21:261227 if 'target' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:311228 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1229 'Error: target= is required!')
Dan Shi61305df2015-10-26 23:52:351230
1231 if _is_android_build_request(kwargs):
1232 branch = kwargs.get('branch', None)
1233 target = kwargs.get('target', None)
1234 if not target or not branch:
Amin Hassanid4e35392019-10-03 18:02:441235 raise DevServerError('Both target and branch must be specified to query'
1236 ' for the latest Android build.')
Dan Shi61305df2015-10-26 23:52:351237 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
1238
Scott Zawalski16954532012-03-20 19:31:361239 try:
Gilad Arnoldc65330c2012-09-20 22:17:481240 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-08 02:21:261241 updater.static_dir, kwargs['target'],
1242 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 17:05:011243 except common_util.CommonUtilError as errmsg:
Amin Hassani722e0962019-11-15 23:45:311244 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1245 str(errmsg))
Scott Zawalski16954532012-03-20 19:31:361246
1247 @cherrypy.expose
xixuan7efd0002016-04-14 22:34:011248 def list_suite_controls(self, **kwargs):
1249 """Return a list of contents of all known control files.
1250
1251 Example URL:
1252 To List all control files' content:
1253 http://dev-server/list_suite_controls?suite_name=bvt&
1254 build=daisy_spring-release/R29-4279.0.0
1255
1256 Args:
1257 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
1258 suite_name: List the control files belonging to that suite.
1259
1260 Returns:
Dan Shia1cd6522016-04-18 23:07:211261 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 22:34:011262 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251263 if is_deprecated_server():
1264 raise DeprecatedRPCError('list_suite_controls')
1265
xixuan7efd0002016-04-14 22:34:011266 if not kwargs:
1267 return _PrintDocStringAsHTML(self.controlfiles)
1268
1269 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:311270 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1271 'Error: build= is required!')
xixuan7efd0002016-04-14 22:34:011272
1273 if 'suite_name' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:311274 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1275 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 22:34:011276
1277 control_file_list = [
1278 line.rstrip() for line in common_util.GetControlFileListForSuite(
1279 updater.static_dir, kwargs['build'],
1280 kwargs['suite_name']).splitlines()]
1281
Dan Shia1cd6522016-04-18 23:07:211282 control_file_content_dict = {}
xixuan7efd0002016-04-14 22:34:011283 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 23:07:211284 control_file_content_dict[control_path] = (common_util.GetControlFile(
xixuan7efd0002016-04-14 22:34:011285 updater.static_dir, kwargs['build'], control_path))
1286
Dan Shia1cd6522016-04-18 23:07:211287 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 22:34:011288
1289 @cherrypy.expose
Don Garrettf84631a2014-01-08 02:21:261290 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 22:17:281291 """Return a control file or a list of all known control files.
1292
1293 Example URL:
1294 To List all control files:
beepsbd337242013-07-10 05:44:061295 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
1296 To List all control files for, say, the bvt suite:
1297 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 22:17:281298 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 20:12:421299 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0&control_path=client/sleeptest/control
Scott Zawalski4647ce62012-01-03 22:17:281300
1301 Args:
Scott Zawalski84a39c92012-01-13 20:12:421302 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 22:17:281303 control_path: If you want the contents of a control file set this
1304 to the path. E.g. client/site_tests/sleeptest/control
1305 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-10 05:44:061306 suite_name: If control_path is not specified but a suite_name is
1307 specified, list the control files belonging to that suite instead of
1308 all control files. The empty string for suite_name will list all control
1309 files for the build.
Don Garrettf84631a2014-01-08 02:21:261310
Scott Zawalski4647ce62012-01-03 22:17:281311 Returns:
1312 Contents of a control file if control_path is provided.
1313 A list of control files if no control_path is provided.
1314 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251315 if is_deprecated_server():
1316 raise DeprecatedRPCError('controlfiles')
1317
Don Garrettf84631a2014-01-08 02:21:261318 if not kwargs:
Scott Zawalski4647ce62012-01-03 22:17:281319 return _PrintDocStringAsHTML(self.controlfiles)
1320
Don Garrettf84631a2014-01-08 02:21:261321 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:311322 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1323 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 22:17:281324
Don Garrettf84631a2014-01-08 02:21:261325 if 'control_path' not in kwargs:
1326 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-10 05:44:061327 return common_util.GetControlFileListForSuite(
Don Garrettf84631a2014-01-08 02:21:261328 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-10 05:44:061329 else:
1330 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-08 02:21:261331 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 22:17:281332 else:
Gilad Arnoldc65330c2012-09-20 22:17:481333 return common_util.GetControlFile(
Don Garrettf84631a2014-01-08 02:21:261334 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-13 02:39:181335
1336 @cherrypy.expose
Simran Basi99e63c02014-05-20 17:39:521337 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 23:02:111338 """Translates an xBuddy path to a real path to artifact if it exists.
1339
1340 Args:
Simran Basi99e63c02014-05-20 17:39:521341 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
1342 Local searches the devserver's static directory. Remote searches a
1343 Google Storage image archive.
1344
1345 Kwargs:
1346 image_dir: Google Storage image archive to search in if requesting a
1347 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 23:02:111348
1349 Returns:
Simran Basi99e63c02014-05-20 17:39:521350 String in the format of build_id/artifact as stored on the local server
1351 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 23:02:111352 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251353 if is_deprecated_server():
1354 raise DeprecatedRPCError('xbuddy_translate')
1355
Simran Basi99e63c02014-05-20 17:39:521356 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 21:07:591357 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 23:02:111358 response = os.path.join(build_id, filename)
1359 _Log('Path translation requested, returning: %s', response)
1360 return response
1361
1362 @cherrypy.expose
joycheneaf4cfc2013-07-02 15:38:571363 def xbuddy(self, *args, **kwargs):
1364 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 19:13:131365
1366 Args:
joycheneaf4cfc2013-07-02 15:38:571367 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 21:30:301368 components of the path. The path can be understood as
1369 "{local|remote}/build_id/artifact" where build_id is composed of
1370 "board/version."
joycheneaf4cfc2013-07-02 15:38:571371
joychen121fc9b2013-08-02 21:30:301372 The first path element is optional, and can be "remote" or "local"
1373 If local (the default), devserver will not attempt to access Google
1374 Storage, and will only search the static directory for the files.
1375 If remote, devserver will try to obtain the artifact off GS if it's
1376 not found locally.
1377 The board is the familiar board name, optionally suffixed.
1378 The version can be the google storage version number, and may also be
1379 any of a number of xBuddy defined version aliases that will be
1380 translated into the latest built image that fits the description.
1381 Defaults to latest.
1382 The artifact is one of a number of image or artifact aliases used by
1383 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 15:38:571384
1385 Kwargs:
Yu-Ju Hong51495eb2013-12-13 01:08:431386 for_update: {true|false}
Amin Hassanie9ffb862019-09-26 00:10:401387 if true, prepares the update payloads for the image,
Yu-Ju Hong51495eb2013-12-13 01:08:431388 and returns the update uri to pass to the
1389 update_engine_client.
joychen3cb228e2013-06-12 19:13:131390 return_dir: {true|false}
1391 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-13 01:08:431392 relative_path: {true|false}
1393 if set to true, returns the relative path to the payload
1394 directory from static_dir.
joychen3cb228e2013-06-12 19:13:131395 Example URL:
joycheneaf4cfc2013-07-02 15:38:571396 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 19:13:131397 or
joycheneaf4cfc2013-07-02 15:38:571398 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 19:13:131399
1400 Returns:
Yu-Ju Hong51495eb2013-12-13 01:08:431401 If |for_update|, returns a redirect to the image or update file
1402 on the devserver. E.g.,
1403 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
1404 chromium-test-image.bin
1405 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1406 http://host:port/static/x86-generic-release/R26-4000.0.0/
1407 If |relative_path| is true, return a relative path the folder where the
1408 payloads are. E.g.,
1409 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 19:13:131410 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251411 if is_deprecated_server():
1412 raise DeprecatedRPCError('xbuddy')
1413
Chris Sosa75490802013-10-01 00:21:451414 boolean_string = kwargs.get('for_update')
1415 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-13 01:08:431416 boolean_string = kwargs.get('return_dir')
1417 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1418 boolean_string = kwargs.get('relative_path')
1419 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 21:30:301420
Yu-Ju Hong51495eb2013-12-13 01:08:431421 if return_dir and relative_path:
Amin Hassani722e0962019-11-15 23:45:311422 raise DevServerHTTPError(
Amin Hassanid4e35392019-10-03 18:02:441423 http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 07:31:301424 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-10-01 00:21:451425
1426 # For updates, we optimize downloading of test images.
1427 file_name = None
1428 build_id = None
1429 if for_update:
1430 try:
Yu-Ju Hong1bdb7a92014-04-10 23:02:111431 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-10-01 00:21:451432 except build_artifact.ArtifactDownloadError:
1433 build_id = None
1434
1435 if not build_id:
1436 build_id, file_name = self._xbuddy.Get(args)
1437
Yu-Ju Hong51495eb2013-12-13 01:08:431438 if for_update:
Amin Hassanie9ffb862019-09-26 00:10:401439 _Log('Payloads requested.')
Yu-Ju Hong51495eb2013-12-13 01:08:431440 # Forces payload to be in cache and symlinked into build_id dir.
Amin Hassanie9ffb862019-09-26 00:10:401441 updater.GetUpdateForLabel(build_id)
Yu-Ju Hong51495eb2013-12-13 01:08:431442
1443 response = None
1444 if return_dir:
1445 response = os.path.join(cherrypy.request.base, 'static', build_id)
1446 _Log('Directory requested, returning: %s', response)
1447 elif relative_path:
1448 response = build_id
1449 _Log('Relative path requested, returning: %s', response)
1450 elif for_update:
1451 response = os.path.join(cherrypy.request.base, 'update', build_id)
1452 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 19:13:131453 else:
Yu-Ju Hong51495eb2013-12-13 01:08:431454 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 21:30:301455 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-13 01:08:431456 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 21:30:301457 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 19:13:131458
Yu-Ju Hong51495eb2013-12-13 01:08:431459 return response
1460
joychen3cb228e2013-06-12 19:13:131461 @cherrypy.expose
1462 def xbuddy_list(self):
1463 """Lists the currently available images & time since last access.
1464
Gilad Arnold452fd272014-02-04 19:09:281465 Returns:
1466 A string representation of a list of tuples [(build_id, time since last
1467 access),...]
joychen3cb228e2013-06-12 19:13:131468 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251469 if is_deprecated_server():
1470 raise DeprecatedRPCError('xbuddy')
1471
joychen3cb228e2013-06-12 19:13:131472 return self._xbuddy.List()
1473
1474 @cherrypy.expose
1475 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 19:09:281476 """Returns the number of images cached by xBuddy."""
Sanika Kulkarnid4496fd2020-02-05 01:26:251477 if is_deprecated_server():
1478 raise DeprecatedRPCError('xbuddy_capacity')
1479
joychen3cb228e2013-06-12 19:13:131480 return self._xbuddy.Capacity()
1481
1482 @cherrypy.expose
Chris Sosa7c931362010-10-12 02:49:011483 def index(self):
Gilad Arnoldf8f769f2012-09-24 15:43:011484 """Presents a welcome message and documentation links."""
Sanika Kulkarnid4496fd2020-02-05 01:26:251485 if is_deprecated_server():
1486 raise DeprecatedRPCError('index')
1487
Congbin Guo6bc32182019-08-21 00:54:301488 html_template = (
1489 'Welcome to the Dev Server!<br>\n'
1490 '<br>\n'
1491 'Here are the available methods, click for documentation:<br>\n'
1492 '<br>\n'
1493 '%s')
1494
1495 exposed_methods = []
1496 for app in cherrypy.tree.apps.values():
1497 exposed_methods += _FindExposedMethods(
1498 app.root, app.script_name.lstrip('/'),
1499 unlisted=self._UNLISTED_METHODS)
1500
1501 return html_template % '<br>\n'.join(
1502 ['<a href=doc/%s>%s</a>' % (name, name)
1503 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 15:43:011504
1505 @cherrypy.expose
1506 def doc(self, *args):
1507 """Shows the documentation for available methods / URLs.
1508
Amin Hassani08e42d22019-06-03 07:31:301509 Examples:
Gilad Arnoldf8f769f2012-09-24 15:43:011510 http://myhost/doc/update
1511 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251512 if is_deprecated_server():
1513 raise DeprecatedRPCError('doc')
1514
Gilad Arnoldd5ebaaa2012-10-02 18:52:381515 name = '/'.join(args)
Congbin Guo6bc32182019-08-21 00:54:301516 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 15:43:011517 if not method:
Amin Hassanid4e35392019-10-03 18:02:441518 raise DevServerError("No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 15:43:011519 if not method.__doc__:
Amin Hassanid4e35392019-10-03 18:02:441520 raise DevServerError("No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 15:43:011521 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-12 02:49:011522
Dale Curtisc9aaf3a2011-08-09 22:47:401523 @cherrypy.expose
Amin Hassani6eec8792020-01-09 22:06:481524 def update(self, *args, **kwargs):
Gilad Arnoldf8f769f2012-09-24 15:43:011525 """Handles an update check from a Chrome OS client.
1526
1527 The HTTP request should contain the standard Omaha-style XML blob. The URL
1528 line may contain an additional intermediate path to the update payload.
1529
joychen121fc9b2013-08-02 21:30:301530 This request can be handled in one of 4 ways, depending on the devsever
1531 settings and intermediate path.
joychenb0dfe552013-07-30 17:02:061532
Amin Hassanie9ffb862019-09-26 00:10:401533 1. No intermediate path. DEPRECATED
joychen121fc9b2013-08-02 21:30:301534
1535 2. Path explicitly invokes XBuddy
1536 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1537 with 'xbuddy'. This path is then used to acquire an image binary for the
1538 devserver to generate an update payload from. Devserver then serves this
1539 payload.
1540
1541 3. Path is left for the devserver to interpret.
1542 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1543 to generate a payload from the test image in that directory and serve it.
1544
joychen121fc9b2013-08-02 21:30:301545 Examples:
joychen121fc9b2013-08-02 21:30:301546 2. Explicitly invoke xbuddy
1547 update_engine_client --omaha_url=
1548 http://myhost/update/xbuddy/remote/board/version/dev
1549 This would go to GS to download the dev image for the board, from which
1550 the devserver would generate a payload to serve.
1551
1552 3. Give a path for devserver to interpret
1553 update_engine_client --omaha_url=http://myhost/update/some/random/path
1554 This would attempt, in order to:
1555 a) Generate an update from a test image binary if found in
1556 static_dir/some/random/path.
1557 b) Serve an update payload found in static_dir/some/random/path.
1558 c) Hope that some/random/path takes the form "board/version" and
1559 and attempt to download an update payload for that board/version
1560 from GS.
Gilad Arnoldf8f769f2012-09-24 15:43:011561 """
joychen121fc9b2013-08-02 21:30:301562 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 21:47:021563 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-12 02:49:011564 data = cherrypy.request.rfile.read(body_length)
Amin Hassani6aa075c2020-02-21 18:36:441565
Amin Hassani6eec8792020-01-09 22:06:481566 return updater.HandleUpdatePing(data, label, **kwargs)
Chris Sosa0356d3b2010-09-16 22:46:221567
Dan Shif5ce2de2013-04-25 23:06:321568
Chris Sosadbc20082012-12-10 21:39:111569def _CleanCache(cache_dir, wipe):
1570 """Wipes any excess cached items in the cache_dir.
1571
1572 Args:
1573 cache_dir: the directory we are wiping from.
1574 wipe: If True, wipe all the contents -- not just the excess.
1575 """
1576 if wipe:
1577 # Clear the cache and exit on error.
1578 cmd = 'rm -rf %s/*' % cache_dir
1579 if os.system(cmd) != 0:
1580 _Log('Failed to clear the cache with %s' % cmd)
1581 sys.exit(1)
1582 else:
1583 # Clear all but the last N cached updates
1584 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1585 (cache_dir, CACHED_ENTRIES))
1586 if os.system(cmd) != 0:
1587 _Log('Failed to clean up old delta cache files with %s' % cmd)
1588 sys.exit(1)
1589
1590
Chris Sosa3ae4dc12013-03-29 18:47:001591def _AddTestingOptions(parser):
1592 group = optparse.OptionGroup(
1593 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1594 'developers writing integration tests utilizing the devserver. They are '
1595 'not intended to be really used outside the scope of someone '
1596 'knowledgable about the test.')
1597 group.add_option('--exit',
1598 action='store_true',
Amin Hassanie9ffb862019-09-26 00:10:401599 help='do not start the server (yet clear cache)')
Chris Sosa3ae4dc12013-03-29 18:47:001600 group.add_option('--host_log',
1601 action='store_true', default=False,
1602 help='record history of host update events (/api/hostlog)')
1603 group.add_option('--max_updates',
Gabe Black3b567202015-09-23 21:07:591604 metavar='NUM', default=-1, type='int',
Chris Sosa3ae4dc12013-03-29 18:47:001605 help='maximum number of update checks handled positively '
1606 '(default: unlimited)')
Chris Sosa3ae4dc12013-03-29 18:47:001607 group.add_option('--proxy_port',
1608 metavar='PORT', default=None, type='int',
1609 help='port to have the client connect to -- basically the '
1610 'devserver lies to the update to tell it to get the payload '
1611 'from a different port that will proxy the request back to '
1612 'the devserver. The proxy must be managed outside the '
1613 'devserver.')
Chris Sosa3ae4dc12013-03-29 18:47:001614 parser.add_option_group(group)
1615
1616
1617def _AddUpdateOptions(parser):
1618 group = optparse.OptionGroup(
1619 parser, 'Autoupdate Options', 'These options can be used to change '
Amin Hassanie9ffb862019-09-26 00:10:401620 'how the devserver serve update payloads. Please '
Chris Sosa3ae4dc12013-03-29 18:47:001621 'note that all of these option affect how a payload is generated and so '
1622 'do not work in archive-only mode.')
Amin Hassani6eec8792020-01-09 22:06:481623 # TODO(crbug/1004487): Deprecate critical_update.
Chris Sosa3ae4dc12013-03-29 18:47:001624 group.add_option('--critical_update',
1625 action='store_true', default=False,
1626 help='Present update payload as critical')
Chris Sosa3ae4dc12013-03-29 18:47:001627 group.add_option('--payload',
1628 metavar='PATH',
1629 help='use the update payload from specified directory '
1630 '(update.gz).')
Chris Sosa3ae4dc12013-03-29 18:47:001631 parser.add_option_group(group)
1632
1633
1634def _AddProductionOptions(parser):
1635 group = optparse.OptionGroup(
1636 parser, 'Advanced Server Options', 'These options can be used to changed '
1637 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 18:47:001638 group.add_option('--clear_cache',
1639 action='store_true', default=False,
1640 help='At startup, removes all cached entries from the'
Amin Hassanid4e35392019-10-03 18:02:441641 "devserver's cache.")
Chris Sosa3ae4dc12013-03-29 18:47:001642 group.add_option('--logfile',
1643 metavar='PATH',
1644 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 20:24:551645 group.add_option('--pidfile',
1646 metavar='PATH',
1647 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 19:04:131648 group.add_option('--portfile',
1649 metavar='PATH',
1650 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 18:47:001651 group.add_option('--production',
1652 action='store_true', default=False,
1653 help='have the devserver use production values when '
1654 'starting up. This includes using more threads and '
1655 'performing less logging.')
1656 parser.add_option_group(group)
1657
1658
Paul Hobbsef4e0702016-06-28 00:01:421659def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 18:05:191660 """Create a LogHandler instance used to log all messages."""
1661 hdlr_cls = handlers.TimedRotatingFileHandler
1662 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-31 03:00:091663 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 18:05:191664 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 20:24:551665 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 18:05:191666 return hdlr
1667
1668
Chris Sosacde6bf42012-06-01 01:36:391669def main():
Chris Sosa3ae4dc12013-03-29 18:47:001670 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 21:47:021671 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 23:39:341672
1673 # get directory that the devserver is run from
1674 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 16:17:231675 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 23:39:341676 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 17:09:421677 metavar='PATH',
joychen84d13772013-08-06 16:17:231678 default=default_static_dir,
joychened64b222013-06-21 23:39:341679 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 17:09:421680 parser.add_option('--port',
1681 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 21:13:281682 help=('port for the dev server to use; if zero, binds to '
1683 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 17:09:421684 parser.add_option('-t', '--test_image',
1685 action='store_true',
joychen121fc9b2013-08-02 21:30:301686 help='Deprecated.')
joychen5260b9a2013-07-16 21:48:011687 parser.add_option('-x', '--xbuddy_manage_builds',
1688 action='store_true',
1689 default=False,
1690 help='If set, allow xbuddy to manage images in'
1691 'build/images.')
Dan Shi72b16132015-10-08 19:10:331692 parser.add_option('-a', '--android_build_credential',
1693 default=None,
1694 help='Path to a json file which contains the credential '
1695 'needed to access Android builds.')
Sanika Kulkarnid4496fd2020-02-05 01:26:251696 parser.add_option('--infra_removal',
1697 action='store_true', default=False,
1698 help='If option is present, some RPCs will be disabled to '
1699 'help with infra removal efforts. See '
1700 'go/devserver-deprecation')
Chris Sosa3ae4dc12013-03-29 18:47:001701 _AddProductionOptions(parser)
1702 _AddUpdateOptions(parser)
1703 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-12 02:49:011704 (options, _) = parser.parse_args()
[email protected]21a5ca32009-11-04 18:23:231705
J. Richard Barnette3d977b82013-04-23 18:05:191706 # Handle options that must be set globally in cherrypy. Do this
1707 # work up front, because calls to _Log() below depend on this
1708 # initialization.
1709 if options.production:
1710 cherrypy.config.update({'environment': 'production'})
Sanika Kulkarnid4496fd2020-02-05 01:26:251711 cherrypy.config.update({'infra_removal': options.infra_removal})
J. Richard Barnette3d977b82013-04-23 18:05:191712 if not options.logfile:
1713 cherrypy.config.update({'log.screen': True})
1714 else:
1715 cherrypy.config.update({'log.error_file': '',
1716 'log.access_file': ''})
Paul Hobbsef4e0702016-06-28 00:01:421717 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 18:05:191718 # Pylint can't seem to process these two calls properly
1719 # pylint: disable=E1101
1720 cherrypy.log.access_log.addHandler(hdlr)
1721 cherrypy.log.error_log.addHandler(hdlr)
1722 # pylint: enable=E1101
1723
joychened64b222013-06-21 23:39:341724 # set static_dir, from which everything will be served
joychen84d13772013-08-06 16:17:231725 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 22:46:221726
joychened64b222013-06-21 23:39:341727 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 18:05:191728 # If our devserver is only supposed to serve payloads, we shouldn't be
1729 # mucking with the cache at all. If the devserver hadn't previously
1730 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 18:14:071731 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 21:39:111732 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 20:12:171733 else:
1734 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-17 01:36:141735
Chris Sosadbc20082012-12-10 21:39:111736 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 23:39:341737 _Log('Serving from %s' % options.static_dir)
[email protected]21a5ca32009-11-04 18:23:231738
Amin Hassanie9ffb862019-09-26 00:10:401739 _xbuddy = xbuddy.XBuddy(manage_builds=options.xbuddy_manage_builds,
joychen121fc9b2013-08-02 21:30:301740 static_dir=options.static_dir)
Chris Sosa75490802013-10-01 00:21:451741 if options.clear_cache and options.xbuddy_manage_builds:
1742 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 21:30:301743
Chris Sosa6a3697f2013-01-30 00:44:431744 # We allow global use here to share with cherrypy classes.
1745 # pylint: disable=W0603
Chris Sosacde6bf42012-06-01 01:36:391746 global updater
Andrew de los Reyes52620802010-04-12 20:40:071747 updater = autoupdate.Autoupdate(
joychen121fc9b2013-08-02 21:30:301748 _xbuddy,
joychened64b222013-06-21 23:39:341749 static_dir=options.static_dir,
Gilad Arnold0c9c8602012-10-03 06:58:581750 payload_path=options.payload,
Don Garrett0ad09372010-12-07 00:20:301751 proxy_port=options.proxy_port,
Satoru Takabayashid733cbe2011-11-15 17:36:321752 critical_update=options.critical_update,
Gilad Arnolda564b4b2012-10-04 17:32:441753 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 19:52:231754 host_log=options.host_log,
Chris Sosa0f1ec842011-02-15 00:33:221755 )
Chris Sosa7c931362010-10-12 02:49:011756
J. Richard Barnette3d977b82013-04-23 18:05:191757 if options.exit:
1758 return
Chris Sosa2f1c41e2012-07-10 21:32:331759
joychen3cb228e2013-06-12 19:13:131760 dev_server = DevServerRoot(_xbuddy)
Congbin Guo3afae6c2019-08-13 23:29:421761 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 19:13:131762
Amin Hassanic5af4262019-11-13 21:37:201763 # Patch CherryPy to support binding to any available port (--port=0) only for
1764 # cherrypy versions smaller or equal to 3.2.2.
1765 #
1766 # TODO(crbug/1006305): Remove this once we have deprecated omaha_devserver.py
1767 # in the autotests as that is the only use case.
1768 #
1769 # pylint: disable=no-member
1770 if (distutils.version.StrictVersion(cherrypy.__version__) <=
1771 distutils.version.StrictVersion('3.2.2')):
1772 cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy)
1773 # pylint: enable=no-member
1774
Chris Sosa855b8932013-08-21 20:24:551775 if options.pidfile:
1776 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1777
Gilad Arnold11fbef42014-02-10 19:04:131778 if options.portfile:
1779 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1780
Dan Shiafd5c6c2016-01-07 18:27:031781 if (options.android_build_credential and
1782 os.path.exists(options.android_build_credential)):
1783 try:
1784 with open(options.android_build_credential) as f:
1785 android_build.BuildAccessor.credential_info = json.load(f)
1786 except ValueError as e:
1787 _Log('Failed to load the android build credential: %s. Error: %s.' %
1788 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 23:29:421789
1790 cherrypy.tree.mount(health_checker_app, '/check_health',
1791 config=health_checker.get_config())
joychen3cb228e2013-06-12 19:13:131792 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-06-01 01:36:391793
1794
1795if __name__ == '__main__':
1796 main()