blob: 23eb4fc618c7dd04f98990591284f8d142b93b19 [file] [log] [blame]
Keith Haddow58f36d12020-10-28 16:16:39 +00001#!/usr/bin/env python3
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())
Keith Haddow58f36d12020-10-28 16:16:39 +0000646 dl.Download(factory)
Dan Shi59ae7092013-06-04 14:37:27 -0700647 finally:
648 with DevServerRoot._staging_thread_count_lock:
649 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800650 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700651
652 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -0800653 def locate_file(self, **kwargs):
654 """Get the path to the given file name.
655
656 This method looks up the given file name inside specified build artifacts.
657 One use case is to help caller to locate an apk file inside a build
658 artifact. The location of the apk file could be different based on the
659 branch and target.
660
661 Args:
662 file_name: Name of the file to look for.
663 artifacts: A list of artifact names to search for the file.
664
665 Returns:
666 Path to the file with the given name. It's relative to the folder for the
667 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -0800668 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800669 if is_deprecated_server():
670 raise DeprecatedRPCError('locate_file')
671
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700672 dl, _ = _get_downloader_and_factory(self._static_dir, kwargs)
Dan Shi2f136862016-02-11 15:38:38 -0800673 try:
Joe Brennan1691f8e2017-03-15 15:53:36 -0700674 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 15:38:38 -0800675 artifacts = kwargs['artifacts']
676 except KeyError:
Amin Hassanid4e35392019-10-03 11:02:44 -0700677 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700678 '`file_name` and `artifacts` are required to search '
679 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 15:38:38 -0800680 build_path = dl.GetBuildDir()
681 for artifact in artifacts:
682 # Get the unzipped folder of the artifact. If it's not defined in
683 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
684 # directory directly.
685 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
686 artifact_path = os.path.join(build_path, folder)
687 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 15:53:36 -0700688 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 15:38:38 -0800689 return os.path.relpath(os.path.join(root, file_name), build_path)
Amin Hassanid4e35392019-10-03 11:02:44 -0700690 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700691 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 15:38:38 -0800692
693 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800694 def setup_telemetry(self, **kwargs):
695 """Extracts and sets up telemetry
696
697 This method goes through the telemetry deps packages, and stages them on
698 the devserver to be used by the drones and the telemetry tests.
699
700 Args:
701 archive_url: Google Storage URL for the build.
702
703 Returns:
704 Path to the source folder for the telemetry codebase once it is staged.
705 """
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700706 dl = _get_downloader(self._static_dir, kwargs)
Simran Basi4baad082013-02-14 13:39:18 -0800707
Gabe Black3b567202015-09-23 14:07:59 -0700708 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -0800709 deps_path = os.path.join(build_path, 'autotest/packages')
710 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
711 src_folder = os.path.join(telemetry_path, 'src')
712
713 with self._telemetry_lock_dict.lock(telemetry_path):
714 if os.path.exists(src_folder):
715 # Telemetry is already fully stage return
716 return src_folder
717
718 common_util.MkDirP(telemetry_path)
719
720 # Copy over the required deps tar balls to the telemetry directory.
721 for dep in TELEMETRY_DEPS:
722 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700723 if not os.path.exists(dep_path):
724 # This dep does not exist (could be new), do not extract it.
725 continue
Simran Basi4baad082013-02-14 13:39:18 -0800726 try:
727 common_util.ExtractTarball(dep_path, telemetry_path)
728 except common_util.CommonUtilError as e:
729 shutil.rmtree(telemetry_path)
Amin Hassanid4e35392019-10-03 11:02:44 -0700730 raise DevServerError(str(e))
Simran Basi4baad082013-02-14 13:39:18 -0800731
732 # By default all the tarballs extract to test_src but some parts of
733 # the telemetry code specifically hardcoded to exist inside of 'src'.
734 test_src = os.path.join(telemetry_path, 'test_src')
735 try:
736 shutil.move(test_src, src_folder)
737 except shutil.Error:
738 # This can occur if src_folder already exists. Remove and retry move.
739 shutil.rmtree(src_folder)
Amin Hassanid4e35392019-10-03 11:02:44 -0700740 raise DevServerError(
Gabe Black3b567202015-09-23 14:07:59 -0700741 'Failure in telemetry setup for build %s. Appears that the '
742 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -0800743
744 return src_folder
745
746 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800747 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700748 """Symbolicates a minidump using pre-downloaded symbols, returns it.
749
750 Callers will need to POST to this URL with a body of MIME-type
751 "multipart/form-data".
752 The body should include a single argument, 'minidump', containing the
753 binary-formatted minidump to symbolicate.
754
Chris Masone816e38c2012-05-02 12:22:36 -0700755 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800756 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700757 minidump: The binary minidump file to symbolicate.
758 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800759 if is_deprecated_server():
760 raise DeprecatedRPCError('symbolicate_dump')
761
Chris Sosa76e44b92013-01-31 12:11:38 -0800762 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -0700763 # Try debug.tar.xz first, then debug.tgz
764 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
765 kwargs['artifacts'] = artifact
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700766 dl = _get_downloader(self._static_dir, kwargs)
Dan Shif08fe492016-10-04 14:39:25 -0700767
768 try:
769 if self.stage(**kwargs) == 'Success':
770 break
771 except build_artifact.ArtifactDownloadError:
772 continue
773 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700774 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700775 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -0800776
Chris Masone816e38c2012-05-02 12:22:36 -0700777 to_return = ''
778 with tempfile.NamedTemporaryFile() as local:
779 while True:
780 data = minidump.file.read(8192)
781 if not data:
782 break
783 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800784
Chris Masone816e38c2012-05-02 12:22:36 -0700785 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800786
Gabe Black3b567202015-09-23 14:07:59 -0700787 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -0800788
xixuanab744382017-04-27 10:41:27 -0700789 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 12:11:38 -0800790 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 10:41:27 -0700791 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 12:11:38 -0800792 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
793
Chris Masone816e38c2012-05-02 12:22:36 -0700794 to_return, error_text = stackwalk.communicate()
795 if stackwalk.returncode != 0:
Amin Hassanid4e35392019-10-03 11:02:44 -0700796 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700797 "Can't generate stack trace: %s (rc=%d)" % (error_text,
798 stackwalk.returncode))
Chris Masone816e38c2012-05-02 12:22:36 -0700799
800 return to_return
801
802 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800803 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -0400804 """Return a string representing the latest build for a given target.
805
806 Args:
807 target: The build target, typically a combination of the board and the
808 type of build e.g. x86-mario-release.
809 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
810 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -0800811
Scott Zawalski16954532012-03-20 15:31:36 -0400812 Returns:
813 A string representation of the latest build if one exists, i.e.
814 R19-1993.0.0-a1-b1480.
815 An empty string if no latest could be found.
816 """
Sanika Kulkarni07d47ed2020-08-06 14:56:46 -0700817 if is_deprecated_server():
818 raise DeprecatedRPCError('latestbuild')
819
Don Garrettf84631a2014-01-07 18:21:26 -0800820 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -0400821 return _PrintDocStringAsHTML(self.latestbuild)
822
Don Garrettf84631a2014-01-07 18:21:26 -0800823 if 'target' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800824 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
825 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -0700826
827 if _is_android_build_request(kwargs):
828 branch = kwargs.get('branch', None)
829 target = kwargs.get('target', None)
830 if not target or not branch:
Amin Hassanid4e35392019-10-03 11:02:44 -0700831 raise DevServerError('Both target and branch must be specified to query'
832 ' for the latest Android build.')
Dan Shi61305df2015-10-26 16:52:35 -0700833 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
834
Scott Zawalski16954532012-03-20 15:31:36 -0400835 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700836 return common_util.GetLatestBuildVersion(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700837 self._static_dir, kwargs['target'],
Don Garrettf84631a2014-01-07 18:21:26 -0800838 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700839 except common_util.CommonUtilError as errmsg:
Amin Hassani722e0962019-11-15 15:45:31 -0800840 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
841 str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -0400842
843 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -0700844 def list_suite_controls(self, **kwargs):
845 """Return a list of contents of all known control files.
846
847 Example URL:
848 To List all control files' content:
849 http://dev-server/list_suite_controls?suite_name=bvt&
850 build=daisy_spring-release/R29-4279.0.0
851
852 Args:
853 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
854 suite_name: List the control files belonging to that suite.
855
856 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -0700857 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -0700858 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800859 if is_deprecated_server():
860 raise DeprecatedRPCError('list_suite_controls')
861
xixuan7efd0002016-04-14 15:34:01 -0700862 if not kwargs:
863 return _PrintDocStringAsHTML(self.controlfiles)
864
865 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800866 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
867 'Error: build= is required!')
xixuan7efd0002016-04-14 15:34:01 -0700868
869 if 'suite_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800870 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
871 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -0700872
873 control_file_list = [
874 line.rstrip() for line in common_util.GetControlFileListForSuite(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700875 self._static_dir, kwargs['build'],
xixuan7efd0002016-04-14 15:34:01 -0700876 kwargs['suite_name']).splitlines()]
877
Dan Shia1cd6522016-04-18 16:07:21 -0700878 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -0700879 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -0700880 control_file_content_dict[control_path] = (common_util.GetControlFile(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700881 self._static_dir, kwargs['build'], control_path))
xixuan7efd0002016-04-14 15:34:01 -0700882
Dan Shia1cd6522016-04-18 16:07:21 -0700883 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -0700884
885 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800886 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500887 """Return a control file or a list of all known control files.
888
889 Example URL:
890 To List all control files:
beepsbd337242013-07-09 22:44:06 -0700891 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
892 To List all control files for, say, the bvt suite:
893 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500894 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500895 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 -0500896
897 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500898 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500899 control_path: If you want the contents of a control file set this
900 to the path. E.g. client/site_tests/sleeptest/control
901 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -0700902 suite_name: If control_path is not specified but a suite_name is
903 specified, list the control files belonging to that suite instead of
904 all control files. The empty string for suite_name will list all control
905 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -0800906
Scott Zawalski4647ce62012-01-03 17:17:28 -0500907 Returns:
908 Contents of a control file if control_path is provided.
909 A list of control files if no control_path is provided.
910 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800911 if is_deprecated_server():
912 raise DeprecatedRPCError('controlfiles')
913
Don Garrettf84631a2014-01-07 18:21:26 -0800914 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500915 return _PrintDocStringAsHTML(self.controlfiles)
916
Don Garrettf84631a2014-01-07 18:21:26 -0800917 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800918 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
919 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500920
Don Garrettf84631a2014-01-07 18:21:26 -0800921 if 'control_path' not in kwargs:
922 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -0700923 return common_util.GetControlFileListForSuite(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700924 self._static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -0700925 else:
926 return common_util.GetControlFileList(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700927 self._static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500928 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700929 return common_util.GetControlFile(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700930 self._static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800931
932 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -0700933 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700934 """Translates an xBuddy path to a real path to artifact if it exists.
935
936 Args:
Simran Basi99e63c02014-05-20 10:39:52 -0700937 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
938 Local searches the devserver's static directory. Remote searches a
939 Google Storage image archive.
940
941 Kwargs:
942 image_dir: Google Storage image archive to search in if requesting a
943 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700944
945 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -0700946 String in the format of build_id/artifact as stored on the local server
947 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700948 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800949 if is_deprecated_server():
950 raise DeprecatedRPCError('xbuddy_translate')
951
Simran Basi99e63c02014-05-20 10:39:52 -0700952 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -0700953 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700954 response = os.path.join(build_id, filename)
955 _Log('Path translation requested, returning: %s', response)
956 return response
957
958 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -0700959 def xbuddy(self, *args, **kwargs):
960 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -0700961
962 Args:
joycheneaf4cfc2013-07-02 08:38:57 -0700963 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -0700964 components of the path. The path can be understood as
965 "{local|remote}/build_id/artifact" where build_id is composed of
966 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -0700967
joychen121fc9b2013-08-02 14:30:30 -0700968 The first path element is optional, and can be "remote" or "local"
969 If local (the default), devserver will not attempt to access Google
970 Storage, and will only search the static directory for the files.
971 If remote, devserver will try to obtain the artifact off GS if it's
972 not found locally.
973 The board is the familiar board name, optionally suffixed.
974 The version can be the google storage version number, and may also be
975 any of a number of xBuddy defined version aliases that will be
976 translated into the latest built image that fits the description.
977 Defaults to latest.
978 The artifact is one of a number of image or artifact aliases used by
979 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -0700980
981 Kwargs:
joychen3cb228e2013-06-12 12:13:13 -0700982 return_dir: {true|false}
983 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800984 relative_path: {true|false}
985 if set to true, returns the relative path to the payload
986 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -0700987 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -0700988 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -0700989 or
joycheneaf4cfc2013-07-02 08:38:57 -0700990 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -0700991
992 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800993 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
994 http://host:port/static/x86-generic-release/R26-4000.0.0/
995 If |relative_path| is true, return a relative path the folder where the
996 payloads are. E.g.,
997 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -0700998 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800999 if is_deprecated_server():
1000 raise DeprecatedRPCError('xbuddy')
1001
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001002 boolean_string = kwargs.get('return_dir')
1003 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1004 boolean_string = kwargs.get('relative_path')
1005 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001006
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001007 if return_dir and relative_path:
Amin Hassani722e0962019-11-15 15:45:31 -08001008 raise DevServerHTTPError(
Amin Hassanid4e35392019-10-03 11:02:44 -07001009 http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001010 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001011
Amin Hassania4bc5652020-10-19 12:44:24 -07001012 build_id, file_name = self._xbuddy.Get(args)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001013
1014 response = None
1015 if return_dir:
1016 response = os.path.join(cherrypy.request.base, 'static', build_id)
1017 _Log('Directory requested, returning: %s', response)
1018 elif relative_path:
1019 response = build_id
1020 _Log('Relative path requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001021 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001022 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001023 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001024 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001025 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001026
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001027 return response
1028
joychen3cb228e2013-06-12 12:13:13 -07001029 @cherrypy.expose
joychen3cb228e2013-06-12 12:13:13 -07001030 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001031 """Returns the number of images cached by xBuddy."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001032 if is_deprecated_server():
1033 raise DeprecatedRPCError('xbuddy_capacity')
1034
joychen3cb228e2013-06-12 12:13:13 -07001035 return self._xbuddy.Capacity()
1036
1037 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001038 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001039 """Presents a welcome message and documentation links."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001040 if is_deprecated_server():
1041 raise DeprecatedRPCError('index')
1042
Congbin Guo6bc32182019-08-20 17:54:30 -07001043 html_template = (
1044 'Welcome to the Dev Server!<br>\n'
1045 '<br>\n'
1046 'Here are the available methods, click for documentation:<br>\n'
1047 '<br>\n'
1048 '%s')
1049
1050 exposed_methods = []
1051 for app in cherrypy.tree.apps.values():
1052 exposed_methods += _FindExposedMethods(
1053 app.root, app.script_name.lstrip('/'),
1054 unlisted=self._UNLISTED_METHODS)
1055
1056 return html_template % '<br>\n'.join(
1057 ['<a href=doc/%s>%s</a>' % (name, name)
1058 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001059
1060 @cherrypy.expose
1061 def doc(self, *args):
1062 """Shows the documentation for available methods / URLs.
1063
Amin Hassani08e42d22019-06-03 00:31:30 -07001064 Examples:
Amin Hassani3de3e2b2020-10-19 13:34:10 -07001065 http://myhost/doc/xbuddy
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001066 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001067 if is_deprecated_server():
1068 raise DeprecatedRPCError('doc')
1069
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001070 name = '/'.join(args)
Congbin Guo6bc32182019-08-20 17:54:30 -07001071 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001072 if not method:
Amin Hassanid4e35392019-10-03 11:02:44 -07001073 raise DevServerError("No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001074 if not method.__doc__:
Amin Hassanid4e35392019-10-03 11:02:44 -07001075 raise DevServerError("No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001076 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001077
Dan Shif5ce2de2013-04-25 16:06:32 -07001078
Chris Sosadbc20082012-12-10 13:39:11 -08001079def _CleanCache(cache_dir, wipe):
1080 """Wipes any excess cached items in the cache_dir.
1081
1082 Args:
1083 cache_dir: the directory we are wiping from.
1084 wipe: If True, wipe all the contents -- not just the excess.
1085 """
1086 if wipe:
1087 # Clear the cache and exit on error.
1088 cmd = 'rm -rf %s/*' % cache_dir
1089 if os.system(cmd) != 0:
1090 _Log('Failed to clear the cache with %s' % cmd)
1091 sys.exit(1)
1092 else:
1093 # Clear all but the last N cached updates
1094 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1095 (cache_dir, CACHED_ENTRIES))
1096 if os.system(cmd) != 0:
1097 _Log('Failed to clean up old delta cache files with %s' % cmd)
1098 sys.exit(1)
1099
1100
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001101def _AddTestingOptions(parser):
1102 group = optparse.OptionGroup(
1103 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1104 'developers writing integration tests utilizing the devserver. They are '
1105 'not intended to be really used outside the scope of someone '
1106 'knowledgable about the test.')
1107 group.add_option('--exit',
1108 action='store_true',
Amin Hassanie9ffb862019-09-25 17:10:40 -07001109 help='do not start the server (yet clear cache)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001110 parser.add_option_group(group)
1111
1112
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001113def _AddProductionOptions(parser):
1114 group = optparse.OptionGroup(
1115 parser, 'Advanced Server Options', 'These options can be used to changed '
1116 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001117 group.add_option('--clear_cache',
1118 action='store_true', default=False,
1119 help='At startup, removes all cached entries from the'
Amin Hassanid4e35392019-10-03 11:02:44 -07001120 "devserver's cache.")
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001121 group.add_option('--logfile',
1122 metavar='PATH',
1123 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001124 group.add_option('--pidfile',
1125 metavar='PATH',
1126 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001127 group.add_option('--portfile',
1128 metavar='PATH',
1129 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001130 group.add_option('--production',
1131 action='store_true', default=False,
1132 help='have the devserver use production values when '
1133 'starting up. This includes using more threads and '
1134 'performing less logging.')
1135 parser.add_option_group(group)
1136
1137
Paul Hobbsef4e0702016-06-27 17:01:42 -07001138def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001139 """Create a LogHandler instance used to log all messages."""
1140 hdlr_cls = handlers.TimedRotatingFileHandler
1141 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001142 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001143 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001144 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001145 return hdlr
1146
1147
Chris Sosacde6bf42012-05-31 18:36:39 -07001148def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001149 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001150 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001151
1152 # get directory that the devserver is run from
1153 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001154 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001155 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001156 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001157 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001158 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001159 parser.add_option('--port',
1160 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001161 help=('port for the dev server to use; if zero, binds to '
1162 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001163 parser.add_option('-t', '--test_image',
1164 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001165 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001166 parser.add_option('-x', '--xbuddy_manage_builds',
1167 action='store_true',
1168 default=False,
1169 help='If set, allow xbuddy to manage images in'
1170 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001171 parser.add_option('-a', '--android_build_credential',
1172 default=None,
1173 help='Path to a json file which contains the credential '
1174 'needed to access Android builds.')
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001175 parser.add_option('--infra_removal',
1176 action='store_true', default=False,
1177 help='If option is present, some RPCs will be disabled to '
1178 'help with infra removal efforts. See '
1179 'go/devserver-deprecation')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001180 _AddProductionOptions(parser)
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001181 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001182 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001183
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001184 # Handle options that must be set globally in cherrypy. Do this
1185 # work up front, because calls to _Log() below depend on this
1186 # initialization.
1187 if options.production:
1188 cherrypy.config.update({'environment': 'production'})
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001189 cherrypy.config.update({'infra_removal': options.infra_removal})
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001190 if not options.logfile:
1191 cherrypy.config.update({'log.screen': True})
1192 else:
1193 cherrypy.config.update({'log.error_file': '',
1194 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001195 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001196 # Pylint can't seem to process these two calls properly
1197 # pylint: disable=E1101
1198 cherrypy.log.access_log.addHandler(hdlr)
1199 cherrypy.log.error_log.addHandler(hdlr)
1200 # pylint: enable=E1101
1201
joychened64b222013-06-21 16:39:34 -07001202 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001203 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001204
joychened64b222013-06-21 16:39:34 -07001205 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001206 # If our devserver is only supposed to serve payloads, we shouldn't be
1207 # mucking with the cache at all. If the devserver hadn't previously
1208 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001209 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001210 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001211 else:
1212 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001213
Amin Hassanief523622020-07-06 12:09:23 -07001214 pkgroot_dir = os.path.join(options.static_dir, 'pkgroot')
1215 common_util.SymlinkFile('/build', pkgroot_dir)
1216
Chris Sosadbc20082012-12-10 13:39:11 -08001217 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001218 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001219
Amin Hassanie9ffb862019-09-25 17:10:40 -07001220 _xbuddy = xbuddy.XBuddy(manage_builds=options.xbuddy_manage_builds,
joychen121fc9b2013-08-02 14:30:30 -07001221 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001222 if options.clear_cache and options.xbuddy_manage_builds:
1223 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001224
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001225 if options.exit:
1226 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001227
Amin Hassani3de3e2b2020-10-19 13:34:10 -07001228 dev_server = DevServerRoot(_xbuddy, options.static_dir)
Congbin Guo3afae6c2019-08-13 16:29:42 -07001229 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 12:13:13 -07001230
Chris Sosa855b8932013-08-21 13:24:55 -07001231 if options.pidfile:
1232 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1233
Gilad Arnold11fbef42014-02-10 11:04:13 -08001234 if options.portfile:
1235 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1236
Dan Shiafd5c6c2016-01-07 10:27:03 -08001237 if (options.android_build_credential and
1238 os.path.exists(options.android_build_credential)):
1239 try:
1240 with open(options.android_build_credential) as f:
1241 android_build.BuildAccessor.credential_info = json.load(f)
1242 except ValueError as e:
1243 _Log('Failed to load the android build credential: %s. Error: %s.' %
1244 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 16:29:42 -07001245
1246 cherrypy.tree.mount(health_checker_app, '/check_health',
1247 config=health_checker.get_config())
joychen3cb228e2013-06-12 12:13:13 -07001248 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001249
1250
1251if __name__ == '__main__':
1252 main()