blob: 7fddaf3ecd4f181e22f25029c95d5250b45f25d4 [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 Hassani2aa34282020-11-18 01:18:19 +00007"""Chromium OS development server that can be used for all forms of update.
Chris Sosa3ae4dc12013-03-29 11:47:00 -07008
Amin Hassani2aa34282020-11-18 01:18:19 +00009This devserver can be used to perform system-wide autoupdate and update
10of specific portage packages on devices running Chromium OS derived operating
11systems.
12
13The devserver is configured to stage and
14serve artifacts from Google Storage using the credentials provided to it before
15it is run. The easiest way to understand this is that the devserver is
16functioning as a local cache for artifacts produced and uploaded by build
17servers. Users of this form of devserver can either download the artifacts
18from the devservers static directory OR use the update RPC to perform a
19system-wide autoupdate. Archive mode is always active.
20
21For autoupdates, there are many more advanced options that can help specify
22how to update and which payload to give to a requester.
Chris Sosa3ae4dc12013-03-29 11:47:00 -070023"""
24
Gabe Black3b567202015-09-23 14:07:59 -070025from __future__ import print_function
Chris Sosa7c931362010-10-11 19:49:01 -070026
Gilad Arnold55a2a372012-10-02 09:46:32 -070027import json
David Riley2fcb0122017-11-02 11:25:39 -070028import optparse # pylint: disable=deprecated-module
rtc@google.comded22402009-10-26 22:36:21 +000029import os
Scott Zawalski4647ce62012-01-03 17:17:28 -050030import re
Simran Basi4baad082013-02-14 13:39:18 -080031import shutil
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -080032import socket
Chris Masone816e38c2012-05-02 12:22:36 -070033import subprocess
J. Richard Barnette3d977b82013-04-23 11:05:19 -070034import sys
Chris Masone816e38c2012-05-02 12:22:36 -070035import tempfile
Dan Shi59ae7092013-06-04 14:37:27 -070036import threading
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070037import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070038from logging import handlers
39
Amin Hassanid4e35392019-10-03 11:02:44 -070040from six.moves import http_client
41
42# pylint: disable=no-name-in-module, import-error
J. Richard Barnette3d977b82013-04-23 11:05:19 -070043import cherrypy
Chris Sosa855b8932013-08-21 13:24:55 -070044from cherrypy import _cplogging as cplogging
Amin Hassanid4e35392019-10-03 11:02:44 -070045from cherrypy.process import plugins
46# pylint: enable=no-name-in-module, import-error
rtc@google.comded22402009-10-26 22:36:21 +000047
Amin Hassani2aa34282020-11-18 01:18:19 +000048import autoupdate
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070049import cherrypy_ext
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070050import health_checker
51
Richard Barnettedf35c322017-08-18 17:02:13 -070052# This must happen before any local modules get a chance to import
53# anything from chromite. Otherwise, really bad things will happen, and
54# you will _not_ understand why.
Congbin Guo3afae6c2019-08-13 16:29:42 -070055import setup_chromite # pylint: disable=unused-import
Eliot Courtneyf39420b2020-10-27 18:34:04 +090056from chromite.lib import cros_build_lib
Amin Hassani3587fb32021-04-28 10:10:01 -070057from chromite.lib import cros_logging as logging
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070058from chromite.lib.xbuddy import android_build
59from chromite.lib.xbuddy import artifact_info
60from chromite.lib.xbuddy import build_artifact
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070061from chromite.lib.xbuddy import common_util
62from chromite.lib.xbuddy import devserver_constants
63from chromite.lib.xbuddy import downloader
64from chromite.lib.xbuddy import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070065
Amin Hassani3587fb32021-04-28 10:10:01 -070066
Gilad Arnoldc65330c2012-09-20 15:17:48 -070067# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080068def _Log(message, *args):
Amin Hassani3587fb32021-04-28 10:10:01 -070069 return logging.info(message, *args)
Frank Farzan40160872011-12-12 18:39:18 -080070
Chris Sosa417e55d2011-01-25 16:40:48 -080071CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080072
Simran Basi4baad082013-02-14 13:39:18 -080073TELEMETRY_FOLDER = 'telemetry_src'
74TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
75 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070076 'dep-chrome_test.tar.bz2',
77 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -080078
Amin Hassani2aa34282020-11-18 01:18:19 +000079# Sets up global to share between classes.
80updater = None
81
xixuan3d48bff2017-01-30 19:00:09 -080082# Log rotation parameters. These settings correspond to twice a day once
83# devserver is started, with about two weeks (28 backup files) of old logs
84# kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -070085#
xixuan3d48bff2017-01-30 19:00:09 -080086# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 11:05:19 -070087# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-30 19:00:09 -080088_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 16:29:42 -070089_LOG_ROTATION_INTERVAL = 12 # hours
90_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-12 18:39:18 -080091
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -080092# Error msg for deprecated RPC usage.
93DEPRECATED_RPC_ERROR_MSG = ('The %s RPC has been deprecated. Usage of this '
94 'RPC is discouraged. Please go to '
95 'go/devserver-deprecation for more information.')
96
xixuan52c2fba2016-05-20 17:02:48 -070097
Amin Hassanid4e35392019-10-03 11:02:44 -070098class DevServerError(Exception):
99 """Exception class used by DevServer."""
100
101
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800102class DeprecatedRPCError(DevServerError):
103 """Exception class used when an RPC is deprecated but is still being used."""
104
105 def __init__(self, rpc_name):
106 """Constructor for DeprecatedRPCError class.
107
108 :param rpc_name: (str) name of the RPC that has been deprecated.
109 """
Amin Hassani6ecda232020-03-09 19:03:23 -0700110 super(DeprecatedRPCError, self).__init__(
111 DEPRECATED_RPC_ERROR_MSG % rpc_name)
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800112 self.rpc_name = rpc_name
113
114
Amin Hassani722e0962019-11-15 15:45:31 -0800115class DevServerHTTPError(cherrypy.HTTPError):
116 """Exception class to log the HTTPResponse before routing it to cherrypy."""
117 def __init__(self, status, message):
118 """CherryPy error with logging.
119
120 Args:
121 status: HTTPResponse status.
122 message: Message associated with the response.
123 """
124 cherrypy.HTTPError.__init__(self, status, message)
125 _Log('HTTPError status: %s message: %s', status, message)
126
127
Gabe Black3b567202015-09-23 14:07:59 -0700128def _canonicalize_archive_url(archive_url):
129 """Canonicalizes archive_url strings.
130
131 Raises:
132 DevserverError: if archive_url is not set.
133 """
134 if archive_url:
135 if not archive_url.startswith('gs://'):
Amin Hassanid4e35392019-10-03 11:02:44 -0700136 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700137 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 14:07:59 -0700138
139 return archive_url.rstrip('/')
140 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700141 raise DevServerError('Must specify an archive_url in the request')
Gabe Black3b567202015-09-23 14:07:59 -0700142
143
Amin Hassani2aa34282020-11-18 01:18:19 +0000144def _canonicalize_local_path(local_path):
Gabe Black3b567202015-09-23 14:07:59 -0700145 """Canonicalizes |local_path| strings.
146
147 Raises:
148 DevserverError: if |local_path| is not set.
149 """
150 # Restrict staging of local content to only files within the static
151 # directory.
152 local_path = os.path.abspath(local_path)
Amin Hassani2aa34282020-11-18 01:18:19 +0000153 if not local_path.startswith(updater.static_dir):
Amin Hassanid4e35392019-10-03 11:02:44 -0700154 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700155 'Local path %s must be a subdirectory of the static'
Amin Hassani2aa34282020-11-18 01:18:19 +0000156 ' directory: %s' % (local_path, updater.static_dir))
Gabe Black3b567202015-09-23 14:07:59 -0700157
158 return local_path.rstrip('/')
159
160
161def _get_artifacts(kwargs):
162 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
163
164 Raises:
165 DevserverError if no artifacts would be returned.
166 """
167 artifacts = kwargs.get('artifacts')
168 files = kwargs.get('files')
169 if not artifacts and not files:
Amin Hassanid4e35392019-10-03 11:02:44 -0700170 raise DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700171
172 # Note we NEED to coerce files to a string as we get raw unicode from
173 # cherrypy and we treat files as strings elsewhere in the code.
174 return (str(artifacts).split(',') if artifacts else [],
175 str(files).split(',') if files else [])
176
177
Dan Shi61305df2015-10-26 16:52:35 -0700178def _is_android_build_request(kwargs):
179 """Check if a devserver call is for Android build, based on the arguments.
180
181 This method exams the request's arguments (os_type) to determine if the
182 request is for Android build. If os_type is set to `android`, returns True.
183 If os_type is not set or has other values, returns False.
184
185 Args:
186 kwargs: Keyword arguments for the request.
187
188 Returns:
189 True if the request is for Android build. False otherwise.
190 """
191 os_type = kwargs.get('os_type', None)
192 return os_type == 'android'
193
194
Amin Hassani2aa34282020-11-18 01:18:19 +0000195def _get_downloader(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700196 """Returns the downloader based on passed in arguments.
197
198 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700199 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700200 """
201 local_path = kwargs.get('local_path')
202 if local_path:
Amin Hassani2aa34282020-11-18 01:18:19 +0000203 local_path = _canonicalize_local_path(local_path)
Gabe Black3b567202015-09-23 14:07:59 -0700204
205 dl = None
206 if local_path:
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800207 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
Amin Hassani2aa34282020-11-18 01:18:19 +0000208 dl = downloader.LocalDownloader(updater.static_dir, local_path,
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800209 delete_source=delete_source)
Gabe Black3b567202015-09-23 14:07:59 -0700210
Dan Shi61305df2015-10-26 16:52:35 -0700211 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700212 archive_url = kwargs.get('archive_url')
213 if not archive_url and not local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700214 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700215 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700216 if archive_url and local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700217 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700218 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700219 if not dl:
220 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700221 dl = downloader.GoogleStorageDownloader(
Amin Hassani2aa34282020-11-18 01:18:19 +0000222 updater.static_dir, archive_url,
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700223 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
224 archive_url))
Gabe Black3b567202015-09-23 14:07:59 -0700225 elif not dl:
226 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 12:10:33 -0700227 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 16:52:35 -0700228 build_id = kwargs.get('build_id', None)
229 if not target or not branch or not build_id:
Amin Hassanid4e35392019-10-03 11:02:44 -0700230 raise DevServerError('target, branch, build ID must all be specified for '
231 'downloading Android build.')
Amin Hassani2aa34282020-11-18 01:18:19 +0000232 dl = downloader.AndroidBuildDownloader(updater.static_dir, branch, build_id,
Dan Shi72b16132015-10-08 12:10:33 -0700233 target)
Gabe Black3b567202015-09-23 14:07:59 -0700234
235 return dl
236
237
Amin Hassani2aa34282020-11-18 01:18:19 +0000238def _get_downloader_and_factory(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700239 """Returns the downloader and artifact factory based on passed in arguments.
240
241 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700242 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700243 """
244 artifacts, files = _get_artifacts(kwargs)
Amin Hassani2aa34282020-11-18 01:18:19 +0000245 dl = _get_downloader(kwargs)
Gabe Black3b567202015-09-23 14:07:59 -0700246
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700247 if (isinstance(dl, (downloader.GoogleStorageDownloader,
248 downloader.LocalDownloader))):
Gabe Black3b567202015-09-23 14:07:59 -0700249 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 12:10:33 -0700250 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 14:07:59 -0700251 factory_class = build_artifact.AndroidArtifactFactory
252 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700253 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700254 'Unrecognized value for downloader type: %s' % type(dl))
Gabe Black3b567202015-09-23 14:07:59 -0700255
256 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
257
258 return dl, factory
259
260
Scott Zawalski4647ce62012-01-03 17:17:28 -0500261def _LeadingWhiteSpaceCount(string):
262 """Count the amount of leading whitespace in a string.
263
264 Args:
265 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800266
Scott Zawalski4647ce62012-01-03 17:17:28 -0500267 Returns:
268 number of white space chars before characters start.
269 """
Gabe Black3b567202015-09-23 14:07:59 -0700270 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500271 if matched:
272 return len(matched.group())
273
274 return 0
275
276
277def _PrintDocStringAsHTML(func):
278 """Make a functions docstring somewhat HTML style.
279
280 Args:
281 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800282
Scott Zawalski4647ce62012-01-03 17:17:28 -0500283 Returns:
284 A string that is somewhat formated for a web browser.
285 """
286 # TODO(scottz): Make this parse Args/Returns in a prettier way.
287 # Arguments could be bolded and indented etc.
288 html_doc = []
289 for line in func.__doc__.splitlines():
290 leading_space = _LeadingWhiteSpaceCount(line)
291 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700292 line = ' ' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500293
294 html_doc.append('<BR>%s' % line)
295
296 return '\n'.join(html_doc)
297
298
Simran Basief83d6a2014-08-28 14:32:01 -0700299def _GetUpdateTimestampHandler(static_dir):
300 """Returns a handler to update directory staged.timestamp.
301
302 This handler resets the stage.timestamp whenever static content is accessed.
303
304 Args:
305 static_dir: Directory from which static content is being staged.
306
307 Returns:
Amin Hassani08e42d22019-06-03 00:31:30 -0700308 A cherrypy handler to update the timestamp of accessed content.
Simran Basief83d6a2014-08-28 14:32:01 -0700309 """
310 def UpdateTimestampHandler():
311 if not '404' in cherrypy.response.status:
312 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
313 cherrypy.request.path_info)
314 if build_match:
315 build_dir = os.path.join(static_dir, build_match.group('build'))
316 downloader.Downloader.TouchTimestampForStaged(build_dir)
317 return UpdateTimestampHandler
318
319
Chris Sosa7c931362010-10-11 19:49:01 -0700320def _GetConfig(options):
321 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800322
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800323 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800324 # Fall back to IPv4 when python is not configured with IPv6.
325 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800326 socket_host = '0.0.0.0'
327
Simran Basief83d6a2014-08-28 14:32:01 -0700328 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
329 # on the on_end_resource hook. This hook is called once processing is
330 # complete and the response is ready to be returned.
331 cherrypy.tools.update_timestamp = cherrypy.Tool(
332 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
333
David Riley2fcb0122017-11-02 11:25:39 -0700334 base_config = {
335 'global': {
336 'server.log_request_headers': True,
337 'server.protocol_version': 'HTTP/1.1',
338 'server.socket_host': socket_host,
339 'server.socket_port': int(options.port),
340 'response.timeout': 6000,
341 'request.show_tracebacks': True,
342 'server.socket_timeout': 60,
343 'server.thread_pool': 2,
344 'engine.autoreload.on': False,
345 },
David Riley2fcb0122017-11-02 11:25:39 -0700346 '/build': {
347 'response.timeout': 100000,
348 },
Amin Hassani2aa34282020-11-18 01:18:19 +0000349 '/update': {
350 # Gets rid of cherrypy parsing post file for args.
351 'request.process_request_body': False,
352 'response.timeout': 10000,
353 },
David Riley2fcb0122017-11-02 11:25:39 -0700354 # Sets up the static dir for file hosting.
355 '/static': {
356 'tools.staticdir.dir': options.static_dir,
357 'tools.staticdir.on': True,
358 'response.timeout': 10000,
359 'tools.update_timestamp.on': True,
360 },
361 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700362 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700363 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500364
Chris Sosa7c931362010-10-11 19:49:01 -0700365 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000366
Darin Petkove17164a2010-08-11 13:24:41 -0700367
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700368def _GetRecursiveMemberObject(root, member_list):
369 """Returns an object corresponding to a nested member list.
370
371 Args:
372 root: the root object to search
373 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800374
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700375 Returns:
376 An object corresponding to the member name list; None otherwise.
377 """
378 for member in member_list:
379 next_root = root.__class__.__dict__.get(member)
380 if not next_root:
381 return None
382 root = next_root
383 return root
384
385
386def _IsExposed(name):
387 """Returns True iff |name| has an `exposed' attribute and it is set."""
388 return hasattr(name, 'exposed') and name.exposed
389
390
Congbin Guo6bc32182019-08-20 17:54:30 -0700391def _GetExposedMethod(nested_member):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700392 """Returns a CherryPy-exposed method, if such exists.
393
394 Args:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700395 nested_member: a slash-joined path to the nested member
Don Garrettf84631a2014-01-07 18:21:26 -0800396
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700397 Returns:
Congbin Guo6bc32182019-08-20 17:54:30 -0700398 A function object corresponding to the path defined by |nested_member| from
399 the app root object registered, if the function is exposed; None otherwise.
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700400 """
Congbin Guo6bc32182019-08-20 17:54:30 -0700401 for app in cherrypy.tree.apps.values():
402 # Use the 'index' function doc as the doc of the app.
403 if nested_member == app.script_name.lstrip('/'):
404 nested_member = 'index'
405
406 method = _GetRecursiveMemberObject(app.root, nested_member.split('/'))
407 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
408 return method
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700409
410
Gilad Arnold748c8322012-10-12 09:51:35 -0700411def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700412 """Finds exposed CherryPy methods.
413
414 Args:
415 root: the root object for searching
416 prefix: slash-joined chain of members leading to current object
417 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800418
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700419 Returns:
420 List of exposed URLs that are not unlisted.
421 """
422 method_list = []
Congbin Guo6bc32182019-08-20 17:54:30 -0700423 for member in root.__class__.__dict__.keys():
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700424 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700425 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700426 continue
427 member_obj = root.__class__.__dict__[member]
428 if _IsExposed(member_obj):
Amin Hassani08e42d22019-06-03 00:31:30 -0700429 if isinstance(member_obj, types.FunctionType):
Congbin Guo6bc32182019-08-20 17:54:30 -0700430 # Regard the app name as exposed "method" name if it exposed 'index'
431 # function.
432 if prefix and member == 'index':
433 method_list.append(prefix)
434 else:
435 method_list.append(prefixed_member)
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700436 else:
437 method_list += _FindExposedMethods(
438 member_obj, prefixed_member, unlisted)
439 return method_list
440
441
xixuan52c2fba2016-05-20 17:02:48 -0700442def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-11-30 16:48:20 -0800443 """Parse boolean arg from kwargs.
444
445 Args:
446 kwargs: the parameters to be checked.
447 key: the key to be parsed.
448
449 Returns:
450 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
451
452 Raises:
453 DevServerHTTPError if kwargs[key] is not a boolean variable.
454 """
xixuan52c2fba2016-05-20 17:02:48 -0700455 if key in kwargs:
456 if kwargs[key] == 'True':
457 return True
458 elif kwargs[key] == 'False':
459 return False
460 else:
Amin Hassani722e0962019-11-15 15:45:31 -0800461 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
462 'The value for key %s is not boolean.' % key)
xixuan52c2fba2016-05-20 17:02:48 -0700463 else:
464 return False
465
xixuan447ad9d2017-02-28 14:46:20 -0800466
xixuanac89ce82016-11-30 16:48:20 -0800467def _parse_string_arg(kwargs, key):
468 """Parse string arg from kwargs.
469
470 Args:
471 kwargs: the parameters to be checked.
472 key: the key to be parsed.
473
474 Returns:
475 The string value of kwargs[key], or None if key doesn't exist in kwargs.
476 """
477 if key in kwargs:
478 return kwargs[key]
479 else:
480 return None
481
xixuan447ad9d2017-02-28 14:46:20 -0800482
xixuanac89ce82016-11-30 16:48:20 -0800483def _build_uri_from_build_name(build_name):
484 """Get build url from a given build name.
485
486 Args:
487 build_name: the build name to be parsed, whose format is
488 'board/release_version'.
489
490 Returns:
491 The release_archive_url on Google Storage for this build name.
492 """
Amin Hassani08e42d22019-06-03 00:31:30 -0700493 # TODO(ahassani): This function doesn't seem to be used anywhere since its
494 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
495 # causing any runtime issues. So deprecate this in the future.
496 tokens = build_name.split('/')
497 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-20 17:02:48 -0700498
xixuan447ad9d2017-02-28 14:46:20 -0800499
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800500def is_deprecated_server():
501 """Gets whether the devserver has deprecated RPCs."""
502 return cherrypy.config.get('infra_removal', False)
503
504
David Rochberg7c79a812011-01-19 14:24:45 -0500505class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700506 """The Root Class for the Dev Server.
507
508 CherryPy works as follows:
509 For each method in this class, cherrpy interprets root/path
510 as a call to an instance of DevServerRoot->method_name. For example,
511 a call to http://myhost/build will call build. CherryPy automatically
512 parses http args and places them as keyword arguments in each method.
513 For paths http://myhost/update/dir1/dir2, you can use *args so that
514 cherrypy uses the update method and puts the extra paths in args.
515 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700516 # Method names that should not be listed on the index page.
517 _UNLISTED_METHODS = ['index', 'doc']
518
Dan Shi59ae7092013-06-04 14:37:27 -0700519 # Number of threads that devserver is staging images.
520 _staging_thread_count = 0
521 # Lock used to lock increasing/decreasing count.
522 _staging_thread_count_lock = threading.Lock()
523
Amin Hassani2aa34282020-11-18 01:18:19 +0000524 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700525 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800526 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700527 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500528
Congbin Guo3afae6c2019-08-13 16:29:42 -0700529 @property
530 def staging_thread_count(self):
531 """Get the staging thread count."""
532 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700533
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700534 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500535 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700536 """Builds the package specified."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800537 if is_deprecated_server():
538 raise DeprecatedRPCError('build')
539
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700540 import builder
541 if self._builder is None:
542 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500543 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700544
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700545 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700546 def is_staged(self, **kwargs):
547 """Check if artifacts have been downloaded.
548
Congbin Guo3afae6c2019-08-13 16:29:42 -0700549 Examples:
550 To check if autotest and test_suites are staged:
551 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
552 artifacts=autotest,test_suites
553
Amin Hassani08e42d22019-06-03 00:31:30 -0700554 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700555 async: True to return without waiting for download to complete.
556 artifacts: Comma separated list of named artifacts to download.
557 These are defined in artifact_info and have their implementation
558 in build_artifact.py.
559 files: Comma separated list of file artifacts to stage. These
560 will be available as is in the corresponding static directory with no
561 custom post-processing.
562
Congbin Guo3afae6c2019-08-13 16:29:42 -0700563 Returns:
564 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700565 """
Amin Hassani2aa34282020-11-18 01:18:19 +0000566 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700567 response = str(dl.IsStaged(factory))
568 _Log('Responding to is_staged %s request with %r', kwargs, response)
569 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700570
Chris Sosa76e44b92013-01-31 12:11:38 -0800571 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800572 def list_image_dir(self, **kwargs):
573 """Take an archive url and list the contents in its staged directory.
574
Amin Hassani08e42d22019-06-03 00:31:30 -0700575 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800576 To list the contents of where this devserver should have staged
577 gs://image-archive/<board>-release/<build> call:
578 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
579
Congbin Guo3afae6c2019-08-13 16:29:42 -0700580 Args:
581 archive_url: Google Storage URL for the build.
582
Prashanth Ba06d2d22014-03-07 15:35:19 -0800583 Returns:
584 A string with information about the contents of the image directory.
585 """
Amin Hassani2aa34282020-11-18 01:18:19 +0000586 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800587 try:
Gabe Black3b567202015-09-23 14:07:59 -0700588 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800589 except build_artifact.ArtifactDownloadError as e:
590 return 'Cannot list the contents of staged artifacts. %s' % e
591 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700592 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800593 return image_dir_contents
594
595 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800596 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700597 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800598
Gabe Black3b567202015-09-23 14:07:59 -0700599 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700600 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700601 on the devserver. A call to this will attempt to cache non-specified
602 artifacts in the background for the given from the given URL following
603 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800604 artifacts is explicitly defined in the build_artifact module.
605
606 These artifacts will then be available from the static/ sub-directory of
607 the devserver.
608
Amin Hassani08e42d22019-06-03 00:31:30 -0700609 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800610 To download the autotest and test suites tarballs:
611 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
612 artifacts=autotest,test_suites
613 To download the full update payload:
614 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
615 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700616 To download just a file called blah.bin:
617 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
618 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800619
620 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700621 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800622
623 Note for this example, relative path is the archive_url stripped of its
624 basename i.e. path/ in the examples above. Specific example:
625
626 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
627
628 Will get staged to:
629
joychened64b222013-06-21 16:39:34 -0700630 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700631
632 Args:
633 archive_url: Google Storage URL for the build.
634 local_path: Local path for the build.
635 delete_source: Only meaningful with local_path. bool to indicate if the
636 source files should be deleted. This is especially useful when staging
637 a file locally in resource constrained environments as it allows us to
638 move the relevant files locally instead of copying them.
639 async: True to return without waiting for download to complete.
640 artifacts: Comma separated list of named artifacts to download.
641 These are defined in artifact_info and have their implementation
642 in build_artifact.py.
643 files: Comma separated list of files to stage. These
644 will be available as is in the corresponding static directory with no
645 custom post-processing.
646 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800647 """
Amin Hassani2aa34282020-11-18 01:18:19 +0000648 dl, factory = _get_downloader_and_factory(kwargs)
Gabe Black3b567202015-09-23 14:07:59 -0700649
Dan Shi59ae7092013-06-04 14:37:27 -0700650 with DevServerRoot._staging_thread_count_lock:
651 DevServerRoot._staging_thread_count += 1
652 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800653 boolean_string = kwargs.get('clean')
654 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
655 if clean and os.path.exists(dl.GetBuildDir()):
656 _Log('Removing %s' % dl.GetBuildDir())
657 shutil.rmtree(dl.GetBuildDir())
Keith Haddow58f36d12020-10-28 16:16:39 +0000658 dl.Download(factory)
Dan Shi59ae7092013-06-04 14:37:27 -0700659 finally:
660 with DevServerRoot._staging_thread_count_lock:
661 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800662 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700663
664 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -0800665 def locate_file(self, **kwargs):
666 """Get the path to the given file name.
667
668 This method looks up the given file name inside specified build artifacts.
669 One use case is to help caller to locate an apk file inside a build
670 artifact. The location of the apk file could be different based on the
671 branch and target.
672
673 Args:
674 file_name: Name of the file to look for.
675 artifacts: A list of artifact names to search for the file.
676
677 Returns:
678 Path to the file with the given name. It's relative to the folder for the
679 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -0800680 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800681 if is_deprecated_server():
682 raise DeprecatedRPCError('locate_file')
683
Amin Hassani2aa34282020-11-18 01:18:19 +0000684 dl, _ = _get_downloader_and_factory(kwargs)
Dan Shi2f136862016-02-11 15:38:38 -0800685 try:
Joe Brennan1691f8e2017-03-15 15:53:36 -0700686 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 15:38:38 -0800687 artifacts = kwargs['artifacts']
688 except KeyError:
Amin Hassanid4e35392019-10-03 11:02:44 -0700689 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700690 '`file_name` and `artifacts` are required to search '
691 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 15:38:38 -0800692 build_path = dl.GetBuildDir()
693 for artifact in artifacts:
694 # Get the unzipped folder of the artifact. If it's not defined in
695 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
696 # directory directly.
697 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
698 artifact_path = os.path.join(build_path, folder)
699 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 15:53:36 -0700700 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 15:38:38 -0800701 return os.path.relpath(os.path.join(root, file_name), build_path)
Amin Hassanid4e35392019-10-03 11:02:44 -0700702 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700703 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 15:38:38 -0800704
705 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800706 def setup_telemetry(self, **kwargs):
707 """Extracts and sets up telemetry
708
709 This method goes through the telemetry deps packages, and stages them on
710 the devserver to be used by the drones and the telemetry tests.
711
712 Args:
713 archive_url: Google Storage URL for the build.
714
715 Returns:
716 Path to the source folder for the telemetry codebase once it is staged.
717 """
Amin Hassani2aa34282020-11-18 01:18:19 +0000718 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 13:39:18 -0800719
Gabe Black3b567202015-09-23 14:07:59 -0700720 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -0800721 deps_path = os.path.join(build_path, 'autotest/packages')
722 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
723 src_folder = os.path.join(telemetry_path, 'src')
724
725 with self._telemetry_lock_dict.lock(telemetry_path):
726 if os.path.exists(src_folder):
727 # Telemetry is already fully stage return
728 return src_folder
729
730 common_util.MkDirP(telemetry_path)
731
732 # Copy over the required deps tar balls to the telemetry directory.
733 for dep in TELEMETRY_DEPS:
734 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700735 if not os.path.exists(dep_path):
736 # This dep does not exist (could be new), do not extract it.
737 continue
Simran Basi4baad082013-02-14 13:39:18 -0800738 try:
Eliot Courtneyf39420b2020-10-27 18:34:04 +0900739 cros_build_lib.ExtractTarball(dep_path, telemetry_path)
740 except cros_build_lib.TarballError as e:
Simran Basi4baad082013-02-14 13:39:18 -0800741 shutil.rmtree(telemetry_path)
Amin Hassanid4e35392019-10-03 11:02:44 -0700742 raise DevServerError(str(e))
Simran Basi4baad082013-02-14 13:39:18 -0800743
744 # By default all the tarballs extract to test_src but some parts of
745 # the telemetry code specifically hardcoded to exist inside of 'src'.
746 test_src = os.path.join(telemetry_path, 'test_src')
747 try:
748 shutil.move(test_src, src_folder)
749 except shutil.Error:
750 # This can occur if src_folder already exists. Remove and retry move.
751 shutil.rmtree(src_folder)
Amin Hassanid4e35392019-10-03 11:02:44 -0700752 raise DevServerError(
Gabe Black3b567202015-09-23 14:07:59 -0700753 'Failure in telemetry setup for build %s. Appears that the '
754 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -0800755
756 return src_folder
757
758 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800759 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700760 """Symbolicates a minidump using pre-downloaded symbols, returns it.
761
762 Callers will need to POST to this URL with a body of MIME-type
763 "multipart/form-data".
764 The body should include a single argument, 'minidump', containing the
765 binary-formatted minidump to symbolicate.
766
Chris Masone816e38c2012-05-02 12:22:36 -0700767 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800768 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700769 minidump: The binary minidump file to symbolicate.
770 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800771 if is_deprecated_server():
772 raise DeprecatedRPCError('symbolicate_dump')
773
Chris Sosa76e44b92013-01-31 12:11:38 -0800774 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -0700775 # Try debug.tar.xz first, then debug.tgz
776 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
777 kwargs['artifacts'] = artifact
Amin Hassani2aa34282020-11-18 01:18:19 +0000778 dl = _get_downloader(kwargs)
Dan Shif08fe492016-10-04 14:39:25 -0700779
780 try:
781 if self.stage(**kwargs) == 'Success':
782 break
783 except build_artifact.ArtifactDownloadError:
784 continue
785 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700786 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700787 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -0800788
Chris Masone816e38c2012-05-02 12:22:36 -0700789 to_return = ''
790 with tempfile.NamedTemporaryFile() as local:
791 while True:
792 data = minidump.file.read(8192)
793 if not data:
794 break
795 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800796
Chris Masone816e38c2012-05-02 12:22:36 -0700797 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800798
Gabe Black3b567202015-09-23 14:07:59 -0700799 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -0800800
xixuanab744382017-04-27 10:41:27 -0700801 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 12:11:38 -0800802 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 10:41:27 -0700803 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 12:11:38 -0800804 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
805
Chris Masone816e38c2012-05-02 12:22:36 -0700806 to_return, error_text = stackwalk.communicate()
807 if stackwalk.returncode != 0:
Amin Hassanid4e35392019-10-03 11:02:44 -0700808 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700809 "Can't generate stack trace: %s (rc=%d)" % (error_text,
810 stackwalk.returncode))
Chris Masone816e38c2012-05-02 12:22:36 -0700811
812 return to_return
813
814 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800815 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -0400816 """Return a string representing the latest build for a given target.
817
818 Args:
819 target: The build target, typically a combination of the board and the
820 type of build e.g. x86-mario-release.
821 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
822 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -0800823
Scott Zawalski16954532012-03-20 15:31:36 -0400824 Returns:
825 A string representation of the latest build if one exists, i.e.
826 R19-1993.0.0-a1-b1480.
827 An empty string if no latest could be found.
828 """
Sanika Kulkarni07d47ed2020-08-06 14:56:46 -0700829 if is_deprecated_server():
830 raise DeprecatedRPCError('latestbuild')
831
Don Garrettf84631a2014-01-07 18:21:26 -0800832 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -0400833 return _PrintDocStringAsHTML(self.latestbuild)
834
Don Garrettf84631a2014-01-07 18:21:26 -0800835 if 'target' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800836 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
837 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -0700838
839 if _is_android_build_request(kwargs):
840 branch = kwargs.get('branch', None)
841 target = kwargs.get('target', None)
842 if not target or not branch:
Amin Hassanid4e35392019-10-03 11:02:44 -0700843 raise DevServerError('Both target and branch must be specified to query'
844 ' for the latest Android build.')
Dan Shi61305df2015-10-26 16:52:35 -0700845 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
846
Scott Zawalski16954532012-03-20 15:31:36 -0400847 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700848 return common_util.GetLatestBuildVersion(
Amin Hassani2aa34282020-11-18 01:18:19 +0000849 updater.static_dir, kwargs['target'],
Don Garrettf84631a2014-01-07 18:21:26 -0800850 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700851 except common_util.CommonUtilError as errmsg:
Amin Hassani722e0962019-11-15 15:45:31 -0800852 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
853 str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -0400854
855 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -0700856 def list_suite_controls(self, **kwargs):
857 """Return a list of contents of all known control files.
858
859 Example URL:
860 To List all control files' content:
861 http://dev-server/list_suite_controls?suite_name=bvt&
862 build=daisy_spring-release/R29-4279.0.0
863
864 Args:
865 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
866 suite_name: List the control files belonging to that suite.
867
868 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -0700869 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -0700870 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800871 if is_deprecated_server():
872 raise DeprecatedRPCError('list_suite_controls')
873
xixuan7efd0002016-04-14 15:34:01 -0700874 if not kwargs:
875 return _PrintDocStringAsHTML(self.controlfiles)
876
877 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800878 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
879 'Error: build= is required!')
xixuan7efd0002016-04-14 15:34:01 -0700880
881 if 'suite_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800882 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
883 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -0700884
885 control_file_list = [
886 line.rstrip() for line in common_util.GetControlFileListForSuite(
Amin Hassani2aa34282020-11-18 01:18:19 +0000887 updater.static_dir, kwargs['build'],
xixuan7efd0002016-04-14 15:34:01 -0700888 kwargs['suite_name']).splitlines()]
889
Dan Shia1cd6522016-04-18 16:07:21 -0700890 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -0700891 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -0700892 control_file_content_dict[control_path] = (common_util.GetControlFile(
Amin Hassani2aa34282020-11-18 01:18:19 +0000893 updater.static_dir, kwargs['build'], control_path))
xixuan7efd0002016-04-14 15:34:01 -0700894
Dan Shia1cd6522016-04-18 16:07:21 -0700895 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -0700896
897 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800898 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500899 """Return a control file or a list of all known control files.
900
901 Example URL:
902 To List all control files:
beepsbd337242013-07-09 22:44:06 -0700903 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
904 To List all control files for, say, the bvt suite:
905 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500906 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500907 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 -0500908
909 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500910 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500911 control_path: If you want the contents of a control file set this
912 to the path. E.g. client/site_tests/sleeptest/control
913 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -0700914 suite_name: If control_path is not specified but a suite_name is
915 specified, list the control files belonging to that suite instead of
916 all control files. The empty string for suite_name will list all control
917 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -0800918
Scott Zawalski4647ce62012-01-03 17:17:28 -0500919 Returns:
920 Contents of a control file if control_path is provided.
921 A list of control files if no control_path is provided.
922 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800923 if is_deprecated_server():
924 raise DeprecatedRPCError('controlfiles')
925
Don Garrettf84631a2014-01-07 18:21:26 -0800926 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500927 return _PrintDocStringAsHTML(self.controlfiles)
928
Don Garrettf84631a2014-01-07 18:21:26 -0800929 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800930 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
931 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500932
Don Garrettf84631a2014-01-07 18:21:26 -0800933 if 'control_path' not in kwargs:
934 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -0700935 return common_util.GetControlFileListForSuite(
Amin Hassani2aa34282020-11-18 01:18:19 +0000936 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -0700937 else:
938 return common_util.GetControlFileList(
Amin Hassani2aa34282020-11-18 01:18:19 +0000939 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500940 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700941 return common_util.GetControlFile(
Amin Hassani2aa34282020-11-18 01:18:19 +0000942 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800943
944 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -0700945 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700946 """Translates an xBuddy path to a real path to artifact if it exists.
947
948 Args:
Simran Basi99e63c02014-05-20 10:39:52 -0700949 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
950 Local searches the devserver's static directory. Remote searches a
951 Google Storage image archive.
952
953 Kwargs:
954 image_dir: Google Storage image archive to search in if requesting a
955 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700956
957 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -0700958 String in the format of build_id/artifact as stored on the local server
959 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700960 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800961 if is_deprecated_server():
962 raise DeprecatedRPCError('xbuddy_translate')
963
Simran Basi99e63c02014-05-20 10:39:52 -0700964 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -0700965 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700966 response = os.path.join(build_id, filename)
967 _Log('Path translation requested, returning: %s', response)
968 return response
969
970 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -0700971 def xbuddy(self, *args, **kwargs):
972 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -0700973
974 Args:
joycheneaf4cfc2013-07-02 08:38:57 -0700975 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -0700976 components of the path. The path can be understood as
977 "{local|remote}/build_id/artifact" where build_id is composed of
978 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -0700979
joychen121fc9b2013-08-02 14:30:30 -0700980 The first path element is optional, and can be "remote" or "local"
981 If local (the default), devserver will not attempt to access Google
982 Storage, and will only search the static directory for the files.
983 If remote, devserver will try to obtain the artifact off GS if it's
984 not found locally.
985 The board is the familiar board name, optionally suffixed.
986 The version can be the google storage version number, and may also be
987 any of a number of xBuddy defined version aliases that will be
988 translated into the latest built image that fits the description.
989 Defaults to latest.
990 The artifact is one of a number of image or artifact aliases used by
991 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -0700992
993 Kwargs:
joychen3cb228e2013-06-12 12:13:13 -0700994 return_dir: {true|false}
995 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800996 relative_path: {true|false}
997 if set to true, returns the relative path to the payload
998 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -0700999 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -07001000 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -07001001 or
joycheneaf4cfc2013-07-02 08:38:57 -07001002 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -07001003
1004 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001005 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1006 http://host:port/static/x86-generic-release/R26-4000.0.0/
1007 If |relative_path| is true, return a relative path the folder where the
1008 payloads are. E.g.,
1009 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -07001010 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001011 if is_deprecated_server():
1012 raise DeprecatedRPCError('xbuddy')
1013
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001014 boolean_string = kwargs.get('return_dir')
1015 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1016 boolean_string = kwargs.get('relative_path')
1017 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001018
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001019 if return_dir and relative_path:
Amin Hassani722e0962019-11-15 15:45:31 -08001020 raise DevServerHTTPError(
Amin Hassanid4e35392019-10-03 11:02:44 -07001021 http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001022 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001023
Amin Hassania4bc5652020-10-19 12:44:24 -07001024 build_id, file_name = self._xbuddy.Get(args)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001025
1026 response = None
1027 if return_dir:
1028 response = os.path.join(cherrypy.request.base, 'static', build_id)
1029 _Log('Directory requested, returning: %s', response)
1030 elif relative_path:
1031 response = build_id
1032 _Log('Relative path requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001033 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001034 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001035 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001036 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001037 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001038
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001039 return response
1040
joychen3cb228e2013-06-12 12:13:13 -07001041 @cherrypy.expose
joychen3cb228e2013-06-12 12:13:13 -07001042 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001043 """Returns the number of images cached by xBuddy."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001044 if is_deprecated_server():
1045 raise DeprecatedRPCError('xbuddy_capacity')
1046
joychen3cb228e2013-06-12 12:13:13 -07001047 return self._xbuddy.Capacity()
1048
1049 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001050 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001051 """Presents a welcome message and documentation links."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001052 if is_deprecated_server():
1053 raise DeprecatedRPCError('index')
1054
Congbin Guo6bc32182019-08-20 17:54:30 -07001055 html_template = (
1056 'Welcome to the Dev Server!<br>\n'
1057 '<br>\n'
1058 'Here are the available methods, click for documentation:<br>\n'
1059 '<br>\n'
1060 '%s')
1061
1062 exposed_methods = []
1063 for app in cherrypy.tree.apps.values():
1064 exposed_methods += _FindExposedMethods(
1065 app.root, app.script_name.lstrip('/'),
1066 unlisted=self._UNLISTED_METHODS)
1067
1068 return html_template % '<br>\n'.join(
1069 ['<a href=doc/%s>%s</a>' % (name, name)
1070 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001071
1072 @cherrypy.expose
1073 def doc(self, *args):
1074 """Shows the documentation for available methods / URLs.
1075
Amin Hassani08e42d22019-06-03 00:31:30 -07001076 Examples:
Amin Hassani2aa34282020-11-18 01:18:19 +00001077 http://myhost/doc/update
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001078 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001079 if is_deprecated_server():
1080 raise DeprecatedRPCError('doc')
1081
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001082 name = '/'.join(args)
Congbin Guo6bc32182019-08-20 17:54:30 -07001083 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001084 if not method:
Amin Hassanid4e35392019-10-03 11:02:44 -07001085 raise DevServerError("No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001086 if not method.__doc__:
Amin Hassanid4e35392019-10-03 11:02:44 -07001087 raise DevServerError("No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001088 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001089
Amin Hassani2aa34282020-11-18 01:18:19 +00001090 @cherrypy.expose
1091 def update(self, *args, **kwargs):
1092 """Handles an update check from a Chrome OS client.
1093
1094 The HTTP request should contain the standard Omaha-style XML blob. The URL
1095 line may contain an additional intermediate path to the update payload.
1096
1097 This request can be handled in one of 4 ways, depending on the devsever
1098 settings and intermediate path.
1099
1100 1. No intermediate path. DEPRECATED
1101
1102 2. Path explicitly invokes XBuddy
1103 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1104 with 'xbuddy'. This path is then used to acquire an image binary for the
1105 devserver to generate an update payload from. Devserver then serves this
1106 payload.
1107
1108 3. Path is left for the devserver to interpret.
1109 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1110 to generate a payload from the test image in that directory and serve it.
1111
1112 Examples:
1113 2. Explicitly invoke xbuddy
1114 update_engine_client --omaha_url=
1115 http://myhost/update/xbuddy/remote/board/version/dev
1116 This would go to GS to download the dev image for the board, from which
1117 the devserver would generate a payload to serve.
1118
1119 3. Give a path for devserver to interpret
1120 update_engine_client --omaha_url=http://myhost/update/some/random/path
1121 This would attempt, in order to:
1122 a) Generate an update from a test image binary if found in
1123 static_dir/some/random/path.
1124 b) Serve an update payload found in static_dir/some/random/path.
1125 c) Hope that some/random/path takes the form "board/version" and
1126 and attempt to download an update payload for that board/version
1127 from GS.
1128 """
1129 label = '/'.join(args)
1130 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
1131 data = cherrypy.request.rfile.read(body_length)
1132
1133 return updater.HandleUpdatePing(data, label, **kwargs)
1134
Dan Shif5ce2de2013-04-25 16:06:32 -07001135
Chris Sosadbc20082012-12-10 13:39:11 -08001136def _CleanCache(cache_dir, wipe):
1137 """Wipes any excess cached items in the cache_dir.
1138
1139 Args:
1140 cache_dir: the directory we are wiping from.
1141 wipe: If True, wipe all the contents -- not just the excess.
1142 """
1143 if wipe:
1144 # Clear the cache and exit on error.
1145 cmd = 'rm -rf %s/*' % cache_dir
1146 if os.system(cmd) != 0:
1147 _Log('Failed to clear the cache with %s' % cmd)
1148 sys.exit(1)
1149 else:
1150 # Clear all but the last N cached updates
1151 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1152 (cache_dir, CACHED_ENTRIES))
1153 if os.system(cmd) != 0:
1154 _Log('Failed to clean up old delta cache files with %s' % cmd)
1155 sys.exit(1)
1156
1157
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001158def _AddTestingOptions(parser):
1159 group = optparse.OptionGroup(
1160 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1161 'developers writing integration tests utilizing the devserver. They are '
1162 'not intended to be really used outside the scope of someone '
1163 'knowledgable about the test.')
1164 group.add_option('--exit',
1165 action='store_true',
Amin Hassanie9ffb862019-09-25 17:10:40 -07001166 help='do not start the server (yet clear cache)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001167 parser.add_option_group(group)
1168
1169
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001170def _AddProductionOptions(parser):
1171 group = optparse.OptionGroup(
1172 parser, 'Advanced Server Options', 'These options can be used to changed '
1173 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001174 group.add_option('--clear_cache',
1175 action='store_true', default=False,
1176 help='At startup, removes all cached entries from the'
Amin Hassanid4e35392019-10-03 11:02:44 -07001177 "devserver's cache.")
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001178 group.add_option('--logfile',
1179 metavar='PATH',
1180 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001181 group.add_option('--pidfile',
1182 metavar='PATH',
1183 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001184 group.add_option('--portfile',
1185 metavar='PATH',
1186 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001187 group.add_option('--production',
1188 action='store_true', default=False,
1189 help='have the devserver use production values when '
1190 'starting up. This includes using more threads and '
1191 'performing less logging.')
1192 parser.add_option_group(group)
1193
1194
Paul Hobbsef4e0702016-06-27 17:01:42 -07001195def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001196 """Create a LogHandler instance used to log all messages."""
1197 hdlr_cls = handlers.TimedRotatingFileHandler
1198 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001199 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001200 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001201 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001202 return hdlr
1203
1204
Chris Sosacde6bf42012-05-31 18:36:39 -07001205def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001206 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001207 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001208
1209 # get directory that the devserver is run from
1210 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001211 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001212 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001213 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001214 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001215 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001216 parser.add_option('--port',
1217 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001218 help=('port for the dev server to use; if zero, binds to '
1219 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001220 parser.add_option('-t', '--test_image',
1221 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001222 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001223 parser.add_option('-x', '--xbuddy_manage_builds',
1224 action='store_true',
1225 default=False,
1226 help='If set, allow xbuddy to manage images in'
1227 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001228 parser.add_option('-a', '--android_build_credential',
1229 default=None,
1230 help='Path to a json file which contains the credential '
1231 'needed to access Android builds.')
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001232 parser.add_option('--infra_removal',
1233 action='store_true', default=False,
1234 help='If option is present, some RPCs will be disabled to '
1235 'help with infra removal efforts. See '
1236 'go/devserver-deprecation')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001237 _AddProductionOptions(parser)
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001238 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001239 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001240
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001241 # Handle options that must be set globally in cherrypy. Do this
1242 # work up front, because calls to _Log() below depend on this
1243 # initialization.
1244 if options.production:
1245 cherrypy.config.update({'environment': 'production'})
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001246 cherrypy.config.update({'infra_removal': options.infra_removal})
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001247 if not options.logfile:
1248 cherrypy.config.update({'log.screen': True})
1249 else:
1250 cherrypy.config.update({'log.error_file': '',
1251 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001252 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001253 # Pylint can't seem to process these two calls properly
1254 # pylint: disable=E1101
1255 cherrypy.log.access_log.addHandler(hdlr)
1256 cherrypy.log.error_log.addHandler(hdlr)
1257 # pylint: enable=E1101
1258
joychened64b222013-06-21 16:39:34 -07001259 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001260 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001261
joychened64b222013-06-21 16:39:34 -07001262 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001263 # If our devserver is only supposed to serve payloads, we shouldn't be
1264 # mucking with the cache at all. If the devserver hadn't previously
1265 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001266 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001267 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001268 else:
1269 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001270
Amin Hassanief523622020-07-06 12:09:23 -07001271 pkgroot_dir = os.path.join(options.static_dir, 'pkgroot')
1272 common_util.SymlinkFile('/build', pkgroot_dir)
1273
Chris Sosadbc20082012-12-10 13:39:11 -08001274 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001275 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001276
Amin Hassanie9ffb862019-09-25 17:10:40 -07001277 _xbuddy = xbuddy.XBuddy(manage_builds=options.xbuddy_manage_builds,
joychen121fc9b2013-08-02 14:30:30 -07001278 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001279 if options.clear_cache and options.xbuddy_manage_builds:
1280 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001281
Amin Hassani2aa34282020-11-18 01:18:19 +00001282 # We allow global use here to share with cherrypy classes.
1283 # pylint: disable=W0603
1284 global updater
1285 updater = autoupdate.Autoupdate(_xbuddy, static_dir=options.static_dir)
1286
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001287 if options.exit:
1288 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001289
Amin Hassani2aa34282020-11-18 01:18:19 +00001290 dev_server = DevServerRoot(_xbuddy)
Congbin Guo3afae6c2019-08-13 16:29:42 -07001291 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 12:13:13 -07001292
Chris Sosa855b8932013-08-21 13:24:55 -07001293 if options.pidfile:
1294 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1295
Gilad Arnold11fbef42014-02-10 11:04:13 -08001296 if options.portfile:
1297 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1298
Dan Shiafd5c6c2016-01-07 10:27:03 -08001299 if (options.android_build_credential and
1300 os.path.exists(options.android_build_credential)):
1301 try:
1302 with open(options.android_build_credential) as f:
1303 android_build.BuildAccessor.credential_info = json.load(f)
1304 except ValueError as e:
1305 _Log('Failed to load the android build credential: %s. Error: %s.' %
1306 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 16:29:42 -07001307
1308 cherrypy.tree.mount(health_checker_app, '/check_health',
1309 config=health_checker.get_config())
joychen3cb228e2013-06-12 12:13:13 -07001310 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001311
1312
1313if __name__ == '__main__':
1314 main()