blob: f7d67079e956579797f3a8c945196295cf64d606 [file] [log] [blame]
David Riley2fcb0122017-11-02 11:25:39 -07001#!/usr/bin/env python2
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -07002# -*- coding: utf-8 -*-
Chris Sosa781ba6d2012-04-11 12:44:43 -07003# Copyright (c) 2009-2012 The Chromium OS Authors. All rights reserved.
rtc@google.comded22402009-10-26 22:36:21 +00004# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Amin Hassani3de3e2b2020-10-19 13:34:10 -07007"""Chromium OS development server that can be used for caching files.
Chris Sosa3ae4dc12013-03-29 11:47:00 -07008
Amin Hassani3de3e2b2020-10-19 13:34:10 -07009The devserver is configured to stage and serve artifacts from Google Storage
10using the credentials provided to it before it is run. The easiest way to
11understand this is that the devserver is functioning as a local cache for
12artifacts produced and uploaded by build servers. Users of this form of
13devserver can download the artifacts from the devservers static directory.
14Archive mode is always active.
Chris Sosa3ae4dc12013-03-29 11:47:00 -070015"""
16
Gabe Black3b567202015-09-23 14:07:59 -070017from __future__ import print_function
Chris Sosa7c931362010-10-11 19:49:01 -070018
Gilad Arnold55a2a372012-10-02 09:46:32 -070019import json
David Riley2fcb0122017-11-02 11:25:39 -070020import optparse # pylint: disable=deprecated-module
rtc@google.comded22402009-10-26 22:36:21 +000021import os
Scott Zawalski4647ce62012-01-03 17:17:28 -050022import re
Simran Basi4baad082013-02-14 13:39:18 -080023import shutil
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -080024import socket
Chris Masone816e38c2012-05-02 12:22:36 -070025import subprocess
J. Richard Barnette3d977b82013-04-23 11:05:19 -070026import sys
Chris Masone816e38c2012-05-02 12:22:36 -070027import tempfile
Dan Shi59ae7092013-06-04 14:37:27 -070028import threading
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070029import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070030from logging import handlers
31
Amin Hassanid4e35392019-10-03 11:02:44 -070032from six.moves import http_client
33
34# pylint: disable=no-name-in-module, import-error
J. Richard Barnette3d977b82013-04-23 11:05:19 -070035import cherrypy
Chris Sosa855b8932013-08-21 13:24:55 -070036from cherrypy import _cplogging as cplogging
Amin Hassanid4e35392019-10-03 11:02:44 -070037from cherrypy.process import plugins
38# pylint: enable=no-name-in-module, import-error
rtc@google.comded22402009-10-26 22:36:21 +000039
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070040import cherrypy_ext
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070041import health_checker
42
Richard Barnettedf35c322017-08-18 17:02:13 -070043# This must happen before any local modules get a chance to import
44# anything from chromite. Otherwise, really bad things will happen, and
45# you will _not_ understand why.
Congbin Guo3afae6c2019-08-13 16:29:42 -070046import setup_chromite # pylint: disable=unused-import
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070047from chromite.lib.xbuddy import android_build
48from chromite.lib.xbuddy import artifact_info
49from chromite.lib.xbuddy import build_artifact
50from chromite.lib.xbuddy import cherrypy_log_util
51from chromite.lib.xbuddy import common_util
52from chromite.lib.xbuddy import devserver_constants
53from chromite.lib.xbuddy import downloader
54from chromite.lib.xbuddy import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070055
Gilad Arnoldc65330c2012-09-20 15:17:48 -070056# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080057def _Log(message, *args):
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070058 return cherrypy_log_util.LogWithTag('DEVSERVER', message, *args)
Frank Farzan40160872011-12-12 18:39:18 -080059
Chris Sosa417e55d2011-01-25 16:40:48 -080060CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080061
Simran Basi4baad082013-02-14 13:39:18 -080062TELEMETRY_FOLDER = 'telemetry_src'
63TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
64 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070065 'dep-chrome_test.tar.bz2',
66 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -080067
xixuan3d48bff2017-01-30 19:00:09 -080068# Log rotation parameters. These settings correspond to twice a day once
69# devserver is started, with about two weeks (28 backup files) of old logs
70# kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -070071#
xixuan3d48bff2017-01-30 19:00:09 -080072# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 11:05:19 -070073# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-30 19:00:09 -080074_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 16:29:42 -070075_LOG_ROTATION_INTERVAL = 12 # hours
76_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-12 18:39:18 -080077
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -080078# Error msg for deprecated RPC usage.
79DEPRECATED_RPC_ERROR_MSG = ('The %s RPC has been deprecated. Usage of this '
80 'RPC is discouraged. Please go to '
81 'go/devserver-deprecation for more information.')
82
xixuan52c2fba2016-05-20 17:02:48 -070083
Amin Hassanid4e35392019-10-03 11:02:44 -070084class DevServerError(Exception):
85 """Exception class used by DevServer."""
86
87
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -080088class DeprecatedRPCError(DevServerError):
89 """Exception class used when an RPC is deprecated but is still being used."""
90
91 def __init__(self, rpc_name):
92 """Constructor for DeprecatedRPCError class.
93
94 :param rpc_name: (str) name of the RPC that has been deprecated.
95 """
Amin Hassani6ecda232020-03-09 19:03:23 -070096 super(DeprecatedRPCError, self).__init__(
97 DEPRECATED_RPC_ERROR_MSG % rpc_name)
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -080098 self.rpc_name = rpc_name
99
100
Amin Hassani722e0962019-11-15 15:45:31 -0800101class DevServerHTTPError(cherrypy.HTTPError):
102 """Exception class to log the HTTPResponse before routing it to cherrypy."""
103 def __init__(self, status, message):
104 """CherryPy error with logging.
105
106 Args:
107 status: HTTPResponse status.
108 message: Message associated with the response.
109 """
110 cherrypy.HTTPError.__init__(self, status, message)
111 _Log('HTTPError status: %s message: %s', status, message)
112
113
Gabe Black3b567202015-09-23 14:07:59 -0700114def _canonicalize_archive_url(archive_url):
115 """Canonicalizes archive_url strings.
116
117 Raises:
118 DevserverError: if archive_url is not set.
119 """
120 if archive_url:
121 if not archive_url.startswith('gs://'):
Amin Hassanid4e35392019-10-03 11:02:44 -0700122 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700123 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 14:07:59 -0700124
125 return archive_url.rstrip('/')
126 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700127 raise DevServerError('Must specify an archive_url in the request')
Gabe Black3b567202015-09-23 14:07:59 -0700128
129
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700130def _canonicalize_local_path(local_path, static_dir):
Gabe Black3b567202015-09-23 14:07:59 -0700131 """Canonicalizes |local_path| strings.
132
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700133 Args:
134 local_path: The input path.
135 static_dir: Devserver's static cache directory.
136
Gabe Black3b567202015-09-23 14:07:59 -0700137 Raises:
138 DevserverError: if |local_path| is not set.
139 """
140 # Restrict staging of local content to only files within the static
141 # directory.
142 local_path = os.path.abspath(local_path)
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700143 if not local_path.startswith(static_dir):
Amin Hassanid4e35392019-10-03 11:02:44 -0700144 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700145 'Local path %s must be a subdirectory of the static'
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700146 ' directory: %s' % (local_path, static_dir))
Gabe Black3b567202015-09-23 14:07:59 -0700147
148 return local_path.rstrip('/')
149
150
151def _get_artifacts(kwargs):
152 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
153
154 Raises:
155 DevserverError if no artifacts would be returned.
156 """
157 artifacts = kwargs.get('artifacts')
158 files = kwargs.get('files')
159 if not artifacts and not files:
Amin Hassanid4e35392019-10-03 11:02:44 -0700160 raise DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700161
162 # Note we NEED to coerce files to a string as we get raw unicode from
163 # cherrypy and we treat files as strings elsewhere in the code.
164 return (str(artifacts).split(',') if artifacts else [],
165 str(files).split(',') if files else [])
166
167
Dan Shi61305df2015-10-26 16:52:35 -0700168def _is_android_build_request(kwargs):
169 """Check if a devserver call is for Android build, based on the arguments.
170
171 This method exams the request's arguments (os_type) to determine if the
172 request is for Android build. If os_type is set to `android`, returns True.
173 If os_type is not set or has other values, returns False.
174
175 Args:
176 kwargs: Keyword arguments for the request.
177
178 Returns:
179 True if the request is for Android build. False otherwise.
180 """
181 os_type = kwargs.get('os_type', None)
182 return os_type == 'android'
183
184
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700185def _get_downloader(static_dir, kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700186 """Returns the downloader based on passed in arguments.
187
188 Args:
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700189 static_dir: Devserver's static cache directory.
Amin Hassani08e42d22019-06-03 00:31:30 -0700190 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700191 """
192 local_path = kwargs.get('local_path')
193 if local_path:
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700194 local_path = _canonicalize_local_path(local_path, static_dir)
Gabe Black3b567202015-09-23 14:07:59 -0700195
196 dl = None
197 if local_path:
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800198 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700199 dl = downloader.LocalDownloader(static_dir, local_path,
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800200 delete_source=delete_source)
Gabe Black3b567202015-09-23 14:07:59 -0700201
Dan Shi61305df2015-10-26 16:52:35 -0700202 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700203 archive_url = kwargs.get('archive_url')
204 if not archive_url and not local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700205 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700206 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700207 if archive_url and local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700208 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700209 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700210 if not dl:
211 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700212 dl = downloader.GoogleStorageDownloader(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700213 static_dir, archive_url,
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700214 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
215 archive_url))
Gabe Black3b567202015-09-23 14:07:59 -0700216 elif not dl:
217 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 12:10:33 -0700218 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 16:52:35 -0700219 build_id = kwargs.get('build_id', None)
220 if not target or not branch or not build_id:
Amin Hassanid4e35392019-10-03 11:02:44 -0700221 raise DevServerError('target, branch, build ID must all be specified for '
222 'downloading Android build.')
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700223 dl = downloader.AndroidBuildDownloader(static_dir, branch, build_id,
Dan Shi72b16132015-10-08 12:10:33 -0700224 target)
Gabe Black3b567202015-09-23 14:07:59 -0700225
226 return dl
227
228
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700229def _get_downloader_and_factory(static_dir, kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700230 """Returns the downloader and artifact factory based on passed in arguments.
231
232 Args:
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700233 static_dir: Devserver's static cache directory.
Amin Hassani08e42d22019-06-03 00:31:30 -0700234 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700235 """
236 artifacts, files = _get_artifacts(kwargs)
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700237 dl = _get_downloader(static_dir, kwargs)
Gabe Black3b567202015-09-23 14:07:59 -0700238
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700239 if (isinstance(dl, (downloader.GoogleStorageDownloader,
240 downloader.LocalDownloader))):
Gabe Black3b567202015-09-23 14:07:59 -0700241 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 12:10:33 -0700242 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 14:07:59 -0700243 factory_class = build_artifact.AndroidArtifactFactory
244 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700245 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700246 'Unrecognized value for downloader type: %s' % type(dl))
Gabe Black3b567202015-09-23 14:07:59 -0700247
248 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
249
250 return dl, factory
251
252
Scott Zawalski4647ce62012-01-03 17:17:28 -0500253def _LeadingWhiteSpaceCount(string):
254 """Count the amount of leading whitespace in a string.
255
256 Args:
257 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800258
Scott Zawalski4647ce62012-01-03 17:17:28 -0500259 Returns:
260 number of white space chars before characters start.
261 """
Gabe Black3b567202015-09-23 14:07:59 -0700262 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500263 if matched:
264 return len(matched.group())
265
266 return 0
267
268
269def _PrintDocStringAsHTML(func):
270 """Make a functions docstring somewhat HTML style.
271
272 Args:
273 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800274
Scott Zawalski4647ce62012-01-03 17:17:28 -0500275 Returns:
276 A string that is somewhat formated for a web browser.
277 """
278 # TODO(scottz): Make this parse Args/Returns in a prettier way.
279 # Arguments could be bolded and indented etc.
280 html_doc = []
281 for line in func.__doc__.splitlines():
282 leading_space = _LeadingWhiteSpaceCount(line)
283 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700284 line = ' ' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500285
286 html_doc.append('<BR>%s' % line)
287
288 return '\n'.join(html_doc)
289
290
Simran Basief83d6a2014-08-28 14:32:01 -0700291def _GetUpdateTimestampHandler(static_dir):
292 """Returns a handler to update directory staged.timestamp.
293
294 This handler resets the stage.timestamp whenever static content is accessed.
295
296 Args:
297 static_dir: Directory from which static content is being staged.
298
299 Returns:
Amin Hassani08e42d22019-06-03 00:31:30 -0700300 A cherrypy handler to update the timestamp of accessed content.
Simran Basief83d6a2014-08-28 14:32:01 -0700301 """
302 def UpdateTimestampHandler():
303 if not '404' in cherrypy.response.status:
304 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
305 cherrypy.request.path_info)
306 if build_match:
307 build_dir = os.path.join(static_dir, build_match.group('build'))
308 downloader.Downloader.TouchTimestampForStaged(build_dir)
309 return UpdateTimestampHandler
310
311
Chris Sosa7c931362010-10-11 19:49:01 -0700312def _GetConfig(options):
313 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800314
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800315 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800316 # Fall back to IPv4 when python is not configured with IPv6.
317 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800318 socket_host = '0.0.0.0'
319
Simran Basief83d6a2014-08-28 14:32:01 -0700320 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
321 # on the on_end_resource hook. This hook is called once processing is
322 # complete and the response is ready to be returned.
323 cherrypy.tools.update_timestamp = cherrypy.Tool(
324 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
325
David Riley2fcb0122017-11-02 11:25:39 -0700326 base_config = {
327 'global': {
328 'server.log_request_headers': True,
329 'server.protocol_version': 'HTTP/1.1',
330 'server.socket_host': socket_host,
331 'server.socket_port': int(options.port),
332 'response.timeout': 6000,
333 'request.show_tracebacks': True,
334 'server.socket_timeout': 60,
335 'server.thread_pool': 2,
336 'engine.autoreload.on': False,
337 },
David Riley2fcb0122017-11-02 11:25:39 -0700338 '/build': {
339 'response.timeout': 100000,
340 },
David Riley2fcb0122017-11-02 11:25:39 -0700341 # Sets up the static dir for file hosting.
342 '/static': {
343 'tools.staticdir.dir': options.static_dir,
344 'tools.staticdir.on': True,
345 'response.timeout': 10000,
346 'tools.update_timestamp.on': True,
347 },
348 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700349 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700350 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500351
Chris Sosa7c931362010-10-11 19:49:01 -0700352 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000353
Darin Petkove17164a2010-08-11 13:24:41 -0700354
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700355def _GetRecursiveMemberObject(root, member_list):
356 """Returns an object corresponding to a nested member list.
357
358 Args:
359 root: the root object to search
360 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800361
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700362 Returns:
363 An object corresponding to the member name list; None otherwise.
364 """
365 for member in member_list:
366 next_root = root.__class__.__dict__.get(member)
367 if not next_root:
368 return None
369 root = next_root
370 return root
371
372
373def _IsExposed(name):
374 """Returns True iff |name| has an `exposed' attribute and it is set."""
375 return hasattr(name, 'exposed') and name.exposed
376
377
Congbin Guo6bc32182019-08-20 17:54:30 -0700378def _GetExposedMethod(nested_member):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700379 """Returns a CherryPy-exposed method, if such exists.
380
381 Args:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700382 nested_member: a slash-joined path to the nested member
Don Garrettf84631a2014-01-07 18:21:26 -0800383
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700384 Returns:
Congbin Guo6bc32182019-08-20 17:54:30 -0700385 A function object corresponding to the path defined by |nested_member| from
386 the app root object registered, if the function is exposed; None otherwise.
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700387 """
Congbin Guo6bc32182019-08-20 17:54:30 -0700388 for app in cherrypy.tree.apps.values():
389 # Use the 'index' function doc as the doc of the app.
390 if nested_member == app.script_name.lstrip('/'):
391 nested_member = 'index'
392
393 method = _GetRecursiveMemberObject(app.root, nested_member.split('/'))
394 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
395 return method
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700396
397
Gilad Arnold748c8322012-10-12 09:51:35 -0700398def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700399 """Finds exposed CherryPy methods.
400
401 Args:
402 root: the root object for searching
403 prefix: slash-joined chain of members leading to current object
404 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800405
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700406 Returns:
407 List of exposed URLs that are not unlisted.
408 """
409 method_list = []
Congbin Guo6bc32182019-08-20 17:54:30 -0700410 for member in root.__class__.__dict__.keys():
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700411 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700412 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700413 continue
414 member_obj = root.__class__.__dict__[member]
415 if _IsExposed(member_obj):
Amin Hassani08e42d22019-06-03 00:31:30 -0700416 if isinstance(member_obj, types.FunctionType):
Congbin Guo6bc32182019-08-20 17:54:30 -0700417 # Regard the app name as exposed "method" name if it exposed 'index'
418 # function.
419 if prefix and member == 'index':
420 method_list.append(prefix)
421 else:
422 method_list.append(prefixed_member)
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700423 else:
424 method_list += _FindExposedMethods(
425 member_obj, prefixed_member, unlisted)
426 return method_list
427
428
xixuan52c2fba2016-05-20 17:02:48 -0700429def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-11-30 16:48:20 -0800430 """Parse boolean arg from kwargs.
431
432 Args:
433 kwargs: the parameters to be checked.
434 key: the key to be parsed.
435
436 Returns:
437 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
438
439 Raises:
440 DevServerHTTPError if kwargs[key] is not a boolean variable.
441 """
xixuan52c2fba2016-05-20 17:02:48 -0700442 if key in kwargs:
443 if kwargs[key] == 'True':
444 return True
445 elif kwargs[key] == 'False':
446 return False
447 else:
Amin Hassani722e0962019-11-15 15:45:31 -0800448 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
449 'The value for key %s is not boolean.' % key)
xixuan52c2fba2016-05-20 17:02:48 -0700450 else:
451 return False
452
xixuan447ad9d2017-02-28 14:46:20 -0800453
xixuanac89ce82016-11-30 16:48:20 -0800454def _parse_string_arg(kwargs, key):
455 """Parse string arg from kwargs.
456
457 Args:
458 kwargs: the parameters to be checked.
459 key: the key to be parsed.
460
461 Returns:
462 The string value of kwargs[key], or None if key doesn't exist in kwargs.
463 """
464 if key in kwargs:
465 return kwargs[key]
466 else:
467 return None
468
xixuan447ad9d2017-02-28 14:46:20 -0800469
xixuanac89ce82016-11-30 16:48:20 -0800470def _build_uri_from_build_name(build_name):
471 """Get build url from a given build name.
472
473 Args:
474 build_name: the build name to be parsed, whose format is
475 'board/release_version'.
476
477 Returns:
478 The release_archive_url on Google Storage for this build name.
479 """
Amin Hassani08e42d22019-06-03 00:31:30 -0700480 # TODO(ahassani): This function doesn't seem to be used anywhere since its
481 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
482 # causing any runtime issues. So deprecate this in the future.
483 tokens = build_name.split('/')
484 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-20 17:02:48 -0700485
xixuan447ad9d2017-02-28 14:46:20 -0800486
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800487def is_deprecated_server():
488 """Gets whether the devserver has deprecated RPCs."""
489 return cherrypy.config.get('infra_removal', False)
490
491
David Rochberg7c79a812011-01-19 14:24:45 -0500492class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700493 """The Root Class for the Dev Server.
494
495 CherryPy works as follows:
496 For each method in this class, cherrpy interprets root/path
497 as a call to an instance of DevServerRoot->method_name. For example,
498 a call to http://myhost/build will call build. CherryPy automatically
499 parses http args and places them as keyword arguments in each method.
500 For paths http://myhost/update/dir1/dir2, you can use *args so that
501 cherrypy uses the update method and puts the extra paths in args.
502 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700503 # Method names that should not be listed on the index page.
504 _UNLISTED_METHODS = ['index', 'doc']
505
Dan Shi59ae7092013-06-04 14:37:27 -0700506 # Number of threads that devserver is staging images.
507 _staging_thread_count = 0
508 # Lock used to lock increasing/decreasing count.
509 _staging_thread_count_lock = threading.Lock()
510
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700511 def __init__(self, _xbuddy, static_dir):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700512 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800513 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700514 self._xbuddy = _xbuddy
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700515 self._static_dir = static_dir
David Rochberg7c79a812011-01-19 14:24:45 -0500516
Congbin Guo3afae6c2019-08-13 16:29:42 -0700517 @property
518 def staging_thread_count(self):
519 """Get the staging thread count."""
520 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700521
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700522 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500523 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700524 """Builds the package specified."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800525 if is_deprecated_server():
526 raise DeprecatedRPCError('build')
527
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700528 import builder
529 if self._builder is None:
530 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500531 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700532
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700533 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700534 def is_staged(self, **kwargs):
535 """Check if artifacts have been downloaded.
536
Congbin Guo3afae6c2019-08-13 16:29:42 -0700537 Examples:
538 To check if autotest and test_suites are staged:
539 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
540 artifacts=autotest,test_suites
541
Amin Hassani08e42d22019-06-03 00:31:30 -0700542 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700543 async: True to return without waiting for download to complete.
544 artifacts: Comma separated list of named artifacts to download.
545 These are defined in artifact_info and have their implementation
546 in build_artifact.py.
547 files: Comma separated list of file artifacts to stage. These
548 will be available as is in the corresponding static directory with no
549 custom post-processing.
550
Congbin Guo3afae6c2019-08-13 16:29:42 -0700551 Returns:
552 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700553 """
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700554 dl, factory = _get_downloader_and_factory(self._static_dir, kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700555 response = str(dl.IsStaged(factory))
556 _Log('Responding to is_staged %s request with %r', kwargs, response)
557 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700558
Chris Sosa76e44b92013-01-31 12:11:38 -0800559 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800560 def list_image_dir(self, **kwargs):
561 """Take an archive url and list the contents in its staged directory.
562
Amin Hassani08e42d22019-06-03 00:31:30 -0700563 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800564 To list the contents of where this devserver should have staged
565 gs://image-archive/<board>-release/<build> call:
566 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
567
Congbin Guo3afae6c2019-08-13 16:29:42 -0700568 Args:
569 archive_url: Google Storage URL for the build.
570
Prashanth Ba06d2d22014-03-07 15:35:19 -0800571 Returns:
572 A string with information about the contents of the image directory.
573 """
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700574 dl = _get_downloader(self._static_dir, kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800575 try:
Gabe Black3b567202015-09-23 14:07:59 -0700576 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800577 except build_artifact.ArtifactDownloadError as e:
578 return 'Cannot list the contents of staged artifacts. %s' % e
579 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700580 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800581 return image_dir_contents
582
583 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800584 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700585 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800586
Gabe Black3b567202015-09-23 14:07:59 -0700587 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700588 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700589 on the devserver. A call to this will attempt to cache non-specified
590 artifacts in the background for the given from the given URL following
591 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800592 artifacts is explicitly defined in the build_artifact module.
593
594 These artifacts will then be available from the static/ sub-directory of
595 the devserver.
596
Amin Hassani08e42d22019-06-03 00:31:30 -0700597 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800598 To download the autotest and test suites tarballs:
599 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
600 artifacts=autotest,test_suites
601 To download the full update payload:
602 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
603 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700604 To download just a file called blah.bin:
605 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
606 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800607
608 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700609 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800610
611 Note for this example, relative path is the archive_url stripped of its
612 basename i.e. path/ in the examples above. Specific example:
613
614 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
615
616 Will get staged to:
617
joychened64b222013-06-21 16:39:34 -0700618 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700619
620 Args:
621 archive_url: Google Storage URL for the build.
622 local_path: Local path for the build.
623 delete_source: Only meaningful with local_path. bool to indicate if the
624 source files should be deleted. This is especially useful when staging
625 a file locally in resource constrained environments as it allows us to
626 move the relevant files locally instead of copying them.
627 async: True to return without waiting for download to complete.
628 artifacts: Comma separated list of named artifacts to download.
629 These are defined in artifact_info and have their implementation
630 in build_artifact.py.
631 files: Comma separated list of files to stage. These
632 will be available as is in the corresponding static directory with no
633 custom post-processing.
634 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800635 """
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700636 dl, factory = _get_downloader_and_factory(self._static_dir, kwargs)
Gabe Black3b567202015-09-23 14:07:59 -0700637
Dan Shi59ae7092013-06-04 14:37:27 -0700638 with DevServerRoot._staging_thread_count_lock:
639 DevServerRoot._staging_thread_count += 1
640 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800641 boolean_string = kwargs.get('clean')
642 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
643 if clean and os.path.exists(dl.GetBuildDir()):
644 _Log('Removing %s' % dl.GetBuildDir())
645 shutil.rmtree(dl.GetBuildDir())
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700646 is_async = kwargs.get('async', False)
647 dl.Download(factory, is_async=is_async)
Dan Shi59ae7092013-06-04 14:37:27 -0700648 finally:
649 with DevServerRoot._staging_thread_count_lock:
650 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800651 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700652
653 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -0800654 def locate_file(self, **kwargs):
655 """Get the path to the given file name.
656
657 This method looks up the given file name inside specified build artifacts.
658 One use case is to help caller to locate an apk file inside a build
659 artifact. The location of the apk file could be different based on the
660 branch and target.
661
662 Args:
663 file_name: Name of the file to look for.
664 artifacts: A list of artifact names to search for the file.
665
666 Returns:
667 Path to the file with the given name. It's relative to the folder for the
668 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -0800669 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800670 if is_deprecated_server():
671 raise DeprecatedRPCError('locate_file')
672
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700673 dl, _ = _get_downloader_and_factory(self._static_dir, kwargs)
Dan Shi2f136862016-02-11 15:38:38 -0800674 try:
Joe Brennan1691f8e2017-03-15 15:53:36 -0700675 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 15:38:38 -0800676 artifacts = kwargs['artifacts']
677 except KeyError:
Amin Hassanid4e35392019-10-03 11:02:44 -0700678 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700679 '`file_name` and `artifacts` are required to search '
680 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 15:38:38 -0800681 build_path = dl.GetBuildDir()
682 for artifact in artifacts:
683 # Get the unzipped folder of the artifact. If it's not defined in
684 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
685 # directory directly.
686 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
687 artifact_path = os.path.join(build_path, folder)
688 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 15:53:36 -0700689 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 15:38:38 -0800690 return os.path.relpath(os.path.join(root, file_name), build_path)
Amin Hassanid4e35392019-10-03 11:02:44 -0700691 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700692 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 15:38:38 -0800693
694 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800695 def setup_telemetry(self, **kwargs):
696 """Extracts and sets up telemetry
697
698 This method goes through the telemetry deps packages, and stages them on
699 the devserver to be used by the drones and the telemetry tests.
700
701 Args:
702 archive_url: Google Storage URL for the build.
703
704 Returns:
705 Path to the source folder for the telemetry codebase once it is staged.
706 """
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700707 dl = _get_downloader(self._static_dir, kwargs)
Simran Basi4baad082013-02-14 13:39:18 -0800708
Gabe Black3b567202015-09-23 14:07:59 -0700709 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -0800710 deps_path = os.path.join(build_path, 'autotest/packages')
711 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
712 src_folder = os.path.join(telemetry_path, 'src')
713
714 with self._telemetry_lock_dict.lock(telemetry_path):
715 if os.path.exists(src_folder):
716 # Telemetry is already fully stage return
717 return src_folder
718
719 common_util.MkDirP(telemetry_path)
720
721 # Copy over the required deps tar balls to the telemetry directory.
722 for dep in TELEMETRY_DEPS:
723 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700724 if not os.path.exists(dep_path):
725 # This dep does not exist (could be new), do not extract it.
726 continue
Simran Basi4baad082013-02-14 13:39:18 -0800727 try:
728 common_util.ExtractTarball(dep_path, telemetry_path)
729 except common_util.CommonUtilError as e:
730 shutil.rmtree(telemetry_path)
Amin Hassanid4e35392019-10-03 11:02:44 -0700731 raise DevServerError(str(e))
Simran Basi4baad082013-02-14 13:39:18 -0800732
733 # By default all the tarballs extract to test_src but some parts of
734 # the telemetry code specifically hardcoded to exist inside of 'src'.
735 test_src = os.path.join(telemetry_path, 'test_src')
736 try:
737 shutil.move(test_src, src_folder)
738 except shutil.Error:
739 # This can occur if src_folder already exists. Remove and retry move.
740 shutil.rmtree(src_folder)
Amin Hassanid4e35392019-10-03 11:02:44 -0700741 raise DevServerError(
Gabe Black3b567202015-09-23 14:07:59 -0700742 'Failure in telemetry setup for build %s. Appears that the '
743 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -0800744
745 return src_folder
746
747 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800748 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700749 """Symbolicates a minidump using pre-downloaded symbols, returns it.
750
751 Callers will need to POST to this URL with a body of MIME-type
752 "multipart/form-data".
753 The body should include a single argument, 'minidump', containing the
754 binary-formatted minidump to symbolicate.
755
Chris Masone816e38c2012-05-02 12:22:36 -0700756 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800757 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700758 minidump: The binary minidump file to symbolicate.
759 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800760 if is_deprecated_server():
761 raise DeprecatedRPCError('symbolicate_dump')
762
Chris Sosa76e44b92013-01-31 12:11:38 -0800763 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -0700764 # Try debug.tar.xz first, then debug.tgz
765 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
766 kwargs['artifacts'] = artifact
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700767 dl = _get_downloader(self._static_dir, kwargs)
Dan Shif08fe492016-10-04 14:39:25 -0700768
769 try:
770 if self.stage(**kwargs) == 'Success':
771 break
772 except build_artifact.ArtifactDownloadError:
773 continue
774 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700775 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700776 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -0800777
Chris Masone816e38c2012-05-02 12:22:36 -0700778 to_return = ''
779 with tempfile.NamedTemporaryFile() as local:
780 while True:
781 data = minidump.file.read(8192)
782 if not data:
783 break
784 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800785
Chris Masone816e38c2012-05-02 12:22:36 -0700786 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800787
Gabe Black3b567202015-09-23 14:07:59 -0700788 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -0800789
xixuanab744382017-04-27 10:41:27 -0700790 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 12:11:38 -0800791 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 10:41:27 -0700792 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 12:11:38 -0800793 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
794
Chris Masone816e38c2012-05-02 12:22:36 -0700795 to_return, error_text = stackwalk.communicate()
796 if stackwalk.returncode != 0:
Amin Hassanid4e35392019-10-03 11:02:44 -0700797 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700798 "Can't generate stack trace: %s (rc=%d)" % (error_text,
799 stackwalk.returncode))
Chris Masone816e38c2012-05-02 12:22:36 -0700800
801 return to_return
802
803 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800804 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -0400805 """Return a string representing the latest build for a given target.
806
807 Args:
808 target: The build target, typically a combination of the board and the
809 type of build e.g. x86-mario-release.
810 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
811 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -0800812
Scott Zawalski16954532012-03-20 15:31:36 -0400813 Returns:
814 A string representation of the latest build if one exists, i.e.
815 R19-1993.0.0-a1-b1480.
816 An empty string if no latest could be found.
817 """
Sanika Kulkarni07d47ed2020-08-06 14:56:46 -0700818 if is_deprecated_server():
819 raise DeprecatedRPCError('latestbuild')
820
Don Garrettf84631a2014-01-07 18:21:26 -0800821 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -0400822 return _PrintDocStringAsHTML(self.latestbuild)
823
Don Garrettf84631a2014-01-07 18:21:26 -0800824 if 'target' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800825 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
826 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -0700827
828 if _is_android_build_request(kwargs):
829 branch = kwargs.get('branch', None)
830 target = kwargs.get('target', None)
831 if not target or not branch:
Amin Hassanid4e35392019-10-03 11:02:44 -0700832 raise DevServerError('Both target and branch must be specified to query'
833 ' for the latest Android build.')
Dan Shi61305df2015-10-26 16:52:35 -0700834 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
835
Scott Zawalski16954532012-03-20 15:31:36 -0400836 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700837 return common_util.GetLatestBuildVersion(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700838 self._static_dir, kwargs['target'],
Don Garrettf84631a2014-01-07 18:21:26 -0800839 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700840 except common_util.CommonUtilError as errmsg:
Amin Hassani722e0962019-11-15 15:45:31 -0800841 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
842 str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -0400843
844 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -0700845 def list_suite_controls(self, **kwargs):
846 """Return a list of contents of all known control files.
847
848 Example URL:
849 To List all control files' content:
850 http://dev-server/list_suite_controls?suite_name=bvt&
851 build=daisy_spring-release/R29-4279.0.0
852
853 Args:
854 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
855 suite_name: List the control files belonging to that suite.
856
857 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -0700858 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -0700859 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800860 if is_deprecated_server():
861 raise DeprecatedRPCError('list_suite_controls')
862
xixuan7efd0002016-04-14 15:34:01 -0700863 if not kwargs:
864 return _PrintDocStringAsHTML(self.controlfiles)
865
866 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800867 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
868 'Error: build= is required!')
xixuan7efd0002016-04-14 15:34:01 -0700869
870 if 'suite_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800871 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
872 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -0700873
874 control_file_list = [
875 line.rstrip() for line in common_util.GetControlFileListForSuite(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700876 self._static_dir, kwargs['build'],
xixuan7efd0002016-04-14 15:34:01 -0700877 kwargs['suite_name']).splitlines()]
878
Dan Shia1cd6522016-04-18 16:07:21 -0700879 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -0700880 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -0700881 control_file_content_dict[control_path] = (common_util.GetControlFile(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700882 self._static_dir, kwargs['build'], control_path))
xixuan7efd0002016-04-14 15:34:01 -0700883
Dan Shia1cd6522016-04-18 16:07:21 -0700884 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -0700885
886 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800887 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500888 """Return a control file or a list of all known control files.
889
890 Example URL:
891 To List all control files:
beepsbd337242013-07-09 22:44:06 -0700892 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
893 To List all control files for, say, the bvt suite:
894 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500895 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500896 http://dev-server/controlfiles?board=x86-alex-release&build=R18-1514.0.0&control_path=client/sleeptest/control
Scott Zawalski4647ce62012-01-03 17:17:28 -0500897
898 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500899 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500900 control_path: If you want the contents of a control file set this
901 to the path. E.g. client/site_tests/sleeptest/control
902 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -0700903 suite_name: If control_path is not specified but a suite_name is
904 specified, list the control files belonging to that suite instead of
905 all control files. The empty string for suite_name will list all control
906 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -0800907
Scott Zawalski4647ce62012-01-03 17:17:28 -0500908 Returns:
909 Contents of a control file if control_path is provided.
910 A list of control files if no control_path is provided.
911 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800912 if is_deprecated_server():
913 raise DeprecatedRPCError('controlfiles')
914
Don Garrettf84631a2014-01-07 18:21:26 -0800915 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500916 return _PrintDocStringAsHTML(self.controlfiles)
917
Don Garrettf84631a2014-01-07 18:21:26 -0800918 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800919 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
920 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500921
Don Garrettf84631a2014-01-07 18:21:26 -0800922 if 'control_path' not in kwargs:
923 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -0700924 return common_util.GetControlFileListForSuite(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700925 self._static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -0700926 else:
927 return common_util.GetControlFileList(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700928 self._static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500929 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700930 return common_util.GetControlFile(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700931 self._static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800932
933 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -0700934 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700935 """Translates an xBuddy path to a real path to artifact if it exists.
936
937 Args:
Simran Basi99e63c02014-05-20 10:39:52 -0700938 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
939 Local searches the devserver's static directory. Remote searches a
940 Google Storage image archive.
941
942 Kwargs:
943 image_dir: Google Storage image archive to search in if requesting a
944 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700945
946 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -0700947 String in the format of build_id/artifact as stored on the local server
948 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700949 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800950 if is_deprecated_server():
951 raise DeprecatedRPCError('xbuddy_translate')
952
Simran Basi99e63c02014-05-20 10:39:52 -0700953 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -0700954 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700955 response = os.path.join(build_id, filename)
956 _Log('Path translation requested, returning: %s', response)
957 return response
958
959 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -0700960 def xbuddy(self, *args, **kwargs):
961 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -0700962
963 Args:
joycheneaf4cfc2013-07-02 08:38:57 -0700964 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -0700965 components of the path. The path can be understood as
966 "{local|remote}/build_id/artifact" where build_id is composed of
967 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -0700968
joychen121fc9b2013-08-02 14:30:30 -0700969 The first path element is optional, and can be "remote" or "local"
970 If local (the default), devserver will not attempt to access Google
971 Storage, and will only search the static directory for the files.
972 If remote, devserver will try to obtain the artifact off GS if it's
973 not found locally.
974 The board is the familiar board name, optionally suffixed.
975 The version can be the google storage version number, and may also be
976 any of a number of xBuddy defined version aliases that will be
977 translated into the latest built image that fits the description.
978 Defaults to latest.
979 The artifact is one of a number of image or artifact aliases used by
980 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -0700981
982 Kwargs:
joychen3cb228e2013-06-12 12:13:13 -0700983 return_dir: {true|false}
984 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800985 relative_path: {true|false}
986 if set to true, returns the relative path to the payload
987 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -0700988 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -0700989 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -0700990 or
joycheneaf4cfc2013-07-02 08:38:57 -0700991 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -0700992
993 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800994 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
995 http://host:port/static/x86-generic-release/R26-4000.0.0/
996 If |relative_path| is true, return a relative path the folder where the
997 payloads are. E.g.,
998 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -0700999 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001000 if is_deprecated_server():
1001 raise DeprecatedRPCError('xbuddy')
1002
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001003 boolean_string = kwargs.get('return_dir')
1004 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1005 boolean_string = kwargs.get('relative_path')
1006 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001007
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001008 if return_dir and relative_path:
Amin Hassani722e0962019-11-15 15:45:31 -08001009 raise DevServerHTTPError(
Amin Hassanid4e35392019-10-03 11:02:44 -07001010 http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001011 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001012
Amin Hassania4bc5652020-10-19 12:44:24 -07001013 build_id, file_name = self._xbuddy.Get(args)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001014
1015 response = None
1016 if return_dir:
1017 response = os.path.join(cherrypy.request.base, 'static', build_id)
1018 _Log('Directory requested, returning: %s', response)
1019 elif relative_path:
1020 response = build_id
1021 _Log('Relative path requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001022 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001023 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001024 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001025 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001026 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001027
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001028 return response
1029
joychen3cb228e2013-06-12 12:13:13 -07001030 @cherrypy.expose
joychen3cb228e2013-06-12 12:13:13 -07001031 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001032 """Returns the number of images cached by xBuddy."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001033 if is_deprecated_server():
1034 raise DeprecatedRPCError('xbuddy_capacity')
1035
joychen3cb228e2013-06-12 12:13:13 -07001036 return self._xbuddy.Capacity()
1037
1038 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001039 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001040 """Presents a welcome message and documentation links."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001041 if is_deprecated_server():
1042 raise DeprecatedRPCError('index')
1043
Congbin Guo6bc32182019-08-20 17:54:30 -07001044 html_template = (
1045 'Welcome to the Dev Server!<br>\n'
1046 '<br>\n'
1047 'Here are the available methods, click for documentation:<br>\n'
1048 '<br>\n'
1049 '%s')
1050
1051 exposed_methods = []
1052 for app in cherrypy.tree.apps.values():
1053 exposed_methods += _FindExposedMethods(
1054 app.root, app.script_name.lstrip('/'),
1055 unlisted=self._UNLISTED_METHODS)
1056
1057 return html_template % '<br>\n'.join(
1058 ['<a href=doc/%s>%s</a>' % (name, name)
1059 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001060
1061 @cherrypy.expose
1062 def doc(self, *args):
1063 """Shows the documentation for available methods / URLs.
1064
Amin Hassani08e42d22019-06-03 00:31:30 -07001065 Examples:
Amin Hassani3de3e2b2020-10-19 13:34:10 -07001066 http://myhost/doc/xbuddy
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001067 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001068 if is_deprecated_server():
1069 raise DeprecatedRPCError('doc')
1070
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001071 name = '/'.join(args)
Congbin Guo6bc32182019-08-20 17:54:30 -07001072 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001073 if not method:
Amin Hassanid4e35392019-10-03 11:02:44 -07001074 raise DevServerError("No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001075 if not method.__doc__:
Amin Hassanid4e35392019-10-03 11:02:44 -07001076 raise DevServerError("No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001077 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001078
Dan Shif5ce2de2013-04-25 16:06:32 -07001079
Chris Sosadbc20082012-12-10 13:39:11 -08001080def _CleanCache(cache_dir, wipe):
1081 """Wipes any excess cached items in the cache_dir.
1082
1083 Args:
1084 cache_dir: the directory we are wiping from.
1085 wipe: If True, wipe all the contents -- not just the excess.
1086 """
1087 if wipe:
1088 # Clear the cache and exit on error.
1089 cmd = 'rm -rf %s/*' % cache_dir
1090 if os.system(cmd) != 0:
1091 _Log('Failed to clear the cache with %s' % cmd)
1092 sys.exit(1)
1093 else:
1094 # Clear all but the last N cached updates
1095 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1096 (cache_dir, CACHED_ENTRIES))
1097 if os.system(cmd) != 0:
1098 _Log('Failed to clean up old delta cache files with %s' % cmd)
1099 sys.exit(1)
1100
1101
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001102def _AddTestingOptions(parser):
1103 group = optparse.OptionGroup(
1104 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1105 'developers writing integration tests utilizing the devserver. They are '
1106 'not intended to be really used outside the scope of someone '
1107 'knowledgable about the test.')
1108 group.add_option('--exit',
1109 action='store_true',
Amin Hassanie9ffb862019-09-25 17:10:40 -07001110 help='do not start the server (yet clear cache)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001111 parser.add_option_group(group)
1112
1113
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001114def _AddProductionOptions(parser):
1115 group = optparse.OptionGroup(
1116 parser, 'Advanced Server Options', 'These options can be used to changed '
1117 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001118 group.add_option('--clear_cache',
1119 action='store_true', default=False,
1120 help='At startup, removes all cached entries from the'
Amin Hassanid4e35392019-10-03 11:02:44 -07001121 "devserver's cache.")
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001122 group.add_option('--logfile',
1123 metavar='PATH',
1124 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001125 group.add_option('--pidfile',
1126 metavar='PATH',
1127 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001128 group.add_option('--portfile',
1129 metavar='PATH',
1130 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001131 group.add_option('--production',
1132 action='store_true', default=False,
1133 help='have the devserver use production values when '
1134 'starting up. This includes using more threads and '
1135 'performing less logging.')
1136 parser.add_option_group(group)
1137
1138
Paul Hobbsef4e0702016-06-27 17:01:42 -07001139def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001140 """Create a LogHandler instance used to log all messages."""
1141 hdlr_cls = handlers.TimedRotatingFileHandler
1142 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001143 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001144 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001145 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001146 return hdlr
1147
1148
Chris Sosacde6bf42012-05-31 18:36:39 -07001149def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001150 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001151 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001152
1153 # get directory that the devserver is run from
1154 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001155 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001156 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001157 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001158 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001159 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001160 parser.add_option('--port',
1161 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001162 help=('port for the dev server to use; if zero, binds to '
1163 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001164 parser.add_option('-t', '--test_image',
1165 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001166 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001167 parser.add_option('-x', '--xbuddy_manage_builds',
1168 action='store_true',
1169 default=False,
1170 help='If set, allow xbuddy to manage images in'
1171 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001172 parser.add_option('-a', '--android_build_credential',
1173 default=None,
1174 help='Path to a json file which contains the credential '
1175 'needed to access Android builds.')
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001176 parser.add_option('--infra_removal',
1177 action='store_true', default=False,
1178 help='If option is present, some RPCs will be disabled to '
1179 'help with infra removal efforts. See '
1180 'go/devserver-deprecation')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001181 _AddProductionOptions(parser)
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001182 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001183 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001184
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001185 # Handle options that must be set globally in cherrypy. Do this
1186 # work up front, because calls to _Log() below depend on this
1187 # initialization.
1188 if options.production:
1189 cherrypy.config.update({'environment': 'production'})
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001190 cherrypy.config.update({'infra_removal': options.infra_removal})
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001191 if not options.logfile:
1192 cherrypy.config.update({'log.screen': True})
1193 else:
1194 cherrypy.config.update({'log.error_file': '',
1195 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001196 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001197 # Pylint can't seem to process these two calls properly
1198 # pylint: disable=E1101
1199 cherrypy.log.access_log.addHandler(hdlr)
1200 cherrypy.log.error_log.addHandler(hdlr)
1201 # pylint: enable=E1101
1202
joychened64b222013-06-21 16:39:34 -07001203 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001204 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001205
joychened64b222013-06-21 16:39:34 -07001206 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001207 # If our devserver is only supposed to serve payloads, we shouldn't be
1208 # mucking with the cache at all. If the devserver hadn't previously
1209 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001210 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001211 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001212 else:
1213 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001214
Amin Hassanief523622020-07-06 12:09:23 -07001215 pkgroot_dir = os.path.join(options.static_dir, 'pkgroot')
1216 common_util.SymlinkFile('/build', pkgroot_dir)
1217
Chris Sosadbc20082012-12-10 13:39:11 -08001218 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001219 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001220
Amin Hassanie9ffb862019-09-25 17:10:40 -07001221 _xbuddy = xbuddy.XBuddy(manage_builds=options.xbuddy_manage_builds,
joychen121fc9b2013-08-02 14:30:30 -07001222 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001223 if options.clear_cache and options.xbuddy_manage_builds:
1224 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001225
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001226 if options.exit:
1227 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001228
Amin Hassani3de3e2b2020-10-19 13:34:10 -07001229 dev_server = DevServerRoot(_xbuddy, options.static_dir)
Congbin Guo3afae6c2019-08-13 16:29:42 -07001230 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 12:13:13 -07001231
Chris Sosa855b8932013-08-21 13:24:55 -07001232 if options.pidfile:
1233 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1234
Gilad Arnold11fbef42014-02-10 11:04:13 -08001235 if options.portfile:
1236 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1237
Dan Shiafd5c6c2016-01-07 10:27:03 -08001238 if (options.android_build_credential and
1239 os.path.exists(options.android_build_credential)):
1240 try:
1241 with open(options.android_build_credential) as f:
1242 android_build.BuildAccessor.credential_info = json.load(f)
1243 except ValueError as e:
1244 _Log('Failed to load the android build credential: %s. Error: %s.' %
1245 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 16:29:42 -07001246
1247 cherrypy.tree.mount(health_checker_app, '/check_health',
1248 config=health_checker.get_config())
joychen3cb228e2013-06-12 12:13:13 -07001249 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001250
1251
1252if __name__ == '__main__':
1253 main()