blob: ed023095e9350ddf256305ed79ab88df861dd484 [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
Gilad Arnold55a2a372012-10-02 16:46:3227import json
David Riley2fcb0122017-11-02 18:25:3928import optparse # pylint: disable=deprecated-module
[email protected]ded22402009-10-26 22:36:2129import os
Scott Zawalski4647ce62012-01-03 22:17:2830import re
Simran Basi4baad082013-02-14 21:39:1831import shutil
Mandeep Singh Baines38dcdda2012-12-08 01:55:3332import socket
Chris Masone816e38c2012-05-02 19:22:3633import subprocess
J. Richard Barnette3d977b82013-04-23 18:05:1934import sys
Chris Masone816e38c2012-05-02 19:22:3635import tempfile
Dan Shi59ae7092013-06-04 21:37:2736import threading
Gilad Arnoldd5ebaaa2012-10-02 18:52:3837import types
J. Richard Barnette3d977b82013-04-23 18:05:1938from logging import handlers
39
Amin Hassanid4e35392019-10-03 18:02:4440from six.moves import http_client
41
42# pylint: disable=no-name-in-module, import-error
J. Richard Barnette3d977b82013-04-23 18:05:1943import cherrypy
Chris Sosa855b8932013-08-21 20:24:5544from cherrypy import _cplogging as cplogging
Amin Hassanid4e35392019-10-03 18:02:4445from cherrypy.process import plugins
46# pylint: enable=no-name-in-module, import-error
[email protected]ded22402009-10-26 22:36:2147
Achuith Bhandarkar662fb722019-10-31 23:12:4948import autoupdate
49import cherrypy_ext
Achuith Bhandarkar662fb722019-10-31 23:12:4950import health_checker
51
Richard Barnettedf35c322017-08-19 00:02:1352# This must happen before any local modules get a chance to import
53# anything from chromite. Otherwise, really bad things will happen, and
54# you will _not_ understand why.
Congbin Guo3afae6c2019-08-13 23:29:4255import setup_chromite # pylint: disable=unused-import
Achuith Bhandarkar662fb722019-10-31 23:12:4956from chromite.lib.xbuddy import android_build
57from chromite.lib.xbuddy import artifact_info
58from chromite.lib.xbuddy import build_artifact
59from chromite.lib.xbuddy import cherrypy_log_util
60from chromite.lib.xbuddy import common_util
61from chromite.lib.xbuddy import devserver_constants
62from chromite.lib.xbuddy import downloader
63from chromite.lib.xbuddy import xbuddy
Gilad Arnoldc65330c2012-09-20 22:17:4864
Gilad Arnoldc65330c2012-09-20 22:17:4865# Module-local log function.
Chris Sosa6a3697f2013-01-30 00:44:4366def _Log(message, *args):
Achuith Bhandarkar662fb722019-10-31 23:12:4967 return cherrypy_log_util.LogWithTag('DEVSERVER', message, *args)
Frank Farzan40160872011-12-13 02:39:1868
Chris Sosa417e55d2011-01-26 00:40:4869CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-17 01:36:1470
Simran Basi4baad082013-02-14 21:39:1871TELEMETRY_FOLDER = 'telemetry_src'
72TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
73 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 23:40:0474 'dep-chrome_test.tar.bz2',
75 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 21:39:1876
Chris Sosa0356d3b2010-09-16 22:46:2277# Sets up global to share between classes.
[email protected]21a5ca32009-11-04 18:23:2378updater = None
[email protected]ded22402009-10-26 22:36:2179
xixuan3d48bff2017-01-31 03:00:0980# Log rotation parameters. These settings correspond to twice a day once
81# devserver is started, with about two weeks (28 backup files) of old logs
82# kept for backup.
J. Richard Barnette3d977b82013-04-23 18:05:1983#
xixuan3d48bff2017-01-31 03:00:0984# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 18:05:1985# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-31 03:00:0986_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 23:29:4287_LOG_ROTATION_INTERVAL = 12 # hours
88_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-13 02:39:1889
Sanika Kulkarnid4496fd2020-02-05 01:26:2590# Error msg for deprecated RPC usage.
91DEPRECATED_RPC_ERROR_MSG = ('The %s RPC has been deprecated. Usage of this '
92 'RPC is discouraged. Please go to '
93 'go/devserver-deprecation for more information.')
94
xixuan52c2fba2016-05-21 00:02:4895
Amin Hassanid4e35392019-10-03 18:02:4496class DevServerError(Exception):
97 """Exception class used by DevServer."""
98
99
Sanika Kulkarnid4496fd2020-02-05 01:26:25100class DeprecatedRPCError(DevServerError):
101 """Exception class used when an RPC is deprecated but is still being used."""
102
103 def __init__(self, rpc_name):
104 """Constructor for DeprecatedRPCError class.
105
106 :param rpc_name: (str) name of the RPC that has been deprecated.
107 """
Amin Hassani6ecda232020-03-10 02:03:23108 super(DeprecatedRPCError, self).__init__(
109 DEPRECATED_RPC_ERROR_MSG % rpc_name)
Sanika Kulkarnid4496fd2020-02-05 01:26:25110 self.rpc_name = rpc_name
111
112
Amin Hassani722e0962019-11-15 23:45:31113class DevServerHTTPError(cherrypy.HTTPError):
114 """Exception class to log the HTTPResponse before routing it to cherrypy."""
115 def __init__(self, status, message):
116 """CherryPy error with logging.
117
118 Args:
119 status: HTTPResponse status.
120 message: Message associated with the response.
121 """
122 cherrypy.HTTPError.__init__(self, status, message)
123 _Log('HTTPError status: %s message: %s', status, message)
124
125
Gabe Black3b567202015-09-23 21:07:59126def _canonicalize_archive_url(archive_url):
127 """Canonicalizes archive_url strings.
128
129 Raises:
130 DevserverError: if archive_url is not set.
131 """
132 if archive_url:
133 if not archive_url.startswith('gs://'):
Amin Hassanid4e35392019-10-03 18:02:44134 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14135 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 21:07:59136
137 return archive_url.rstrip('/')
138 else:
Amin Hassanid4e35392019-10-03 18:02:44139 raise DevServerError('Must specify an archive_url in the request')
Gabe Black3b567202015-09-23 21:07:59140
141
142def _canonicalize_local_path(local_path):
143 """Canonicalizes |local_path| strings.
144
145 Raises:
146 DevserverError: if |local_path| is not set.
147 """
148 # Restrict staging of local content to only files within the static
149 # directory.
150 local_path = os.path.abspath(local_path)
151 if not local_path.startswith(updater.static_dir):
Amin Hassanid4e35392019-10-03 18:02:44152 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14153 'Local path %s must be a subdirectory of the static'
154 ' directory: %s' % (local_path, updater.static_dir))
Gabe Black3b567202015-09-23 21:07:59155
156 return local_path.rstrip('/')
157
158
159def _get_artifacts(kwargs):
160 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
161
162 Raises:
163 DevserverError if no artifacts would be returned.
164 """
165 artifacts = kwargs.get('artifacts')
166 files = kwargs.get('files')
167 if not artifacts and not files:
Amin Hassanid4e35392019-10-03 18:02:44168 raise DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 21:07:59169
170 # Note we NEED to coerce files to a string as we get raw unicode from
171 # cherrypy and we treat files as strings elsewhere in the code.
172 return (str(artifacts).split(',') if artifacts else [],
173 str(files).split(',') if files else [])
174
175
Dan Shi61305df2015-10-26 23:52:35176def _is_android_build_request(kwargs):
177 """Check if a devserver call is for Android build, based on the arguments.
178
179 This method exams the request's arguments (os_type) to determine if the
180 request is for Android build. If os_type is set to `android`, returns True.
181 If os_type is not set or has other values, returns False.
182
183 Args:
184 kwargs: Keyword arguments for the request.
185
186 Returns:
187 True if the request is for Android build. False otherwise.
188 """
189 os_type = kwargs.get('os_type', None)
190 return os_type == 'android'
191
192
Gabe Black3b567202015-09-23 21:07:59193def _get_downloader(kwargs):
194 """Returns the downloader based on passed in arguments.
195
196 Args:
Amin Hassani08e42d22019-06-03 07:31:30197 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 21:07:59198 """
199 local_path = kwargs.get('local_path')
200 if local_path:
201 local_path = _canonicalize_local_path(local_path)
202
203 dl = None
204 if local_path:
Prathmesh Prabhu58d08932018-01-19 23:08:19205 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
206 dl = downloader.LocalDownloader(updater.static_dir, local_path,
207 delete_source=delete_source)
Gabe Black3b567202015-09-23 21:07:59208
Dan Shi61305df2015-10-26 23:52:35209 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 21:07:59210 archive_url = kwargs.get('archive_url')
211 if not archive_url and not local_path:
Amin Hassanid4e35392019-10-03 18:02:44212 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14213 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 21:07:59214 if archive_url and local_path:
Amin Hassanid4e35392019-10-03 18:02:44215 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14216 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 21:07:59217 if not dl:
218 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 19:56:30219 dl = downloader.GoogleStorageDownloader(
220 updater.static_dir, archive_url,
221 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
222 archive_url))
Gabe Black3b567202015-09-23 21:07:59223 elif not dl:
224 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 19:10:33225 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 23:52:35226 build_id = kwargs.get('build_id', None)
227 if not target or not branch or not build_id:
Amin Hassanid4e35392019-10-03 18:02:44228 raise DevServerError('target, branch, build ID must all be specified for '
229 'downloading Android build.')
Dan Shi72b16132015-10-08 19:10:33230 dl = downloader.AndroidBuildDownloader(updater.static_dir, branch, build_id,
231 target)
Gabe Black3b567202015-09-23 21:07:59232
233 return dl
234
235
236def _get_downloader_and_factory(kwargs):
237 """Returns the downloader and artifact factory based on passed in arguments.
238
239 Args:
Amin Hassani08e42d22019-06-03 07:31:30240 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 21:07:59241 """
242 artifacts, files = _get_artifacts(kwargs)
243 dl = _get_downloader(kwargs)
244
Achuith Bhandarkar2a1fcd82019-10-18 00:45:58245 if (isinstance(dl, (downloader.GoogleStorageDownloader,
246 downloader.LocalDownloader))):
Gabe Black3b567202015-09-23 21:07:59247 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 19:10:33248 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 21:07:59249 factory_class = build_artifact.AndroidArtifactFactory
250 else:
Amin Hassanid4e35392019-10-03 18:02:44251 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14252 'Unrecognized value for downloader type: %s' % type(dl))
Gabe Black3b567202015-09-23 21:07:59253
254 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
255
256 return dl, factory
257
258
Scott Zawalski4647ce62012-01-03 22:17:28259def _LeadingWhiteSpaceCount(string):
260 """Count the amount of leading whitespace in a string.
261
262 Args:
263 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-08 02:21:26264
Scott Zawalski4647ce62012-01-03 22:17:28265 Returns:
266 number of white space chars before characters start.
267 """
Gabe Black3b567202015-09-23 21:07:59268 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 22:17:28269 if matched:
270 return len(matched.group())
271
272 return 0
273
274
275def _PrintDocStringAsHTML(func):
276 """Make a functions docstring somewhat HTML style.
277
278 Args:
279 func: The function to return the docstring from.
Don Garrettf84631a2014-01-08 02:21:26280
Scott Zawalski4647ce62012-01-03 22:17:28281 Returns:
282 A string that is somewhat formated for a web browser.
283 """
284 # TODO(scottz): Make this parse Args/Returns in a prettier way.
285 # Arguments could be bolded and indented etc.
286 html_doc = []
287 for line in func.__doc__.splitlines():
288 leading_space = _LeadingWhiteSpaceCount(line)
289 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 18:26:55290 line = ' ' * leading_space + line
Scott Zawalski4647ce62012-01-03 22:17:28291
292 html_doc.append('<BR>%s' % line)
293
294 return '\n'.join(html_doc)
295
296
Simran Basief83d6a2014-08-28 21:32:01297def _GetUpdateTimestampHandler(static_dir):
298 """Returns a handler to update directory staged.timestamp.
299
300 This handler resets the stage.timestamp whenever static content is accessed.
301
302 Args:
303 static_dir: Directory from which static content is being staged.
304
305 Returns:
Amin Hassani08e42d22019-06-03 07:31:30306 A cherrypy handler to update the timestamp of accessed content.
Simran Basief83d6a2014-08-28 21:32:01307 """
308 def UpdateTimestampHandler():
309 if not '404' in cherrypy.response.status:
310 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
311 cherrypy.request.path_info)
312 if build_match:
313 build_dir = os.path.join(static_dir, build_match.group('build'))
314 downloader.Downloader.TouchTimestampForStaged(build_dir)
315 return UpdateTimestampHandler
316
317
Chris Sosa7c931362010-10-12 02:49:01318def _GetConfig(options):
319 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-08 01:55:33320
Mandeep Singh Baines38dcdda2012-12-08 01:55:33321 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 23:14:26322 # Fall back to IPv4 when python is not configured with IPv6.
323 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-08 01:55:33324 socket_host = '0.0.0.0'
325
Simran Basief83d6a2014-08-28 21:32:01326 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
327 # on the on_end_resource hook. This hook is called once processing is
328 # complete and the response is ready to be returned.
329 cherrypy.tools.update_timestamp = cherrypy.Tool(
330 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
331
David Riley2fcb0122017-11-02 18:25:39332 base_config = {
333 'global': {
334 'server.log_request_headers': True,
335 'server.protocol_version': 'HTTP/1.1',
336 'server.socket_host': socket_host,
337 'server.socket_port': int(options.port),
338 'response.timeout': 6000,
339 'request.show_tracebacks': True,
340 'server.socket_timeout': 60,
341 'server.thread_pool': 2,
342 'engine.autoreload.on': False,
343 },
David Riley2fcb0122017-11-02 18:25:39344 '/build': {
345 'response.timeout': 100000,
346 },
347 '/update': {
348 # Gets rid of cherrypy parsing post file for args.
349 'request.process_request_body': False,
350 'response.timeout': 10000,
351 },
352 # Sets up the static dir for file hosting.
353 '/static': {
354 'tools.staticdir.dir': options.static_dir,
355 'tools.staticdir.on': True,
356 'response.timeout': 10000,
357 'tools.update_timestamp.on': True,
358 },
359 }
Chris Sosa5f118ef2012-07-12 18:37:50360 if options.production:
Alex Miller93beca52013-07-31 02:25:09361 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 18:12:52362
Chris Sosa7c931362010-10-12 02:49:01363 return base_config
[email protected]64244662009-11-12 00:52:08364
Darin Petkove17164a2010-08-11 20:24:41365
Gilad Arnoldd5ebaaa2012-10-02 18:52:38366def _GetRecursiveMemberObject(root, member_list):
367 """Returns an object corresponding to a nested member list.
368
369 Args:
370 root: the root object to search
371 member_list: list of nested members to search
Don Garrettf84631a2014-01-08 02:21:26372
Gilad Arnoldd5ebaaa2012-10-02 18:52:38373 Returns:
374 An object corresponding to the member name list; None otherwise.
375 """
376 for member in member_list:
377 next_root = root.__class__.__dict__.get(member)
378 if not next_root:
379 return None
380 root = next_root
381 return root
382
383
384def _IsExposed(name):
385 """Returns True iff |name| has an `exposed' attribute and it is set."""
386 return hasattr(name, 'exposed') and name.exposed
387
388
Congbin Guo6bc32182019-08-21 00:54:30389def _GetExposedMethod(nested_member):
Gilad Arnoldd5ebaaa2012-10-02 18:52:38390 """Returns a CherryPy-exposed method, if such exists.
391
392 Args:
Gilad Arnoldd5ebaaa2012-10-02 18:52:38393 nested_member: a slash-joined path to the nested member
Don Garrettf84631a2014-01-08 02:21:26394
Gilad Arnoldd5ebaaa2012-10-02 18:52:38395 Returns:
Congbin Guo6bc32182019-08-21 00:54:30396 A function object corresponding to the path defined by |nested_member| from
397 the app root object registered, if the function is exposed; None otherwise.
Gilad Arnoldd5ebaaa2012-10-02 18:52:38398 """
Congbin Guo6bc32182019-08-21 00:54:30399 for app in cherrypy.tree.apps.values():
400 # Use the 'index' function doc as the doc of the app.
401 if nested_member == app.script_name.lstrip('/'):
402 nested_member = 'index'
403
404 method = _GetRecursiveMemberObject(app.root, nested_member.split('/'))
405 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
406 return method
Gilad Arnoldd5ebaaa2012-10-02 18:52:38407
408
Gilad Arnold748c8322012-10-12 16:51:35409def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 18:52:38410 """Finds exposed CherryPy methods.
411
412 Args:
413 root: the root object for searching
414 prefix: slash-joined chain of members leading to current object
415 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-08 02:21:26416
Gilad Arnoldd5ebaaa2012-10-02 18:52:38417 Returns:
418 List of exposed URLs that are not unlisted.
419 """
420 method_list = []
Congbin Guo6bc32182019-08-21 00:54:30421 for member in root.__class__.__dict__.keys():
Gilad Arnoldd5ebaaa2012-10-02 18:52:38422 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 16:51:35423 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 18:52:38424 continue
425 member_obj = root.__class__.__dict__[member]
426 if _IsExposed(member_obj):
Amin Hassani08e42d22019-06-03 07:31:30427 if isinstance(member_obj, types.FunctionType):
Congbin Guo6bc32182019-08-21 00:54:30428 # Regard the app name as exposed "method" name if it exposed 'index'
429 # function.
430 if prefix and member == 'index':
431 method_list.append(prefix)
432 else:
433 method_list.append(prefixed_member)
Gilad Arnoldd5ebaaa2012-10-02 18:52:38434 else:
435 method_list += _FindExposedMethods(
436 member_obj, prefixed_member, unlisted)
437 return method_list
438
439
xixuan52c2fba2016-05-21 00:02:48440def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-12-01 00:48:20441 """Parse boolean arg from kwargs.
442
443 Args:
444 kwargs: the parameters to be checked.
445 key: the key to be parsed.
446
447 Returns:
448 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
449
450 Raises:
451 DevServerHTTPError if kwargs[key] is not a boolean variable.
452 """
xixuan52c2fba2016-05-21 00:02:48453 if key in kwargs:
454 if kwargs[key] == 'True':
455 return True
456 elif kwargs[key] == 'False':
457 return False
458 else:
Amin Hassani722e0962019-11-15 23:45:31459 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
460 'The value for key %s is not boolean.' % key)
xixuan52c2fba2016-05-21 00:02:48461 else:
462 return False
463
xixuan447ad9d2017-02-28 22:46:20464
xixuanac89ce82016-12-01 00:48:20465def _parse_string_arg(kwargs, key):
466 """Parse string arg from kwargs.
467
468 Args:
469 kwargs: the parameters to be checked.
470 key: the key to be parsed.
471
472 Returns:
473 The string value of kwargs[key], or None if key doesn't exist in kwargs.
474 """
475 if key in kwargs:
476 return kwargs[key]
477 else:
478 return None
479
xixuan447ad9d2017-02-28 22:46:20480
xixuanac89ce82016-12-01 00:48:20481def _build_uri_from_build_name(build_name):
482 """Get build url from a given build name.
483
484 Args:
485 build_name: the build name to be parsed, whose format is
486 'board/release_version'.
487
488 Returns:
489 The release_archive_url on Google Storage for this build name.
490 """
Amin Hassani08e42d22019-06-03 07:31:30491 # TODO(ahassani): This function doesn't seem to be used anywhere since its
492 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
493 # causing any runtime issues. So deprecate this in the future.
494 tokens = build_name.split('/')
495 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-21 00:02:48496
xixuan447ad9d2017-02-28 22:46:20497
Sanika Kulkarnid4496fd2020-02-05 01:26:25498def is_deprecated_server():
499 """Gets whether the devserver has deprecated RPCs."""
500 return cherrypy.config.get('infra_removal', False)
501
502
David Rochberg7c79a812011-01-19 19:24:45503class DevServerRoot(object):
Chris Sosa7c931362010-10-12 02:49:01504 """The Root Class for the Dev Server.
505
506 CherryPy works as follows:
507 For each method in this class, cherrpy interprets root/path
508 as a call to an instance of DevServerRoot->method_name. For example,
509 a call to http://myhost/build will call build. CherryPy automatically
510 parses http args and places them as keyword arguments in each method.
511 For paths http://myhost/update/dir1/dir2, you can use *args so that
512 cherrypy uses the update method and puts the extra paths in args.
513 """
Gilad Arnoldf8f769f2012-09-24 15:43:01514 # Method names that should not be listed on the index page.
515 _UNLISTED_METHODS = ['index', 'doc']
516
Dan Shi59ae7092013-06-04 21:37:27517 # Number of threads that devserver is staging images.
518 _staging_thread_count = 0
519 # Lock used to lock increasing/decreasing count.
520 _staging_thread_count_lock = threading.Lock()
521
joychen3cb228e2013-06-12 19:13:13522 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 22:20:41523 self._builder = None
Simran Basi4baad082013-02-14 21:39:18524 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 19:13:13525 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 19:24:45526
Congbin Guo3afae6c2019-08-13 23:29:42527 @property
528 def staging_thread_count(self):
529 """Get the staging thread count."""
530 return self._staging_thread_count
Dan Shiafd0e492015-05-27 21:23:51531
Dale Curtisc9aaf3a2011-08-09 22:47:40532 @cherrypy.expose
David Rochberg7c79a812011-01-19 19:24:45533 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-12 02:49:01534 """Builds the package specified."""
Sanika Kulkarnid4496fd2020-02-05 01:26:25535 if is_deprecated_server():
536 raise DeprecatedRPCError('build')
537
Nick Sanders7dcaa2e2011-08-04 22:20:41538 import builder
539 if self._builder is None:
540 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 19:24:45541 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-12 02:49:01542
Dale Curtisc9aaf3a2011-08-09 22:47:40543 @cherrypy.expose
Dan Shif8eb0d12013-08-02 00:52:06544 def is_staged(self, **kwargs):
545 """Check if artifacts have been downloaded.
546
Congbin Guo3afae6c2019-08-13 23:29:42547 Examples:
548 To check if autotest and test_suites are staged:
549 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
550 artifacts=autotest,test_suites
551
Amin Hassani08e42d22019-06-03 07:31:30552 Args:
Chris Sosa6b0c6172013-08-06 00:01:33553 async: True to return without waiting for download to complete.
554 artifacts: Comma separated list of named artifacts to download.
555 These are defined in artifact_info and have their implementation
556 in build_artifact.py.
557 files: Comma separated list of file artifacts to stage. These
558 will be available as is in the corresponding static directory with no
559 custom post-processing.
560
Congbin Guo3afae6c2019-08-13 23:29:42561 Returns:
562 True of all artifacts are staged.
Dan Shif8eb0d12013-08-02 00:52:06563 """
Gabe Black3b567202015-09-23 21:07:59564 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-19 03:39:09565 response = str(dl.IsStaged(factory))
566 _Log('Responding to is_staged %s request with %r', kwargs, response)
567 return response
Dan Shi59ae7092013-06-04 21:37:27568
Chris Sosa76e44b92013-01-31 20:11:38569 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 23:35:19570 def list_image_dir(self, **kwargs):
571 """Take an archive url and list the contents in its staged directory.
572
Amin Hassani08e42d22019-06-03 07:31:30573 Examples:
Prashanth Ba06d2d22014-03-07 23:35:19574 To list the contents of where this devserver should have staged
575 gs://image-archive/<board>-release/<build> call:
576 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
577
Congbin Guo3afae6c2019-08-13 23:29:42578 Args:
579 archive_url: Google Storage URL for the build.
580
Prashanth Ba06d2d22014-03-07 23:35:19581 Returns:
582 A string with information about the contents of the image directory.
583 """
Gabe Black3b567202015-09-23 21:07:59584 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 23:35:19585 try:
Gabe Black3b567202015-09-23 21:07:59586 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 23:35:19587 except build_artifact.ArtifactDownloadError as e:
588 return 'Cannot list the contents of staged artifacts. %s' % e
589 if not image_dir_contents:
Gabe Black3b567202015-09-23 21:07:59590 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 23:35:19591 return image_dir_contents
592
593 @cherrypy.expose
Chris Sosa76e44b92013-01-31 20:11:38594 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 21:07:59595 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 20:11:38596
Gabe Black3b567202015-09-23 21:07:59597 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 19:10:33598 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 21:07:59599 on the devserver. A call to this will attempt to cache non-specified
600 artifacts in the background for the given from the given URL following
601 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 20:11:38602 artifacts is explicitly defined in the build_artifact module.
603
604 These artifacts will then be available from the static/ sub-directory of
605 the devserver.
606
Amin Hassani08e42d22019-06-03 07:31:30607 Examples:
Chris Sosa76e44b92013-01-31 20:11:38608 To download the autotest and test suites tarballs:
609 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
610 artifacts=autotest,test_suites
611 To download the full update payload:
612 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
613 artifacts=full_payload
Chris Sosa6b0c6172013-08-06 00:01:33614 To download just a file called blah.bin:
615 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
616 files=blah.bin
Chris Sosa76e44b92013-01-31 20:11:38617
618 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 23:39:34619 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 20:11:38620
621 Note for this example, relative path is the archive_url stripped of its
622 basename i.e. path/ in the examples above. Specific example:
623
624 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
625
626 Will get staged to:
627
joychened64b222013-06-21 23:39:34628 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 23:29:42629
630 Args:
631 archive_url: Google Storage URL for the build.
632 local_path: Local path for the build.
633 delete_source: Only meaningful with local_path. bool to indicate if the
634 source files should be deleted. This is especially useful when staging
635 a file locally in resource constrained environments as it allows us to
636 move the relevant files locally instead of copying them.
637 async: True to return without waiting for download to complete.
638 artifacts: Comma separated list of named artifacts to download.
639 These are defined in artifact_info and have their implementation
640 in build_artifact.py.
641 files: Comma separated list of files to stage. These
642 will be available as is in the corresponding static directory with no
643 custom post-processing.
644 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 20:11:38645 """
Gabe Black3b567202015-09-23 21:07:59646 dl, factory = _get_downloader_and_factory(kwargs)
647
Dan Shi59ae7092013-06-04 21:37:27648 with DevServerRoot._staging_thread_count_lock:
649 DevServerRoot._staging_thread_count += 1
650 try:
Laurence Goodbyf5c958d2016-01-15 02:23:56651 boolean_string = kwargs.get('clean')
652 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
653 if clean and os.path.exists(dl.GetBuildDir()):
654 _Log('Removing %s' % dl.GetBuildDir())
655 shutil.rmtree(dl.GetBuildDir())
Achuith Bhandarkar2a1fcd82019-10-18 00:45:58656 is_async = kwargs.get('async', False)
657 dl.Download(factory, is_async=is_async)
Dan Shi59ae7092013-06-04 21:37:27658 finally:
659 with DevServerRoot._staging_thread_count_lock:
660 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 20:11:38661 return 'Success'
Chris Sosacde6bf42012-06-01 01:36:39662
663 @cherrypy.expose
Dan Shi2f136862016-02-11 23:38:38664 def locate_file(self, **kwargs):
665 """Get the path to the given file name.
666
667 This method looks up the given file name inside specified build artifacts.
668 One use case is to help caller to locate an apk file inside a build
669 artifact. The location of the apk file could be different based on the
670 branch and target.
671
672 Args:
673 file_name: Name of the file to look for.
674 artifacts: A list of artifact names to search for the file.
675
676 Returns:
677 Path to the file with the given name. It's relative to the folder for the
678 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 23:38:38679 """
Sanika Kulkarnid4496fd2020-02-05 01:26:25680 if is_deprecated_server():
681 raise DeprecatedRPCError('locate_file')
682
Dan Shi2f136862016-02-11 23:38:38683 dl, _ = _get_downloader_and_factory(kwargs)
684 try:
Joe Brennan1691f8e2017-03-15 22:53:36685 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 23:38:38686 artifacts = kwargs['artifacts']
687 except KeyError:
Amin Hassanid4e35392019-10-03 18:02:44688 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14689 '`file_name` and `artifacts` are required to search '
690 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 23:38:38691 build_path = dl.GetBuildDir()
692 for artifact in artifacts:
693 # Get the unzipped folder of the artifact. If it's not defined in
694 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
695 # directory directly.
696 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
697 artifact_path = os.path.join(build_path, folder)
698 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 22:53:36699 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 23:38:38700 return os.path.relpath(os.path.join(root, file_name), build_path)
Amin Hassanid4e35392019-10-03 18:02:44701 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14702 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 23:38:38703
704 @cherrypy.expose
Simran Basi4baad082013-02-14 21:39:18705 def setup_telemetry(self, **kwargs):
706 """Extracts and sets up telemetry
707
708 This method goes through the telemetry deps packages, and stages them on
709 the devserver to be used by the drones and the telemetry tests.
710
711 Args:
712 archive_url: Google Storage URL for the build.
713
714 Returns:
715 Path to the source folder for the telemetry codebase once it is staged.
716 """
Gabe Black3b567202015-09-23 21:07:59717 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 21:39:18718
Gabe Black3b567202015-09-23 21:07:59719 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 21:39:18720 deps_path = os.path.join(build_path, 'autotest/packages')
721 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
722 src_folder = os.path.join(telemetry_path, 'src')
723
724 with self._telemetry_lock_dict.lock(telemetry_path):
725 if os.path.exists(src_folder):
726 # Telemetry is already fully stage return
727 return src_folder
728
729 common_util.MkDirP(telemetry_path)
730
731 # Copy over the required deps tar balls to the telemetry directory.
732 for dep in TELEMETRY_DEPS:
733 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 23:40:04734 if not os.path.exists(dep_path):
735 # This dep does not exist (could be new), do not extract it.
736 continue
Simran Basi4baad082013-02-14 21:39:18737 try:
738 common_util.ExtractTarball(dep_path, telemetry_path)
739 except common_util.CommonUtilError as e:
740 shutil.rmtree(telemetry_path)
Amin Hassanid4e35392019-10-03 18:02:44741 raise DevServerError(str(e))
Simran Basi4baad082013-02-14 21:39:18742
743 # By default all the tarballs extract to test_src but some parts of
744 # the telemetry code specifically hardcoded to exist inside of 'src'.
745 test_src = os.path.join(telemetry_path, 'test_src')
746 try:
747 shutil.move(test_src, src_folder)
748 except shutil.Error:
749 # This can occur if src_folder already exists. Remove and retry move.
750 shutil.rmtree(src_folder)
Amin Hassanid4e35392019-10-03 18:02:44751 raise DevServerError(
Gabe Black3b567202015-09-23 21:07:59752 'Failure in telemetry setup for build %s. Appears that the '
753 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 21:39:18754
755 return src_folder
756
757 @cherrypy.expose
Chris Sosa76e44b92013-01-31 20:11:38758 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 19:22:36759 """Symbolicates a minidump using pre-downloaded symbols, returns it.
760
761 Callers will need to POST to this URL with a body of MIME-type
762 "multipart/form-data".
763 The body should include a single argument, 'minidump', containing the
764 binary-formatted minidump to symbolicate.
765
Chris Masone816e38c2012-05-02 19:22:36766 Args:
Chris Sosa76e44b92013-01-31 20:11:38767 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 19:22:36768 minidump: The binary minidump file to symbolicate.
769 """
Sanika Kulkarnid4496fd2020-02-05 01:26:25770 if is_deprecated_server():
771 raise DeprecatedRPCError('symbolicate_dump')
772
Chris Sosa76e44b92013-01-31 20:11:38773 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 21:39:25774 # Try debug.tar.xz first, then debug.tgz
775 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
776 kwargs['artifacts'] = artifact
777 dl = _get_downloader(kwargs)
778
779 try:
780 if self.stage(**kwargs) == 'Success':
781 break
782 except build_artifact.ArtifactDownloadError:
783 continue
784 else:
Amin Hassanid4e35392019-10-03 18:02:44785 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14786 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 20:11:38787
Chris Masone816e38c2012-05-02 19:22:36788 to_return = ''
789 with tempfile.NamedTemporaryFile() as local:
790 while True:
791 data = minidump.file.read(8192)
792 if not data:
793 break
794 local.write(data)
Chris Sosa76e44b92013-01-31 20:11:38795
Chris Masone816e38c2012-05-02 19:22:36796 local.flush()
Chris Sosa76e44b92013-01-31 20:11:38797
Gabe Black3b567202015-09-23 21:07:59798 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 20:11:38799
xixuanab744382017-04-27 17:41:27800 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 20:11:38801 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 17:41:27802 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 20:11:38803 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
804
Chris Masone816e38c2012-05-02 19:22:36805 to_return, error_text = stackwalk.communicate()
806 if stackwalk.returncode != 0:
Amin Hassanid4e35392019-10-03 18:02:44807 raise DevServerError(
Congbin Guo4132a272019-08-20 19:32:14808 "Can't generate stack trace: %s (rc=%d)" % (error_text,
809 stackwalk.returncode))
Chris Masone816e38c2012-05-02 19:22:36810
811 return to_return
812
813 @cherrypy.expose
Don Garrettf84631a2014-01-08 02:21:26814 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 19:31:36815 """Return a string representing the latest build for a given target.
816
817 Args:
818 target: The build target, typically a combination of the board and the
819 type of build e.g. x86-mario-release.
820 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
821 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-08 02:21:26822
Scott Zawalski16954532012-03-20 19:31:36823 Returns:
824 A string representation of the latest build if one exists, i.e.
825 R19-1993.0.0-a1-b1480.
826 An empty string if no latest could be found.
827 """
Don Garrettf84631a2014-01-08 02:21:26828 if not kwargs:
Scott Zawalski16954532012-03-20 19:31:36829 return _PrintDocStringAsHTML(self.latestbuild)
830
Don Garrettf84631a2014-01-08 02:21:26831 if 'target' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31832 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
833 'Error: target= is required!')
Dan Shi61305df2015-10-26 23:52:35834
835 if _is_android_build_request(kwargs):
836 branch = kwargs.get('branch', None)
837 target = kwargs.get('target', None)
838 if not target or not branch:
Amin Hassanid4e35392019-10-03 18:02:44839 raise DevServerError('Both target and branch must be specified to query'
840 ' for the latest Android build.')
Dan Shi61305df2015-10-26 23:52:35841 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
842
Scott Zawalski16954532012-03-20 19:31:36843 try:
Gilad Arnoldc65330c2012-09-20 22:17:48844 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-08 02:21:26845 updater.static_dir, kwargs['target'],
846 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 17:05:01847 except common_util.CommonUtilError as errmsg:
Amin Hassani722e0962019-11-15 23:45:31848 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
849 str(errmsg))
Scott Zawalski16954532012-03-20 19:31:36850
851 @cherrypy.expose
xixuan7efd0002016-04-14 22:34:01852 def list_suite_controls(self, **kwargs):
853 """Return a list of contents of all known control files.
854
855 Example URL:
856 To List all control files' content:
857 http://dev-server/list_suite_controls?suite_name=bvt&
858 build=daisy_spring-release/R29-4279.0.0
859
860 Args:
861 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
862 suite_name: List the control files belonging to that suite.
863
864 Returns:
Dan Shia1cd6522016-04-18 23:07:21865 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 22:34:01866 """
Sanika Kulkarnid4496fd2020-02-05 01:26:25867 if is_deprecated_server():
868 raise DeprecatedRPCError('list_suite_controls')
869
xixuan7efd0002016-04-14 22:34:01870 if not kwargs:
871 return _PrintDocStringAsHTML(self.controlfiles)
872
873 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31874 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
875 'Error: build= is required!')
xixuan7efd0002016-04-14 22:34:01876
877 if 'suite_name' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31878 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
879 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 22:34:01880
881 control_file_list = [
882 line.rstrip() for line in common_util.GetControlFileListForSuite(
883 updater.static_dir, kwargs['build'],
884 kwargs['suite_name']).splitlines()]
885
Dan Shia1cd6522016-04-18 23:07:21886 control_file_content_dict = {}
xixuan7efd0002016-04-14 22:34:01887 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 23:07:21888 control_file_content_dict[control_path] = (common_util.GetControlFile(
xixuan7efd0002016-04-14 22:34:01889 updater.static_dir, kwargs['build'], control_path))
890
Dan Shia1cd6522016-04-18 23:07:21891 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 22:34:01892
893 @cherrypy.expose
Don Garrettf84631a2014-01-08 02:21:26894 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 22:17:28895 """Return a control file or a list of all known control files.
896
897 Example URL:
898 To List all control files:
beepsbd337242013-07-10 05:44:06899 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
900 To List all control files for, say, the bvt suite:
901 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 22:17:28902 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 20:12:42903 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0&control_path=client/sleeptest/control
Scott Zawalski4647ce62012-01-03 22:17:28904
905 Args:
Scott Zawalski84a39c92012-01-13 20:12:42906 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 22:17:28907 control_path: If you want the contents of a control file set this
908 to the path. E.g. client/site_tests/sleeptest/control
909 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-10 05:44:06910 suite_name: If control_path is not specified but a suite_name is
911 specified, list the control files belonging to that suite instead of
912 all control files. The empty string for suite_name will list all control
913 files for the build.
Don Garrettf84631a2014-01-08 02:21:26914
Scott Zawalski4647ce62012-01-03 22:17:28915 Returns:
916 Contents of a control file if control_path is provided.
917 A list of control files if no control_path is provided.
918 """
Sanika Kulkarnid4496fd2020-02-05 01:26:25919 if is_deprecated_server():
920 raise DeprecatedRPCError('controlfiles')
921
Don Garrettf84631a2014-01-08 02:21:26922 if not kwargs:
Scott Zawalski4647ce62012-01-03 22:17:28923 return _PrintDocStringAsHTML(self.controlfiles)
924
Don Garrettf84631a2014-01-08 02:21:26925 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 23:45:31926 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
927 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 22:17:28928
Don Garrettf84631a2014-01-08 02:21:26929 if 'control_path' not in kwargs:
930 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-10 05:44:06931 return common_util.GetControlFileListForSuite(
Don Garrettf84631a2014-01-08 02:21:26932 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-10 05:44:06933 else:
934 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-08 02:21:26935 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 22:17:28936 else:
Gilad Arnoldc65330c2012-09-20 22:17:48937 return common_util.GetControlFile(
Don Garrettf84631a2014-01-08 02:21:26938 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-13 02:39:18939
940 @cherrypy.expose
Simran Basi99e63c02014-05-20 17:39:52941 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 23:02:11942 """Translates an xBuddy path to a real path to artifact if it exists.
943
944 Args:
Simran Basi99e63c02014-05-20 17:39:52945 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
946 Local searches the devserver's static directory. Remote searches a
947 Google Storage image archive.
948
949 Kwargs:
950 image_dir: Google Storage image archive to search in if requesting a
951 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 23:02:11952
953 Returns:
Simran Basi99e63c02014-05-20 17:39:52954 String in the format of build_id/artifact as stored on the local server
955 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 23:02:11956 """
Sanika Kulkarnid4496fd2020-02-05 01:26:25957 if is_deprecated_server():
958 raise DeprecatedRPCError('xbuddy_translate')
959
Simran Basi99e63c02014-05-20 17:39:52960 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 21:07:59961 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 23:02:11962 response = os.path.join(build_id, filename)
963 _Log('Path translation requested, returning: %s', response)
964 return response
965
966 @cherrypy.expose
joycheneaf4cfc2013-07-02 15:38:57967 def xbuddy(self, *args, **kwargs):
968 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 19:13:13969
970 Args:
joycheneaf4cfc2013-07-02 15:38:57971 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 21:30:30972 components of the path. The path can be understood as
973 "{local|remote}/build_id/artifact" where build_id is composed of
974 "board/version."
joycheneaf4cfc2013-07-02 15:38:57975
joychen121fc9b2013-08-02 21:30:30976 The first path element is optional, and can be "remote" or "local"
977 If local (the default), devserver will not attempt to access Google
978 Storage, and will only search the static directory for the files.
979 If remote, devserver will try to obtain the artifact off GS if it's
980 not found locally.
981 The board is the familiar board name, optionally suffixed.
982 The version can be the google storage version number, and may also be
983 any of a number of xBuddy defined version aliases that will be
984 translated into the latest built image that fits the description.
985 Defaults to latest.
986 The artifact is one of a number of image or artifact aliases used by
987 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 15:38:57988
989 Kwargs:
Yu-Ju Hong51495eb2013-12-13 01:08:43990 for_update: {true|false}
Amin Hassanie9ffb862019-09-26 00:10:40991 if true, prepares the update payloads for the image,
Yu-Ju Hong51495eb2013-12-13 01:08:43992 and returns the update uri to pass to the
993 update_engine_client.
joychen3cb228e2013-06-12 19:13:13994 return_dir: {true|false}
995 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-13 01:08:43996 relative_path: {true|false}
997 if set to true, returns the relative path to the payload
998 directory from static_dir.
joychen3cb228e2013-06-12 19:13:13999 Example URL:
joycheneaf4cfc2013-07-02 15:38:571000 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 19:13:131001 or
joycheneaf4cfc2013-07-02 15:38:571002 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 19:13:131003
1004 Returns:
Yu-Ju Hong51495eb2013-12-13 01:08:431005 If |for_update|, returns a redirect to the image or update file
1006 on the devserver. E.g.,
1007 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
1008 chromium-test-image.bin
1009 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1010 http://host:port/static/x86-generic-release/R26-4000.0.0/
1011 If |relative_path| is true, return a relative path the folder where the
1012 payloads are. E.g.,
1013 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 19:13:131014 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251015 if is_deprecated_server():
1016 raise DeprecatedRPCError('xbuddy')
1017
Chris Sosa75490802013-10-01 00:21:451018 boolean_string = kwargs.get('for_update')
1019 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-13 01:08:431020 boolean_string = kwargs.get('return_dir')
1021 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1022 boolean_string = kwargs.get('relative_path')
1023 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 21:30:301024
Yu-Ju Hong51495eb2013-12-13 01:08:431025 if return_dir and relative_path:
Amin Hassani722e0962019-11-15 23:45:311026 raise DevServerHTTPError(
Amin Hassanid4e35392019-10-03 18:02:441027 http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 07:31:301028 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-10-01 00:21:451029
1030 # For updates, we optimize downloading of test images.
1031 file_name = None
1032 build_id = None
1033 if for_update:
1034 try:
Yu-Ju Hong1bdb7a92014-04-10 23:02:111035 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-10-01 00:21:451036 except build_artifact.ArtifactDownloadError:
1037 build_id = None
1038
1039 if not build_id:
1040 build_id, file_name = self._xbuddy.Get(args)
1041
Yu-Ju Hong51495eb2013-12-13 01:08:431042 if for_update:
Amin Hassanie9ffb862019-09-26 00:10:401043 _Log('Payloads requested.')
Yu-Ju Hong51495eb2013-12-13 01:08:431044 # Forces payload to be in cache and symlinked into build_id dir.
Amin Hassanie9ffb862019-09-26 00:10:401045 updater.GetUpdateForLabel(build_id)
Yu-Ju Hong51495eb2013-12-13 01:08:431046
1047 response = None
1048 if return_dir:
1049 response = os.path.join(cherrypy.request.base, 'static', build_id)
1050 _Log('Directory requested, returning: %s', response)
1051 elif relative_path:
1052 response = build_id
1053 _Log('Relative path requested, returning: %s', response)
1054 elif for_update:
1055 response = os.path.join(cherrypy.request.base, 'update', build_id)
1056 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 19:13:131057 else:
Yu-Ju Hong51495eb2013-12-13 01:08:431058 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 21:30:301059 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-13 01:08:431060 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 21:30:301061 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 19:13:131062
Yu-Ju Hong51495eb2013-12-13 01:08:431063 return response
1064
joychen3cb228e2013-06-12 19:13:131065 @cherrypy.expose
1066 def xbuddy_list(self):
1067 """Lists the currently available images & time since last access.
1068
Gilad Arnold452fd272014-02-04 19:09:281069 Returns:
1070 A string representation of a list of tuples [(build_id, time since last
1071 access),...]
joychen3cb228e2013-06-12 19:13:131072 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251073 if is_deprecated_server():
1074 raise DeprecatedRPCError('xbuddy')
1075
joychen3cb228e2013-06-12 19:13:131076 return self._xbuddy.List()
1077
1078 @cherrypy.expose
1079 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 19:09:281080 """Returns the number of images cached by xBuddy."""
Sanika Kulkarnid4496fd2020-02-05 01:26:251081 if is_deprecated_server():
1082 raise DeprecatedRPCError('xbuddy_capacity')
1083
joychen3cb228e2013-06-12 19:13:131084 return self._xbuddy.Capacity()
1085
1086 @cherrypy.expose
Chris Sosa7c931362010-10-12 02:49:011087 def index(self):
Gilad Arnoldf8f769f2012-09-24 15:43:011088 """Presents a welcome message and documentation links."""
Sanika Kulkarnid4496fd2020-02-05 01:26:251089 if is_deprecated_server():
1090 raise DeprecatedRPCError('index')
1091
Congbin Guo6bc32182019-08-21 00:54:301092 html_template = (
1093 'Welcome to the Dev Server!<br>\n'
1094 '<br>\n'
1095 'Here are the available methods, click for documentation:<br>\n'
1096 '<br>\n'
1097 '%s')
1098
1099 exposed_methods = []
1100 for app in cherrypy.tree.apps.values():
1101 exposed_methods += _FindExposedMethods(
1102 app.root, app.script_name.lstrip('/'),
1103 unlisted=self._UNLISTED_METHODS)
1104
1105 return html_template % '<br>\n'.join(
1106 ['<a href=doc/%s>%s</a>' % (name, name)
1107 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 15:43:011108
1109 @cherrypy.expose
1110 def doc(self, *args):
1111 """Shows the documentation for available methods / URLs.
1112
Amin Hassani08e42d22019-06-03 07:31:301113 Examples:
Gilad Arnoldf8f769f2012-09-24 15:43:011114 http://myhost/doc/update
1115 """
Sanika Kulkarnid4496fd2020-02-05 01:26:251116 if is_deprecated_server():
1117 raise DeprecatedRPCError('doc')
1118
Gilad Arnoldd5ebaaa2012-10-02 18:52:381119 name = '/'.join(args)
Congbin Guo6bc32182019-08-21 00:54:301120 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 15:43:011121 if not method:
Amin Hassanid4e35392019-10-03 18:02:441122 raise DevServerError("No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 15:43:011123 if not method.__doc__:
Amin Hassanid4e35392019-10-03 18:02:441124 raise DevServerError("No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 15:43:011125 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-12 02:49:011126
Dale Curtisc9aaf3a2011-08-09 22:47:401127 @cherrypy.expose
Amin Hassani6eec8792020-01-09 22:06:481128 def update(self, *args, **kwargs):
Gilad Arnoldf8f769f2012-09-24 15:43:011129 """Handles an update check from a Chrome OS client.
1130
1131 The HTTP request should contain the standard Omaha-style XML blob. The URL
1132 line may contain an additional intermediate path to the update payload.
1133
joychen121fc9b2013-08-02 21:30:301134 This request can be handled in one of 4 ways, depending on the devsever
1135 settings and intermediate path.
joychenb0dfe552013-07-30 17:02:061136
Amin Hassanie9ffb862019-09-26 00:10:401137 1. No intermediate path. DEPRECATED
joychen121fc9b2013-08-02 21:30:301138
1139 2. Path explicitly invokes XBuddy
1140 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1141 with 'xbuddy'. This path is then used to acquire an image binary for the
1142 devserver to generate an update payload from. Devserver then serves this
1143 payload.
1144
1145 3. Path is left for the devserver to interpret.
1146 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1147 to generate a payload from the test image in that directory and serve it.
1148
joychen121fc9b2013-08-02 21:30:301149 Examples:
joychen121fc9b2013-08-02 21:30:301150 2. Explicitly invoke xbuddy
1151 update_engine_client --omaha_url=
1152 http://myhost/update/xbuddy/remote/board/version/dev
1153 This would go to GS to download the dev image for the board, from which
1154 the devserver would generate a payload to serve.
1155
1156 3. Give a path for devserver to interpret
1157 update_engine_client --omaha_url=http://myhost/update/some/random/path
1158 This would attempt, in order to:
1159 a) Generate an update from a test image binary if found in
1160 static_dir/some/random/path.
1161 b) Serve an update payload found in static_dir/some/random/path.
1162 c) Hope that some/random/path takes the form "board/version" and
1163 and attempt to download an update payload for that board/version
1164 from GS.
Gilad Arnoldf8f769f2012-09-24 15:43:011165 """
joychen121fc9b2013-08-02 21:30:301166 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 21:47:021167 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-12 02:49:011168 data = cherrypy.request.rfile.read(body_length)
Amin Hassani6aa075c2020-02-21 18:36:441169
Amin Hassani6eec8792020-01-09 22:06:481170 return updater.HandleUpdatePing(data, label, **kwargs)
Chris Sosa0356d3b2010-09-16 22:46:221171
Dan Shif5ce2de2013-04-25 23:06:321172
Chris Sosadbc20082012-12-10 21:39:111173def _CleanCache(cache_dir, wipe):
1174 """Wipes any excess cached items in the cache_dir.
1175
1176 Args:
1177 cache_dir: the directory we are wiping from.
1178 wipe: If True, wipe all the contents -- not just the excess.
1179 """
1180 if wipe:
1181 # Clear the cache and exit on error.
1182 cmd = 'rm -rf %s/*' % cache_dir
1183 if os.system(cmd) != 0:
1184 _Log('Failed to clear the cache with %s' % cmd)
1185 sys.exit(1)
1186 else:
1187 # Clear all but the last N cached updates
1188 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1189 (cache_dir, CACHED_ENTRIES))
1190 if os.system(cmd) != 0:
1191 _Log('Failed to clean up old delta cache files with %s' % cmd)
1192 sys.exit(1)
1193
1194
Chris Sosa3ae4dc12013-03-29 18:47:001195def _AddTestingOptions(parser):
1196 group = optparse.OptionGroup(
1197 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1198 'developers writing integration tests utilizing the devserver. They are '
1199 'not intended to be really used outside the scope of someone '
1200 'knowledgable about the test.')
1201 group.add_option('--exit',
1202 action='store_true',
Amin Hassanie9ffb862019-09-26 00:10:401203 help='do not start the server (yet clear cache)')
Chris Sosa3ae4dc12013-03-29 18:47:001204 parser.add_option_group(group)
1205
1206
Chris Sosa3ae4dc12013-03-29 18:47:001207def _AddProductionOptions(parser):
1208 group = optparse.OptionGroup(
1209 parser, 'Advanced Server Options', 'These options can be used to changed '
1210 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 18:47:001211 group.add_option('--clear_cache',
1212 action='store_true', default=False,
1213 help='At startup, removes all cached entries from the'
Amin Hassanid4e35392019-10-03 18:02:441214 "devserver's cache.")
Chris Sosa3ae4dc12013-03-29 18:47:001215 group.add_option('--logfile',
1216 metavar='PATH',
1217 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 20:24:551218 group.add_option('--pidfile',
1219 metavar='PATH',
1220 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 19:04:131221 group.add_option('--portfile',
1222 metavar='PATH',
1223 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 18:47:001224 group.add_option('--production',
1225 action='store_true', default=False,
1226 help='have the devserver use production values when '
1227 'starting up. This includes using more threads and '
1228 'performing less logging.')
1229 parser.add_option_group(group)
1230
1231
Paul Hobbsef4e0702016-06-28 00:01:421232def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 18:05:191233 """Create a LogHandler instance used to log all messages."""
1234 hdlr_cls = handlers.TimedRotatingFileHandler
1235 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-31 03:00:091236 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 18:05:191237 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 20:24:551238 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 18:05:191239 return hdlr
1240
1241
Chris Sosacde6bf42012-06-01 01:36:391242def main():
Chris Sosa3ae4dc12013-03-29 18:47:001243 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 21:47:021244 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 23:39:341245
1246 # get directory that the devserver is run from
1247 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 16:17:231248 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 23:39:341249 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 17:09:421250 metavar='PATH',
joychen84d13772013-08-06 16:17:231251 default=default_static_dir,
joychened64b222013-06-21 23:39:341252 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 17:09:421253 parser.add_option('--port',
1254 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 21:13:281255 help=('port for the dev server to use; if zero, binds to '
1256 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 17:09:421257 parser.add_option('-t', '--test_image',
1258 action='store_true',
joychen121fc9b2013-08-02 21:30:301259 help='Deprecated.')
joychen5260b9a2013-07-16 21:48:011260 parser.add_option('-x', '--xbuddy_manage_builds',
1261 action='store_true',
1262 default=False,
1263 help='If set, allow xbuddy to manage images in'
1264 'build/images.')
Dan Shi72b16132015-10-08 19:10:331265 parser.add_option('-a', '--android_build_credential',
1266 default=None,
1267 help='Path to a json file which contains the credential '
1268 'needed to access Android builds.')
Sanika Kulkarnid4496fd2020-02-05 01:26:251269 parser.add_option('--infra_removal',
1270 action='store_true', default=False,
1271 help='If option is present, some RPCs will be disabled to '
1272 'help with infra removal efforts. See '
1273 'go/devserver-deprecation')
Chris Sosa3ae4dc12013-03-29 18:47:001274 _AddProductionOptions(parser)
Chris Sosa3ae4dc12013-03-29 18:47:001275 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-12 02:49:011276 (options, _) = parser.parse_args()
[email protected]21a5ca32009-11-04 18:23:231277
J. Richard Barnette3d977b82013-04-23 18:05:191278 # Handle options that must be set globally in cherrypy. Do this
1279 # work up front, because calls to _Log() below depend on this
1280 # initialization.
1281 if options.production:
1282 cherrypy.config.update({'environment': 'production'})
Sanika Kulkarnid4496fd2020-02-05 01:26:251283 cherrypy.config.update({'infra_removal': options.infra_removal})
J. Richard Barnette3d977b82013-04-23 18:05:191284 if not options.logfile:
1285 cherrypy.config.update({'log.screen': True})
1286 else:
1287 cherrypy.config.update({'log.error_file': '',
1288 'log.access_file': ''})
Paul Hobbsef4e0702016-06-28 00:01:421289 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 18:05:191290 # Pylint can't seem to process these two calls properly
1291 # pylint: disable=E1101
1292 cherrypy.log.access_log.addHandler(hdlr)
1293 cherrypy.log.error_log.addHandler(hdlr)
1294 # pylint: enable=E1101
1295
joychened64b222013-06-21 23:39:341296 # set static_dir, from which everything will be served
joychen84d13772013-08-06 16:17:231297 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 22:46:221298
joychened64b222013-06-21 23:39:341299 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 18:05:191300 # If our devserver is only supposed to serve payloads, we shouldn't be
1301 # mucking with the cache at all. If the devserver hadn't previously
1302 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 18:14:071303 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 21:39:111304 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 20:12:171305 else:
1306 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-17 01:36:141307
Chris Sosadbc20082012-12-10 21:39:111308 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 23:39:341309 _Log('Serving from %s' % options.static_dir)
[email protected]21a5ca32009-11-04 18:23:231310
Amin Hassanie9ffb862019-09-26 00:10:401311 _xbuddy = xbuddy.XBuddy(manage_builds=options.xbuddy_manage_builds,
joychen121fc9b2013-08-02 21:30:301312 static_dir=options.static_dir)
Chris Sosa75490802013-10-01 00:21:451313 if options.clear_cache and options.xbuddy_manage_builds:
1314 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 21:30:301315
Chris Sosa6a3697f2013-01-30 00:44:431316 # We allow global use here to share with cherrypy classes.
1317 # pylint: disable=W0603
Chris Sosacde6bf42012-06-01 01:36:391318 global updater
Amin Hassanie5612032020-03-25 21:50:421319 updater = autoupdate.Autoupdate(_xbuddy, static_dir=options.static_dir)
Chris Sosa7c931362010-10-12 02:49:011320
J. Richard Barnette3d977b82013-04-23 18:05:191321 if options.exit:
1322 return
Chris Sosa2f1c41e2012-07-10 21:32:331323
joychen3cb228e2013-06-12 19:13:131324 dev_server = DevServerRoot(_xbuddy)
Congbin Guo3afae6c2019-08-13 23:29:421325 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 19:13:131326
Chris Sosa855b8932013-08-21 20:24:551327 if options.pidfile:
1328 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1329
Gilad Arnold11fbef42014-02-10 19:04:131330 if options.portfile:
1331 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1332
Dan Shiafd5c6c2016-01-07 18:27:031333 if (options.android_build_credential and
1334 os.path.exists(options.android_build_credential)):
1335 try:
1336 with open(options.android_build_credential) as f:
1337 android_build.BuildAccessor.credential_info = json.load(f)
1338 except ValueError as e:
1339 _Log('Failed to load the android build credential: %s. Error: %s.' %
1340 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 23:29:421341
1342 cherrypy.tree.mount(health_checker_app, '/check_health',
1343 config=health_checker.get_config())
joychen3cb228e2013-06-12 19:13:131344 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-06-01 01:36:391345
1346
1347if __name__ == '__main__':
1348 main()