blob: d713780f974419618b7e5898a060dd5f9ddfba60 [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
Eliot Courtneyf39420b2020-10-27 18:34:04 +090047from chromite.lib import cros_build_lib
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070048from chromite.lib.xbuddy import android_build
49from chromite.lib.xbuddy import artifact_info
50from chromite.lib.xbuddy import build_artifact
51from chromite.lib.xbuddy import cherrypy_log_util
52from chromite.lib.xbuddy import common_util
53from chromite.lib.xbuddy import devserver_constants
54from chromite.lib.xbuddy import downloader
55from chromite.lib.xbuddy import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070056
Gilad Arnoldc65330c2012-09-20 15:17:48 -070057# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080058def _Log(message, *args):
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070059 return cherrypy_log_util.LogWithTag('DEVSERVER', message, *args)
Frank Farzan40160872011-12-12 18:39:18 -080060
Chris Sosa417e55d2011-01-25 16:40:48 -080061CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080062
Simran Basi4baad082013-02-14 13:39:18 -080063TELEMETRY_FOLDER = 'telemetry_src'
64TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
65 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070066 'dep-chrome_test.tar.bz2',
67 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -080068
xixuan3d48bff2017-01-30 19:00:09 -080069# Log rotation parameters. These settings correspond to twice a day once
70# devserver is started, with about two weeks (28 backup files) of old logs
71# kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -070072#
xixuan3d48bff2017-01-30 19:00:09 -080073# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 11:05:19 -070074# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-30 19:00:09 -080075_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 16:29:42 -070076_LOG_ROTATION_INTERVAL = 12 # hours
77_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-12 18:39:18 -080078
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -080079# Error msg for deprecated RPC usage.
80DEPRECATED_RPC_ERROR_MSG = ('The %s RPC has been deprecated. Usage of this '
81 'RPC is discouraged. Please go to '
82 'go/devserver-deprecation for more information.')
83
xixuan52c2fba2016-05-20 17:02:48 -070084
Amin Hassanid4e35392019-10-03 11:02:44 -070085class DevServerError(Exception):
86 """Exception class used by DevServer."""
87
88
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -080089class DeprecatedRPCError(DevServerError):
90 """Exception class used when an RPC is deprecated but is still being used."""
91
92 def __init__(self, rpc_name):
93 """Constructor for DeprecatedRPCError class.
94
95 :param rpc_name: (str) name of the RPC that has been deprecated.
96 """
Amin Hassani6ecda232020-03-09 19:03:23 -070097 super(DeprecatedRPCError, self).__init__(
98 DEPRECATED_RPC_ERROR_MSG % rpc_name)
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -080099 self.rpc_name = rpc_name
100
101
Amin Hassani722e0962019-11-15 15:45:31 -0800102class DevServerHTTPError(cherrypy.HTTPError):
103 """Exception class to log the HTTPResponse before routing it to cherrypy."""
104 def __init__(self, status, message):
105 """CherryPy error with logging.
106
107 Args:
108 status: HTTPResponse status.
109 message: Message associated with the response.
110 """
111 cherrypy.HTTPError.__init__(self, status, message)
112 _Log('HTTPError status: %s message: %s', status, message)
113
114
Gabe Black3b567202015-09-23 14:07:59 -0700115def _canonicalize_archive_url(archive_url):
116 """Canonicalizes archive_url strings.
117
118 Raises:
119 DevserverError: if archive_url is not set.
120 """
121 if archive_url:
122 if not archive_url.startswith('gs://'):
Amin Hassanid4e35392019-10-03 11:02:44 -0700123 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700124 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 14:07:59 -0700125
126 return archive_url.rstrip('/')
127 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700128 raise DevServerError('Must specify an archive_url in the request')
Gabe Black3b567202015-09-23 14:07:59 -0700129
130
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700131def _canonicalize_local_path(local_path, static_dir):
Gabe Black3b567202015-09-23 14:07:59 -0700132 """Canonicalizes |local_path| strings.
133
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700134 Args:
135 local_path: The input path.
136 static_dir: Devserver's static cache directory.
137
Gabe Black3b567202015-09-23 14:07:59 -0700138 Raises:
139 DevserverError: if |local_path| is not set.
140 """
141 # Restrict staging of local content to only files within the static
142 # directory.
143 local_path = os.path.abspath(local_path)
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700144 if not local_path.startswith(static_dir):
Amin Hassanid4e35392019-10-03 11:02:44 -0700145 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700146 'Local path %s must be a subdirectory of the static'
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700147 ' directory: %s' % (local_path, static_dir))
Gabe Black3b567202015-09-23 14:07:59 -0700148
149 return local_path.rstrip('/')
150
151
152def _get_artifacts(kwargs):
153 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
154
155 Raises:
156 DevserverError if no artifacts would be returned.
157 """
158 artifacts = kwargs.get('artifacts')
159 files = kwargs.get('files')
160 if not artifacts and not files:
Amin Hassanid4e35392019-10-03 11:02:44 -0700161 raise DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700162
163 # Note we NEED to coerce files to a string as we get raw unicode from
164 # cherrypy and we treat files as strings elsewhere in the code.
165 return (str(artifacts).split(',') if artifacts else [],
166 str(files).split(',') if files else [])
167
168
Dan Shi61305df2015-10-26 16:52:35 -0700169def _is_android_build_request(kwargs):
170 """Check if a devserver call is for Android build, based on the arguments.
171
172 This method exams the request's arguments (os_type) to determine if the
173 request is for Android build. If os_type is set to `android`, returns True.
174 If os_type is not set or has other values, returns False.
175
176 Args:
177 kwargs: Keyword arguments for the request.
178
179 Returns:
180 True if the request is for Android build. False otherwise.
181 """
182 os_type = kwargs.get('os_type', None)
183 return os_type == 'android'
184
185
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700186def _get_downloader(static_dir, kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700187 """Returns the downloader based on passed in arguments.
188
189 Args:
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700190 static_dir: Devserver's static cache directory.
Amin Hassani08e42d22019-06-03 00:31:30 -0700191 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700192 """
193 local_path = kwargs.get('local_path')
194 if local_path:
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700195 local_path = _canonicalize_local_path(local_path, static_dir)
Gabe Black3b567202015-09-23 14:07:59 -0700196
197 dl = None
198 if local_path:
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800199 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700200 dl = downloader.LocalDownloader(static_dir, local_path,
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800201 delete_source=delete_source)
Gabe Black3b567202015-09-23 14:07:59 -0700202
Dan Shi61305df2015-10-26 16:52:35 -0700203 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700204 archive_url = kwargs.get('archive_url')
205 if not archive_url and not local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700206 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700207 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700208 if archive_url and local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700209 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700210 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700211 if not dl:
212 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700213 dl = downloader.GoogleStorageDownloader(
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700214 static_dir, archive_url,
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700215 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
216 archive_url))
Gabe Black3b567202015-09-23 14:07:59 -0700217 elif not dl:
218 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 12:10:33 -0700219 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 16:52:35 -0700220 build_id = kwargs.get('build_id', None)
221 if not target or not branch or not build_id:
Amin Hassanid4e35392019-10-03 11:02:44 -0700222 raise DevServerError('target, branch, build ID must all be specified for '
223 'downloading Android build.')
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700224 dl = downloader.AndroidBuildDownloader(static_dir, branch, build_id,
Dan Shi72b16132015-10-08 12:10:33 -0700225 target)
Gabe Black3b567202015-09-23 14:07:59 -0700226
227 return dl
228
229
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700230def _get_downloader_and_factory(static_dir, kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700231 """Returns the downloader and artifact factory based on passed in arguments.
232
233 Args:
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700234 static_dir: Devserver's static cache directory.
Amin Hassani08e42d22019-06-03 00:31:30 -0700235 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700236 """
237 artifacts, files = _get_artifacts(kwargs)
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700238 dl = _get_downloader(static_dir, kwargs)
Gabe Black3b567202015-09-23 14:07:59 -0700239
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700240 if (isinstance(dl, (downloader.GoogleStorageDownloader,
241 downloader.LocalDownloader))):
Gabe Black3b567202015-09-23 14:07:59 -0700242 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 12:10:33 -0700243 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 14:07:59 -0700244 factory_class = build_artifact.AndroidArtifactFactory
245 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700246 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700247 'Unrecognized value for downloader type: %s' % type(dl))
Gabe Black3b567202015-09-23 14:07:59 -0700248
249 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
250
251 return dl, factory
252
253
Scott Zawalski4647ce62012-01-03 17:17:28 -0500254def _LeadingWhiteSpaceCount(string):
255 """Count the amount of leading whitespace in a string.
256
257 Args:
258 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800259
Scott Zawalski4647ce62012-01-03 17:17:28 -0500260 Returns:
261 number of white space chars before characters start.
262 """
Gabe Black3b567202015-09-23 14:07:59 -0700263 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500264 if matched:
265 return len(matched.group())
266
267 return 0
268
269
270def _PrintDocStringAsHTML(func):
271 """Make a functions docstring somewhat HTML style.
272
273 Args:
274 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800275
Scott Zawalski4647ce62012-01-03 17:17:28 -0500276 Returns:
277 A string that is somewhat formated for a web browser.
278 """
279 # TODO(scottz): Make this parse Args/Returns in a prettier way.
280 # Arguments could be bolded and indented etc.
281 html_doc = []
282 for line in func.__doc__.splitlines():
283 leading_space = _LeadingWhiteSpaceCount(line)
284 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700285 line = ' ' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500286
287 html_doc.append('<BR>%s' % line)
288
289 return '\n'.join(html_doc)
290
291
Simran Basief83d6a2014-08-28 14:32:01 -0700292def _GetUpdateTimestampHandler(static_dir):
293 """Returns a handler to update directory staged.timestamp.
294
295 This handler resets the stage.timestamp whenever static content is accessed.
296
297 Args:
298 static_dir: Directory from which static content is being staged.
299
300 Returns:
Amin Hassani08e42d22019-06-03 00:31:30 -0700301 A cherrypy handler to update the timestamp of accessed content.
Simran Basief83d6a2014-08-28 14:32:01 -0700302 """
303 def UpdateTimestampHandler():
304 if not '404' in cherrypy.response.status:
305 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
306 cherrypy.request.path_info)
307 if build_match:
308 build_dir = os.path.join(static_dir, build_match.group('build'))
309 downloader.Downloader.TouchTimestampForStaged(build_dir)
310 return UpdateTimestampHandler
311
312
Chris Sosa7c931362010-10-11 19:49:01 -0700313def _GetConfig(options):
314 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800315
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800316 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800317 # Fall back to IPv4 when python is not configured with IPv6.
318 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800319 socket_host = '0.0.0.0'
320
Simran Basief83d6a2014-08-28 14:32:01 -0700321 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
322 # on the on_end_resource hook. This hook is called once processing is
323 # complete and the response is ready to be returned.
324 cherrypy.tools.update_timestamp = cherrypy.Tool(
325 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
326
David Riley2fcb0122017-11-02 11:25:39 -0700327 base_config = {
328 'global': {
329 'server.log_request_headers': True,
330 'server.protocol_version': 'HTTP/1.1',
331 'server.socket_host': socket_host,
332 'server.socket_port': int(options.port),
333 'response.timeout': 6000,
334 'request.show_tracebacks': True,
335 'server.socket_timeout': 60,
336 'server.thread_pool': 2,
337 'engine.autoreload.on': False,
338 },
David Riley2fcb0122017-11-02 11:25:39 -0700339 '/build': {
340 'response.timeout': 100000,
341 },
David Riley2fcb0122017-11-02 11:25:39 -0700342 # Sets up the static dir for file hosting.
343 '/static': {
344 'tools.staticdir.dir': options.static_dir,
345 'tools.staticdir.on': True,
346 'response.timeout': 10000,
347 'tools.update_timestamp.on': True,
348 },
349 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700350 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700351 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500352
Chris Sosa7c931362010-10-11 19:49:01 -0700353 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000354
Darin Petkove17164a2010-08-11 13:24:41 -0700355
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700356def _GetRecursiveMemberObject(root, member_list):
357 """Returns an object corresponding to a nested member list.
358
359 Args:
360 root: the root object to search
361 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800362
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700363 Returns:
364 An object corresponding to the member name list; None otherwise.
365 """
366 for member in member_list:
367 next_root = root.__class__.__dict__.get(member)
368 if not next_root:
369 return None
370 root = next_root
371 return root
372
373
374def _IsExposed(name):
375 """Returns True iff |name| has an `exposed' attribute and it is set."""
376 return hasattr(name, 'exposed') and name.exposed
377
378
Congbin Guo6bc32182019-08-20 17:54:30 -0700379def _GetExposedMethod(nested_member):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700380 """Returns a CherryPy-exposed method, if such exists.
381
382 Args:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700383 nested_member: a slash-joined path to the nested member
Don Garrettf84631a2014-01-07 18:21:26 -0800384
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700385 Returns:
Congbin Guo6bc32182019-08-20 17:54:30 -0700386 A function object corresponding to the path defined by |nested_member| from
387 the app root object registered, if the function is exposed; None otherwise.
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700388 """
Congbin Guo6bc32182019-08-20 17:54:30 -0700389 for app in cherrypy.tree.apps.values():
390 # Use the 'index' function doc as the doc of the app.
391 if nested_member == app.script_name.lstrip('/'):
392 nested_member = 'index'
393
394 method = _GetRecursiveMemberObject(app.root, nested_member.split('/'))
395 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
396 return method
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700397
398
Gilad Arnold748c8322012-10-12 09:51:35 -0700399def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700400 """Finds exposed CherryPy methods.
401
402 Args:
403 root: the root object for searching
404 prefix: slash-joined chain of members leading to current object
405 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800406
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700407 Returns:
408 List of exposed URLs that are not unlisted.
409 """
410 method_list = []
Congbin Guo6bc32182019-08-20 17:54:30 -0700411 for member in root.__class__.__dict__.keys():
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700412 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700413 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700414 continue
415 member_obj = root.__class__.__dict__[member]
416 if _IsExposed(member_obj):
Amin Hassani08e42d22019-06-03 00:31:30 -0700417 if isinstance(member_obj, types.FunctionType):
Congbin Guo6bc32182019-08-20 17:54:30 -0700418 # Regard the app name as exposed "method" name if it exposed 'index'
419 # function.
420 if prefix and member == 'index':
421 method_list.append(prefix)
422 else:
423 method_list.append(prefixed_member)
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700424 else:
425 method_list += _FindExposedMethods(
426 member_obj, prefixed_member, unlisted)
427 return method_list
428
429
xixuan52c2fba2016-05-20 17:02:48 -0700430def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-11-30 16:48:20 -0800431 """Parse boolean arg from kwargs.
432
433 Args:
434 kwargs: the parameters to be checked.
435 key: the key to be parsed.
436
437 Returns:
438 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
439
440 Raises:
441 DevServerHTTPError if kwargs[key] is not a boolean variable.
442 """
xixuan52c2fba2016-05-20 17:02:48 -0700443 if key in kwargs:
444 if kwargs[key] == 'True':
445 return True
446 elif kwargs[key] == 'False':
447 return False
448 else:
Amin Hassani722e0962019-11-15 15:45:31 -0800449 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
450 'The value for key %s is not boolean.' % key)
xixuan52c2fba2016-05-20 17:02:48 -0700451 else:
452 return False
453
xixuan447ad9d2017-02-28 14:46:20 -0800454
xixuanac89ce82016-11-30 16:48:20 -0800455def _parse_string_arg(kwargs, key):
456 """Parse string arg from kwargs.
457
458 Args:
459 kwargs: the parameters to be checked.
460 key: the key to be parsed.
461
462 Returns:
463 The string value of kwargs[key], or None if key doesn't exist in kwargs.
464 """
465 if key in kwargs:
466 return kwargs[key]
467 else:
468 return None
469
xixuan447ad9d2017-02-28 14:46:20 -0800470
xixuanac89ce82016-11-30 16:48:20 -0800471def _build_uri_from_build_name(build_name):
472 """Get build url from a given build name.
473
474 Args:
475 build_name: the build name to be parsed, whose format is
476 'board/release_version'.
477
478 Returns:
479 The release_archive_url on Google Storage for this build name.
480 """
Amin Hassani08e42d22019-06-03 00:31:30 -0700481 # TODO(ahassani): This function doesn't seem to be used anywhere since its
482 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
483 # causing any runtime issues. So deprecate this in the future.
484 tokens = build_name.split('/')
485 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-20 17:02:48 -0700486
xixuan447ad9d2017-02-28 14:46:20 -0800487
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800488def is_deprecated_server():
489 """Gets whether the devserver has deprecated RPCs."""
490 return cherrypy.config.get('infra_removal', False)
491
492
David Rochberg7c79a812011-01-19 14:24:45 -0500493class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700494 """The Root Class for the Dev Server.
495
496 CherryPy works as follows:
497 For each method in this class, cherrpy interprets root/path
498 as a call to an instance of DevServerRoot->method_name. For example,
499 a call to http://myhost/build will call build. CherryPy automatically
500 parses http args and places them as keyword arguments in each method.
501 For paths http://myhost/update/dir1/dir2, you can use *args so that
502 cherrypy uses the update method and puts the extra paths in args.
503 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700504 # Method names that should not be listed on the index page.
505 _UNLISTED_METHODS = ['index', 'doc']
506
Dan Shi59ae7092013-06-04 14:37:27 -0700507 # Number of threads that devserver is staging images.
508 _staging_thread_count = 0
509 # Lock used to lock increasing/decreasing count.
510 _staging_thread_count_lock = threading.Lock()
511
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700512 def __init__(self, _xbuddy, static_dir):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700513 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800514 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700515 self._xbuddy = _xbuddy
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700516 self._static_dir = static_dir
David Rochberg7c79a812011-01-19 14:24:45 -0500517
Congbin Guo3afae6c2019-08-13 16:29:42 -0700518 @property
519 def staging_thread_count(self):
520 """Get the staging thread count."""
521 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700522
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700523 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500524 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700525 """Builds the package specified."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800526 if is_deprecated_server():
527 raise DeprecatedRPCError('build')
528
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700529 import builder
530 if self._builder is None:
531 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500532 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700533
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700534 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700535 def is_staged(self, **kwargs):
536 """Check if artifacts have been downloaded.
537
Congbin Guo3afae6c2019-08-13 16:29:42 -0700538 Examples:
539 To check if autotest and test_suites are staged:
540 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
541 artifacts=autotest,test_suites
542
Amin Hassani08e42d22019-06-03 00:31:30 -0700543 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700544 async: True to return without waiting for download to complete.
545 artifacts: Comma separated list of named artifacts to download.
546 These are defined in artifact_info and have their implementation
547 in build_artifact.py.
548 files: Comma separated list of file artifacts to stage. These
549 will be available as is in the corresponding static directory with no
550 custom post-processing.
551
Congbin Guo3afae6c2019-08-13 16:29:42 -0700552 Returns:
553 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700554 """
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700555 dl, factory = _get_downloader_and_factory(self._static_dir, kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700556 response = str(dl.IsStaged(factory))
557 _Log('Responding to is_staged %s request with %r', kwargs, response)
558 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700559
Chris Sosa76e44b92013-01-31 12:11:38 -0800560 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800561 def list_image_dir(self, **kwargs):
562 """Take an archive url and list the contents in its staged directory.
563
Amin Hassani08e42d22019-06-03 00:31:30 -0700564 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800565 To list the contents of where this devserver should have staged
566 gs://image-archive/<board>-release/<build> call:
567 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
568
Congbin Guo3afae6c2019-08-13 16:29:42 -0700569 Args:
570 archive_url: Google Storage URL for the build.
571
Prashanth Ba06d2d22014-03-07 15:35:19 -0800572 Returns:
573 A string with information about the contents of the image directory.
574 """
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700575 dl = _get_downloader(self._static_dir, kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800576 try:
Gabe Black3b567202015-09-23 14:07:59 -0700577 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800578 except build_artifact.ArtifactDownloadError as e:
579 return 'Cannot list the contents of staged artifacts. %s' % e
580 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700581 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800582 return image_dir_contents
583
584 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800585 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700586 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800587
Gabe Black3b567202015-09-23 14:07:59 -0700588 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700589 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700590 on the devserver. A call to this will attempt to cache non-specified
591 artifacts in the background for the given from the given URL following
592 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800593 artifacts is explicitly defined in the build_artifact module.
594
595 These artifacts will then be available from the static/ sub-directory of
596 the devserver.
597
Amin Hassani08e42d22019-06-03 00:31:30 -0700598 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800599 To download the autotest and test suites tarballs:
600 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
601 artifacts=autotest,test_suites
602 To download the full update payload:
603 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
604 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700605 To download just a file called blah.bin:
606 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
607 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800608
609 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700610 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800611
612 Note for this example, relative path is the archive_url stripped of its
613 basename i.e. path/ in the examples above. Specific example:
614
615 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
616
617 Will get staged to:
618
joychened64b222013-06-21 16:39:34 -0700619 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700620
621 Args:
622 archive_url: Google Storage URL for the build.
623 local_path: Local path for the build.
624 delete_source: Only meaningful with local_path. bool to indicate if the
625 source files should be deleted. This is especially useful when staging
626 a file locally in resource constrained environments as it allows us to
627 move the relevant files locally instead of copying them.
628 async: True to return without waiting for download to complete.
629 artifacts: Comma separated list of named artifacts to download.
630 These are defined in artifact_info and have their implementation
631 in build_artifact.py.
632 files: Comma separated list of files to stage. These
633 will be available as is in the corresponding static directory with no
634 custom post-processing.
635 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800636 """
Amin Hassani3de3e2b2020-10-19 13:34:10 -0700637 dl, factory = _get_downloader_and_factory(self._static_dir, kwargs)
Gabe Black3b567202015-09-23 14:07:59 -0700638
Dan Shi59ae7092013-06-04 14:37:27 -0700639 with DevServerRoot._staging_thread_count_lock:
640 DevServerRoot._staging_thread_count += 1
641 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800642 boolean_string = kwargs.get('clean')
643 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
644 if clean and os.path.exists(dl.GetBuildDir()):
645 _Log('Removing %s' % dl.GetBuildDir())
646 shutil.rmtree(dl.GetBuildDir())
Keith Haddow58f36d12020-10-28 16:16:39 +0000647 dl.Download(factory)
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:
Eliot Courtneyf39420b2020-10-27 18:34:04 +0900728 cros_build_lib.ExtractTarball(dep_path, telemetry_path)
729 except cros_build_lib.TarballError as e:
Simran Basi4baad082013-02-14 13:39:18 -0800730 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()