blob: 9f29604c164a746bc47406d5d0643ccb26bc8806 [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
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070057from chromite.lib.xbuddy import android_build
58from chromite.lib.xbuddy import artifact_info
59from chromite.lib.xbuddy import build_artifact
60from chromite.lib.xbuddy import cherrypy_log_util
61from 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
Gilad Arnoldc65330c2012-09-20 15:17:48 -070066# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080067def _Log(message, *args):
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070068 return cherrypy_log_util.LogWithTag('DEVSERVER', message, *args)
Frank Farzan40160872011-12-12 18:39:18 -080069
Chris Sosa417e55d2011-01-25 16:40:48 -080070CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080071
Simran Basi4baad082013-02-14 13:39:18 -080072TELEMETRY_FOLDER = 'telemetry_src'
73TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
74 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070075 'dep-chrome_test.tar.bz2',
76 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -080077
Amin Hassani2aa34282020-11-18 01:18:19 +000078# Sets up global to share between classes.
79updater = None
80
xixuan3d48bff2017-01-30 19:00:09 -080081# Log rotation parameters. These settings correspond to twice a day once
82# devserver is started, with about two weeks (28 backup files) of old logs
83# kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -070084#
xixuan3d48bff2017-01-30 19:00:09 -080085# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 11:05:19 -070086# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-30 19:00:09 -080087_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 16:29:42 -070088_LOG_ROTATION_INTERVAL = 12 # hours
89_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-12 18:39:18 -080090
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -080091# Error msg for deprecated RPC usage.
92DEPRECATED_RPC_ERROR_MSG = ('The %s RPC has been deprecated. Usage of this '
93 'RPC is discouraged. Please go to '
94 'go/devserver-deprecation for more information.')
95
xixuan52c2fba2016-05-20 17:02:48 -070096
Amin Hassanid4e35392019-10-03 11:02:44 -070097class DevServerError(Exception):
98 """Exception class used by DevServer."""
99
100
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800101class DeprecatedRPCError(DevServerError):
102 """Exception class used when an RPC is deprecated but is still being used."""
103
104 def __init__(self, rpc_name):
105 """Constructor for DeprecatedRPCError class.
106
107 :param rpc_name: (str) name of the RPC that has been deprecated.
108 """
Amin Hassani6ecda232020-03-09 19:03:23 -0700109 super(DeprecatedRPCError, self).__init__(
110 DEPRECATED_RPC_ERROR_MSG % rpc_name)
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800111 self.rpc_name = rpc_name
112
113
Amin Hassani722e0962019-11-15 15:45:31 -0800114class DevServerHTTPError(cherrypy.HTTPError):
115 """Exception class to log the HTTPResponse before routing it to cherrypy."""
116 def __init__(self, status, message):
117 """CherryPy error with logging.
118
119 Args:
120 status: HTTPResponse status.
121 message: Message associated with the response.
122 """
123 cherrypy.HTTPError.__init__(self, status, message)
124 _Log('HTTPError status: %s message: %s', status, message)
125
126
Gabe Black3b567202015-09-23 14:07:59 -0700127def _canonicalize_archive_url(archive_url):
128 """Canonicalizes archive_url strings.
129
130 Raises:
131 DevserverError: if archive_url is not set.
132 """
133 if archive_url:
134 if not archive_url.startswith('gs://'):
Amin Hassanid4e35392019-10-03 11:02:44 -0700135 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700136 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 14:07:59 -0700137
138 return archive_url.rstrip('/')
139 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700140 raise DevServerError('Must specify an archive_url in the request')
Gabe Black3b567202015-09-23 14:07:59 -0700141
142
Amin Hassani2aa34282020-11-18 01:18:19 +0000143def _canonicalize_local_path(local_path):
Gabe Black3b567202015-09-23 14:07:59 -0700144 """Canonicalizes |local_path| strings.
145
146 Raises:
147 DevserverError: if |local_path| is not set.
148 """
149 # Restrict staging of local content to only files within the static
150 # directory.
151 local_path = os.path.abspath(local_path)
Amin Hassani2aa34282020-11-18 01:18:19 +0000152 if not local_path.startswith(updater.static_dir):
Amin Hassanid4e35392019-10-03 11:02:44 -0700153 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700154 'Local path %s must be a subdirectory of the static'
Amin Hassani2aa34282020-11-18 01:18:19 +0000155 ' directory: %s' % (local_path, updater.static_dir))
Gabe Black3b567202015-09-23 14:07:59 -0700156
157 return local_path.rstrip('/')
158
159
160def _get_artifacts(kwargs):
161 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
162
163 Raises:
164 DevserverError if no artifacts would be returned.
165 """
166 artifacts = kwargs.get('artifacts')
167 files = kwargs.get('files')
168 if not artifacts and not files:
Amin Hassanid4e35392019-10-03 11:02:44 -0700169 raise DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700170
171 # Note we NEED to coerce files to a string as we get raw unicode from
172 # cherrypy and we treat files as strings elsewhere in the code.
173 return (str(artifacts).split(',') if artifacts else [],
174 str(files).split(',') if files else [])
175
176
Dan Shi61305df2015-10-26 16:52:35 -0700177def _is_android_build_request(kwargs):
178 """Check if a devserver call is for Android build, based on the arguments.
179
180 This method exams the request's arguments (os_type) to determine if the
181 request is for Android build. If os_type is set to `android`, returns True.
182 If os_type is not set or has other values, returns False.
183
184 Args:
185 kwargs: Keyword arguments for the request.
186
187 Returns:
188 True if the request is for Android build. False otherwise.
189 """
190 os_type = kwargs.get('os_type', None)
191 return os_type == 'android'
192
193
Amin Hassani2aa34282020-11-18 01:18:19 +0000194def _get_downloader(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700195 """Returns the downloader based on passed in arguments.
196
197 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700198 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700199 """
200 local_path = kwargs.get('local_path')
201 if local_path:
Amin Hassani2aa34282020-11-18 01:18:19 +0000202 local_path = _canonicalize_local_path(local_path)
Gabe Black3b567202015-09-23 14:07:59 -0700203
204 dl = None
205 if local_path:
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800206 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
Amin Hassani2aa34282020-11-18 01:18:19 +0000207 dl = downloader.LocalDownloader(updater.static_dir, local_path,
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800208 delete_source=delete_source)
Gabe Black3b567202015-09-23 14:07:59 -0700209
Dan Shi61305df2015-10-26 16:52:35 -0700210 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700211 archive_url = kwargs.get('archive_url')
212 if not archive_url and not local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700213 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700214 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700215 if archive_url and local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700216 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700217 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700218 if not dl:
219 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700220 dl = downloader.GoogleStorageDownloader(
Amin Hassani2aa34282020-11-18 01:18:19 +0000221 updater.static_dir, archive_url,
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700222 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
223 archive_url))
Gabe Black3b567202015-09-23 14:07:59 -0700224 elif not dl:
225 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 12:10:33 -0700226 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 16:52:35 -0700227 build_id = kwargs.get('build_id', None)
228 if not target or not branch or not build_id:
Amin Hassanid4e35392019-10-03 11:02:44 -0700229 raise DevServerError('target, branch, build ID must all be specified for '
230 'downloading Android build.')
Amin Hassani2aa34282020-11-18 01:18:19 +0000231 dl = downloader.AndroidBuildDownloader(updater.static_dir, branch, build_id,
Dan Shi72b16132015-10-08 12:10:33 -0700232 target)
Gabe Black3b567202015-09-23 14:07:59 -0700233
234 return dl
235
236
Amin Hassani2aa34282020-11-18 01:18:19 +0000237def _get_downloader_and_factory(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700238 """Returns the downloader and artifact factory based on passed in arguments.
239
240 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700241 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700242 """
243 artifacts, files = _get_artifacts(kwargs)
Amin Hassani2aa34282020-11-18 01:18:19 +0000244 dl = _get_downloader(kwargs)
Gabe Black3b567202015-09-23 14:07:59 -0700245
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700246 if (isinstance(dl, (downloader.GoogleStorageDownloader,
247 downloader.LocalDownloader))):
Gabe Black3b567202015-09-23 14:07:59 -0700248 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 12:10:33 -0700249 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 14:07:59 -0700250 factory_class = build_artifact.AndroidArtifactFactory
251 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700252 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700253 'Unrecognized value for downloader type: %s' % type(dl))
Gabe Black3b567202015-09-23 14:07:59 -0700254
255 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
256
257 return dl, factory
258
259
Scott Zawalski4647ce62012-01-03 17:17:28 -0500260def _LeadingWhiteSpaceCount(string):
261 """Count the amount of leading whitespace in a string.
262
263 Args:
264 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800265
Scott Zawalski4647ce62012-01-03 17:17:28 -0500266 Returns:
267 number of white space chars before characters start.
268 """
Gabe Black3b567202015-09-23 14:07:59 -0700269 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500270 if matched:
271 return len(matched.group())
272
273 return 0
274
275
276def _PrintDocStringAsHTML(func):
277 """Make a functions docstring somewhat HTML style.
278
279 Args:
280 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800281
Scott Zawalski4647ce62012-01-03 17:17:28 -0500282 Returns:
283 A string that is somewhat formated for a web browser.
284 """
285 # TODO(scottz): Make this parse Args/Returns in a prettier way.
286 # Arguments could be bolded and indented etc.
287 html_doc = []
288 for line in func.__doc__.splitlines():
289 leading_space = _LeadingWhiteSpaceCount(line)
290 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700291 line = ' ' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500292
293 html_doc.append('<BR>%s' % line)
294
295 return '\n'.join(html_doc)
296
297
Simran Basief83d6a2014-08-28 14:32:01 -0700298def _GetUpdateTimestampHandler(static_dir):
299 """Returns a handler to update directory staged.timestamp.
300
301 This handler resets the stage.timestamp whenever static content is accessed.
302
303 Args:
304 static_dir: Directory from which static content is being staged.
305
306 Returns:
Amin Hassani08e42d22019-06-03 00:31:30 -0700307 A cherrypy handler to update the timestamp of accessed content.
Simran Basief83d6a2014-08-28 14:32:01 -0700308 """
309 def UpdateTimestampHandler():
310 if not '404' in cherrypy.response.status:
311 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
312 cherrypy.request.path_info)
313 if build_match:
314 build_dir = os.path.join(static_dir, build_match.group('build'))
315 downloader.Downloader.TouchTimestampForStaged(build_dir)
316 return UpdateTimestampHandler
317
318
Chris Sosa7c931362010-10-11 19:49:01 -0700319def _GetConfig(options):
320 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800321
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800322 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800323 # Fall back to IPv4 when python is not configured with IPv6.
324 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800325 socket_host = '0.0.0.0'
326
Simran Basief83d6a2014-08-28 14:32:01 -0700327 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
328 # on the on_end_resource hook. This hook is called once processing is
329 # complete and the response is ready to be returned.
330 cherrypy.tools.update_timestamp = cherrypy.Tool(
331 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
332
David Riley2fcb0122017-11-02 11:25:39 -0700333 base_config = {
334 'global': {
335 'server.log_request_headers': True,
336 'server.protocol_version': 'HTTP/1.1',
337 'server.socket_host': socket_host,
338 'server.socket_port': int(options.port),
339 'response.timeout': 6000,
340 'request.show_tracebacks': True,
341 'server.socket_timeout': 60,
342 'server.thread_pool': 2,
343 'engine.autoreload.on': False,
344 },
David Riley2fcb0122017-11-02 11:25:39 -0700345 '/build': {
346 'response.timeout': 100000,
347 },
Amin Hassani2aa34282020-11-18 01:18:19 +0000348 '/update': {
349 # Gets rid of cherrypy parsing post file for args.
350 'request.process_request_body': False,
351 'response.timeout': 10000,
352 },
David Riley2fcb0122017-11-02 11:25:39 -0700353 # Sets up the static dir for file hosting.
354 '/static': {
355 'tools.staticdir.dir': options.static_dir,
356 'tools.staticdir.on': True,
357 'response.timeout': 10000,
358 'tools.update_timestamp.on': True,
359 },
360 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700361 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700362 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500363
Chris Sosa7c931362010-10-11 19:49:01 -0700364 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000365
Darin Petkove17164a2010-08-11 13:24:41 -0700366
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700367def _GetRecursiveMemberObject(root, member_list):
368 """Returns an object corresponding to a nested member list.
369
370 Args:
371 root: the root object to search
372 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800373
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700374 Returns:
375 An object corresponding to the member name list; None otherwise.
376 """
377 for member in member_list:
378 next_root = root.__class__.__dict__.get(member)
379 if not next_root:
380 return None
381 root = next_root
382 return root
383
384
385def _IsExposed(name):
386 """Returns True iff |name| has an `exposed' attribute and it is set."""
387 return hasattr(name, 'exposed') and name.exposed
388
389
Congbin Guo6bc32182019-08-20 17:54:30 -0700390def _GetExposedMethod(nested_member):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700391 """Returns a CherryPy-exposed method, if such exists.
392
393 Args:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700394 nested_member: a slash-joined path to the nested member
Don Garrettf84631a2014-01-07 18:21:26 -0800395
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700396 Returns:
Congbin Guo6bc32182019-08-20 17:54:30 -0700397 A function object corresponding to the path defined by |nested_member| from
398 the app root object registered, if the function is exposed; None otherwise.
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700399 """
Congbin Guo6bc32182019-08-20 17:54:30 -0700400 for app in cherrypy.tree.apps.values():
401 # Use the 'index' function doc as the doc of the app.
402 if nested_member == app.script_name.lstrip('/'):
403 nested_member = 'index'
404
405 method = _GetRecursiveMemberObject(app.root, nested_member.split('/'))
406 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
407 return method
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700408
409
Gilad Arnold748c8322012-10-12 09:51:35 -0700410def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700411 """Finds exposed CherryPy methods.
412
413 Args:
414 root: the root object for searching
415 prefix: slash-joined chain of members leading to current object
416 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800417
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700418 Returns:
419 List of exposed URLs that are not unlisted.
420 """
421 method_list = []
Congbin Guo6bc32182019-08-20 17:54:30 -0700422 for member in root.__class__.__dict__.keys():
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700423 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700424 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700425 continue
426 member_obj = root.__class__.__dict__[member]
427 if _IsExposed(member_obj):
Amin Hassani08e42d22019-06-03 00:31:30 -0700428 if isinstance(member_obj, types.FunctionType):
Congbin Guo6bc32182019-08-20 17:54:30 -0700429 # Regard the app name as exposed "method" name if it exposed 'index'
430 # function.
431 if prefix and member == 'index':
432 method_list.append(prefix)
433 else:
434 method_list.append(prefixed_member)
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700435 else:
436 method_list += _FindExposedMethods(
437 member_obj, prefixed_member, unlisted)
438 return method_list
439
440
xixuan52c2fba2016-05-20 17:02:48 -0700441def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-11-30 16:48:20 -0800442 """Parse boolean arg from kwargs.
443
444 Args:
445 kwargs: the parameters to be checked.
446 key: the key to be parsed.
447
448 Returns:
449 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
450
451 Raises:
452 DevServerHTTPError if kwargs[key] is not a boolean variable.
453 """
xixuan52c2fba2016-05-20 17:02:48 -0700454 if key in kwargs:
455 if kwargs[key] == 'True':
456 return True
457 elif kwargs[key] == 'False':
458 return False
459 else:
Amin Hassani722e0962019-11-15 15:45:31 -0800460 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
461 'The value for key %s is not boolean.' % key)
xixuan52c2fba2016-05-20 17:02:48 -0700462 else:
463 return False
464
xixuan447ad9d2017-02-28 14:46:20 -0800465
xixuanac89ce82016-11-30 16:48:20 -0800466def _parse_string_arg(kwargs, key):
467 """Parse string arg from kwargs.
468
469 Args:
470 kwargs: the parameters to be checked.
471 key: the key to be parsed.
472
473 Returns:
474 The string value of kwargs[key], or None if key doesn't exist in kwargs.
475 """
476 if key in kwargs:
477 return kwargs[key]
478 else:
479 return None
480
xixuan447ad9d2017-02-28 14:46:20 -0800481
xixuanac89ce82016-11-30 16:48:20 -0800482def _build_uri_from_build_name(build_name):
483 """Get build url from a given build name.
484
485 Args:
486 build_name: the build name to be parsed, whose format is
487 'board/release_version'.
488
489 Returns:
490 The release_archive_url on Google Storage for this build name.
491 """
Amin Hassani08e42d22019-06-03 00:31:30 -0700492 # TODO(ahassani): This function doesn't seem to be used anywhere since its
493 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
494 # causing any runtime issues. So deprecate this in the future.
495 tokens = build_name.split('/')
496 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-20 17:02:48 -0700497
xixuan447ad9d2017-02-28 14:46:20 -0800498
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800499def is_deprecated_server():
500 """Gets whether the devserver has deprecated RPCs."""
501 return cherrypy.config.get('infra_removal', False)
502
503
David Rochberg7c79a812011-01-19 14:24:45 -0500504class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700505 """The Root Class for the Dev Server.
506
507 CherryPy works as follows:
508 For each method in this class, cherrpy interprets root/path
509 as a call to an instance of DevServerRoot->method_name. For example,
510 a call to http://myhost/build will call build. CherryPy automatically
511 parses http args and places them as keyword arguments in each method.
512 For paths http://myhost/update/dir1/dir2, you can use *args so that
513 cherrypy uses the update method and puts the extra paths in args.
514 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700515 # Method names that should not be listed on the index page.
516 _UNLISTED_METHODS = ['index', 'doc']
517
Dan Shi59ae7092013-06-04 14:37:27 -0700518 # Number of threads that devserver is staging images.
519 _staging_thread_count = 0
520 # Lock used to lock increasing/decreasing count.
521 _staging_thread_count_lock = threading.Lock()
522
Amin Hassani2aa34282020-11-18 01:18:19 +0000523 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700524 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800525 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700526 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500527
Congbin Guo3afae6c2019-08-13 16:29:42 -0700528 @property
529 def staging_thread_count(self):
530 """Get the staging thread count."""
531 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700532
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700533 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500534 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700535 """Builds the package specified."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800536 if is_deprecated_server():
537 raise DeprecatedRPCError('build')
538
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700539 import builder
540 if self._builder is None:
541 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500542 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700543
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700544 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700545 def is_staged(self, **kwargs):
546 """Check if artifacts have been downloaded.
547
Congbin Guo3afae6c2019-08-13 16:29:42 -0700548 Examples:
549 To check if autotest and test_suites are staged:
550 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
551 artifacts=autotest,test_suites
552
Amin Hassani08e42d22019-06-03 00:31:30 -0700553 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700554 async: True to return without waiting for download to complete.
555 artifacts: Comma separated list of named artifacts to download.
556 These are defined in artifact_info and have their implementation
557 in build_artifact.py.
558 files: Comma separated list of file artifacts to stage. These
559 will be available as is in the corresponding static directory with no
560 custom post-processing.
561
Congbin Guo3afae6c2019-08-13 16:29:42 -0700562 Returns:
563 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700564 """
Amin Hassani2aa34282020-11-18 01:18:19 +0000565 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700566 response = str(dl.IsStaged(factory))
567 _Log('Responding to is_staged %s request with %r', kwargs, response)
568 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700569
Chris Sosa76e44b92013-01-31 12:11:38 -0800570 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800571 def list_image_dir(self, **kwargs):
572 """Take an archive url and list the contents in its staged directory.
573
Amin Hassani08e42d22019-06-03 00:31:30 -0700574 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800575 To list the contents of where this devserver should have staged
576 gs://image-archive/<board>-release/<build> call:
577 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
578
Congbin Guo3afae6c2019-08-13 16:29:42 -0700579 Args:
580 archive_url: Google Storage URL for the build.
581
Prashanth Ba06d2d22014-03-07 15:35:19 -0800582 Returns:
583 A string with information about the contents of the image directory.
584 """
Amin Hassani2aa34282020-11-18 01:18:19 +0000585 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800586 try:
Gabe Black3b567202015-09-23 14:07:59 -0700587 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800588 except build_artifact.ArtifactDownloadError as e:
589 return 'Cannot list the contents of staged artifacts. %s' % e
590 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700591 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800592 return image_dir_contents
593
594 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800595 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700596 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800597
Gabe Black3b567202015-09-23 14:07:59 -0700598 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700599 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700600 on the devserver. A call to this will attempt to cache non-specified
601 artifacts in the background for the given from the given URL following
602 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800603 artifacts is explicitly defined in the build_artifact module.
604
605 These artifacts will then be available from the static/ sub-directory of
606 the devserver.
607
Amin Hassani08e42d22019-06-03 00:31:30 -0700608 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800609 To download the autotest and test suites tarballs:
610 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
611 artifacts=autotest,test_suites
612 To download the full update payload:
613 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
614 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700615 To download just a file called blah.bin:
616 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
617 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800618
619 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700620 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800621
622 Note for this example, relative path is the archive_url stripped of its
623 basename i.e. path/ in the examples above. Specific example:
624
625 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
626
627 Will get staged to:
628
joychened64b222013-06-21 16:39:34 -0700629 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700630
631 Args:
632 archive_url: Google Storage URL for the build.
633 local_path: Local path for the build.
634 delete_source: Only meaningful with local_path. bool to indicate if the
635 source files should be deleted. This is especially useful when staging
636 a file locally in resource constrained environments as it allows us to
637 move the relevant files locally instead of copying them.
638 async: True to return without waiting for download to complete.
639 artifacts: Comma separated list of named artifacts to download.
640 These are defined in artifact_info and have their implementation
641 in build_artifact.py.
642 files: Comma separated list of files to stage. These
643 will be available as is in the corresponding static directory with no
644 custom post-processing.
645 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800646 """
Amin Hassani2aa34282020-11-18 01:18:19 +0000647 dl, factory = _get_downloader_and_factory(kwargs)
Gabe Black3b567202015-09-23 14:07:59 -0700648
Dan Shi59ae7092013-06-04 14:37:27 -0700649 with DevServerRoot._staging_thread_count_lock:
650 DevServerRoot._staging_thread_count += 1
651 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800652 boolean_string = kwargs.get('clean')
653 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
654 if clean and os.path.exists(dl.GetBuildDir()):
655 _Log('Removing %s' % dl.GetBuildDir())
656 shutil.rmtree(dl.GetBuildDir())
Keith Haddow58f36d12020-10-28 16:16:39 +0000657 dl.Download(factory)
Dan Shi59ae7092013-06-04 14:37:27 -0700658 finally:
659 with DevServerRoot._staging_thread_count_lock:
660 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800661 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700662
663 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -0800664 def locate_file(self, **kwargs):
665 """Get the path to the given file name.
666
667 This method looks up the given file name inside specified build artifacts.
668 One use case is to help caller to locate an apk file inside a build
669 artifact. The location of the apk file could be different based on the
670 branch and target.
671
672 Args:
673 file_name: Name of the file to look for.
674 artifacts: A list of artifact names to search for the file.
675
676 Returns:
677 Path to the file with the given name. It's relative to the folder for the
678 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -0800679 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800680 if is_deprecated_server():
681 raise DeprecatedRPCError('locate_file')
682
Amin Hassani2aa34282020-11-18 01:18:19 +0000683 dl, _ = _get_downloader_and_factory(kwargs)
Dan Shi2f136862016-02-11 15:38:38 -0800684 try:
Joe Brennan1691f8e2017-03-15 15:53:36 -0700685 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 15:38:38 -0800686 artifacts = kwargs['artifacts']
687 except KeyError:
Amin Hassanid4e35392019-10-03 11:02:44 -0700688 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700689 '`file_name` and `artifacts` are required to search '
690 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 15:38:38 -0800691 build_path = dl.GetBuildDir()
692 for artifact in artifacts:
693 # Get the unzipped folder of the artifact. If it's not defined in
694 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
695 # directory directly.
696 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
697 artifact_path = os.path.join(build_path, folder)
698 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 15:53:36 -0700699 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 15:38:38 -0800700 return os.path.relpath(os.path.join(root, file_name), build_path)
Amin Hassanid4e35392019-10-03 11:02:44 -0700701 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700702 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 15:38:38 -0800703
704 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -0800705 def setup_telemetry(self, **kwargs):
706 """Extracts and sets up telemetry
707
708 This method goes through the telemetry deps packages, and stages them on
709 the devserver to be used by the drones and the telemetry tests.
710
711 Args:
712 archive_url: Google Storage URL for the build.
713
714 Returns:
715 Path to the source folder for the telemetry codebase once it is staged.
716 """
Amin Hassani2aa34282020-11-18 01:18:19 +0000717 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 13:39:18 -0800718
Gabe Black3b567202015-09-23 14:07:59 -0700719 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -0800720 deps_path = os.path.join(build_path, 'autotest/packages')
721 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
722 src_folder = os.path.join(telemetry_path, 'src')
723
724 with self._telemetry_lock_dict.lock(telemetry_path):
725 if os.path.exists(src_folder):
726 # Telemetry is already fully stage return
727 return src_folder
728
729 common_util.MkDirP(telemetry_path)
730
731 # Copy over the required deps tar balls to the telemetry directory.
732 for dep in TELEMETRY_DEPS:
733 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -0700734 if not os.path.exists(dep_path):
735 # This dep does not exist (could be new), do not extract it.
736 continue
Simran Basi4baad082013-02-14 13:39:18 -0800737 try:
Eliot Courtneyf39420b2020-10-27 18:34:04 +0900738 cros_build_lib.ExtractTarball(dep_path, telemetry_path)
739 except cros_build_lib.TarballError as e:
Simran Basi4baad082013-02-14 13:39:18 -0800740 shutil.rmtree(telemetry_path)
Amin Hassanid4e35392019-10-03 11:02:44 -0700741 raise DevServerError(str(e))
Simran Basi4baad082013-02-14 13:39:18 -0800742
743 # By default all the tarballs extract to test_src but some parts of
744 # the telemetry code specifically hardcoded to exist inside of 'src'.
745 test_src = os.path.join(telemetry_path, 'test_src')
746 try:
747 shutil.move(test_src, src_folder)
748 except shutil.Error:
749 # This can occur if src_folder already exists. Remove and retry move.
750 shutil.rmtree(src_folder)
Amin Hassanid4e35392019-10-03 11:02:44 -0700751 raise DevServerError(
Gabe Black3b567202015-09-23 14:07:59 -0700752 'Failure in telemetry setup for build %s. Appears that the '
753 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -0800754
755 return src_folder
756
757 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800758 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -0700759 """Symbolicates a minidump using pre-downloaded symbols, returns it.
760
761 Callers will need to POST to this URL with a body of MIME-type
762 "multipart/form-data".
763 The body should include a single argument, 'minidump', containing the
764 binary-formatted minidump to symbolicate.
765
Chris Masone816e38c2012-05-02 12:22:36 -0700766 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -0800767 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -0700768 minidump: The binary minidump file to symbolicate.
769 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800770 if is_deprecated_server():
771 raise DeprecatedRPCError('symbolicate_dump')
772
Chris Sosa76e44b92013-01-31 12:11:38 -0800773 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -0700774 # Try debug.tar.xz first, then debug.tgz
775 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
776 kwargs['artifacts'] = artifact
Amin Hassani2aa34282020-11-18 01:18:19 +0000777 dl = _get_downloader(kwargs)
Dan Shif08fe492016-10-04 14:39:25 -0700778
779 try:
780 if self.stage(**kwargs) == 'Success':
781 break
782 except build_artifact.ArtifactDownloadError:
783 continue
784 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700785 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700786 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -0800787
Chris Masone816e38c2012-05-02 12:22:36 -0700788 to_return = ''
789 with tempfile.NamedTemporaryFile() as local:
790 while True:
791 data = minidump.file.read(8192)
792 if not data:
793 break
794 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -0800795
Chris Masone816e38c2012-05-02 12:22:36 -0700796 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -0800797
Gabe Black3b567202015-09-23 14:07:59 -0700798 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -0800799
xixuanab744382017-04-27 10:41:27 -0700800 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 12:11:38 -0800801 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 10:41:27 -0700802 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 12:11:38 -0800803 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
804
Chris Masone816e38c2012-05-02 12:22:36 -0700805 to_return, error_text = stackwalk.communicate()
806 if stackwalk.returncode != 0:
Amin Hassanid4e35392019-10-03 11:02:44 -0700807 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700808 "Can't generate stack trace: %s (rc=%d)" % (error_text,
809 stackwalk.returncode))
Chris Masone816e38c2012-05-02 12:22:36 -0700810
811 return to_return
812
813 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800814 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -0400815 """Return a string representing the latest build for a given target.
816
817 Args:
818 target: The build target, typically a combination of the board and the
819 type of build e.g. x86-mario-release.
820 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
821 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -0800822
Scott Zawalski16954532012-03-20 15:31:36 -0400823 Returns:
824 A string representation of the latest build if one exists, i.e.
825 R19-1993.0.0-a1-b1480.
826 An empty string if no latest could be found.
827 """
Sanika Kulkarni07d47ed2020-08-06 14:56:46 -0700828 if is_deprecated_server():
829 raise DeprecatedRPCError('latestbuild')
830
Don Garrettf84631a2014-01-07 18:21:26 -0800831 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -0400832 return _PrintDocStringAsHTML(self.latestbuild)
833
Don Garrettf84631a2014-01-07 18:21:26 -0800834 if 'target' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800835 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
836 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -0700837
838 if _is_android_build_request(kwargs):
839 branch = kwargs.get('branch', None)
840 target = kwargs.get('target', None)
841 if not target or not branch:
Amin Hassanid4e35392019-10-03 11:02:44 -0700842 raise DevServerError('Both target and branch must be specified to query'
843 ' for the latest Android build.')
Dan Shi61305df2015-10-26 16:52:35 -0700844 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
845
Scott Zawalski16954532012-03-20 15:31:36 -0400846 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700847 return common_util.GetLatestBuildVersion(
Amin Hassani2aa34282020-11-18 01:18:19 +0000848 updater.static_dir, kwargs['target'],
Don Garrettf84631a2014-01-07 18:21:26 -0800849 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -0700850 except common_util.CommonUtilError as errmsg:
Amin Hassani722e0962019-11-15 15:45:31 -0800851 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
852 str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -0400853
854 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -0700855 def list_suite_controls(self, **kwargs):
856 """Return a list of contents of all known control files.
857
858 Example URL:
859 To List all control files' content:
860 http://dev-server/list_suite_controls?suite_name=bvt&
861 build=daisy_spring-release/R29-4279.0.0
862
863 Args:
864 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
865 suite_name: List the control files belonging to that suite.
866
867 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -0700868 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -0700869 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800870 if is_deprecated_server():
871 raise DeprecatedRPCError('list_suite_controls')
872
xixuan7efd0002016-04-14 15:34:01 -0700873 if not kwargs:
874 return _PrintDocStringAsHTML(self.controlfiles)
875
876 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800877 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
878 'Error: build= is required!')
xixuan7efd0002016-04-14 15:34:01 -0700879
880 if 'suite_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800881 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
882 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -0700883
884 control_file_list = [
885 line.rstrip() for line in common_util.GetControlFileListForSuite(
Amin Hassani2aa34282020-11-18 01:18:19 +0000886 updater.static_dir, kwargs['build'],
xixuan7efd0002016-04-14 15:34:01 -0700887 kwargs['suite_name']).splitlines()]
888
Dan Shia1cd6522016-04-18 16:07:21 -0700889 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -0700890 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -0700891 control_file_content_dict[control_path] = (common_util.GetControlFile(
Amin Hassani2aa34282020-11-18 01:18:19 +0000892 updater.static_dir, kwargs['build'], control_path))
xixuan7efd0002016-04-14 15:34:01 -0700893
Dan Shia1cd6522016-04-18 16:07:21 -0700894 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -0700895
896 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800897 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -0500898 """Return a control file or a list of all known control files.
899
900 Example URL:
901 To List all control files:
beepsbd337242013-07-09 22:44:06 -0700902 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
903 To List all control files for, say, the bvt suite:
904 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -0500905 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500906 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 -0500907
908 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -0500909 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -0500910 control_path: If you want the contents of a control file set this
911 to the path. E.g. client/site_tests/sleeptest/control
912 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -0700913 suite_name: If control_path is not specified but a suite_name is
914 specified, list the control files belonging to that suite instead of
915 all control files. The empty string for suite_name will list all control
916 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -0800917
Scott Zawalski4647ce62012-01-03 17:17:28 -0500918 Returns:
919 Contents of a control file if control_path is provided.
920 A list of control files if no control_path is provided.
921 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800922 if is_deprecated_server():
923 raise DeprecatedRPCError('controlfiles')
924
Don Garrettf84631a2014-01-07 18:21:26 -0800925 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -0500926 return _PrintDocStringAsHTML(self.controlfiles)
927
Don Garrettf84631a2014-01-07 18:21:26 -0800928 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800929 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
930 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -0500931
Don Garrettf84631a2014-01-07 18:21:26 -0800932 if 'control_path' not in kwargs:
933 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -0700934 return common_util.GetControlFileListForSuite(
Amin Hassani2aa34282020-11-18 01:18:19 +0000935 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -0700936 else:
937 return common_util.GetControlFileList(
Amin Hassani2aa34282020-11-18 01:18:19 +0000938 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -0500939 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -0700940 return common_util.GetControlFile(
Amin Hassani2aa34282020-11-18 01:18:19 +0000941 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -0800942
943 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -0700944 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700945 """Translates an xBuddy path to a real path to artifact if it exists.
946
947 Args:
Simran Basi99e63c02014-05-20 10:39:52 -0700948 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
949 Local searches the devserver's static directory. Remote searches a
950 Google Storage image archive.
951
952 Kwargs:
953 image_dir: Google Storage image archive to search in if requesting a
954 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700955
956 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -0700957 String in the format of build_id/artifact as stored on the local server
958 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700959 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800960 if is_deprecated_server():
961 raise DeprecatedRPCError('xbuddy_translate')
962
Simran Basi99e63c02014-05-20 10:39:52 -0700963 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -0700964 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -0700965 response = os.path.join(build_id, filename)
966 _Log('Path translation requested, returning: %s', response)
967 return response
968
969 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -0700970 def xbuddy(self, *args, **kwargs):
971 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -0700972
973 Args:
joycheneaf4cfc2013-07-02 08:38:57 -0700974 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -0700975 components of the path. The path can be understood as
976 "{local|remote}/build_id/artifact" where build_id is composed of
977 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -0700978
joychen121fc9b2013-08-02 14:30:30 -0700979 The first path element is optional, and can be "remote" or "local"
980 If local (the default), devserver will not attempt to access Google
981 Storage, and will only search the static directory for the files.
982 If remote, devserver will try to obtain the artifact off GS if it's
983 not found locally.
984 The board is the familiar board name, optionally suffixed.
985 The version can be the google storage version number, and may also be
986 any of a number of xBuddy defined version aliases that will be
987 translated into the latest built image that fits the description.
988 Defaults to latest.
989 The artifact is one of a number of image or artifact aliases used by
990 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -0700991
992 Kwargs:
joychen3cb228e2013-06-12 12:13:13 -0700993 return_dir: {true|false}
994 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -0800995 relative_path: {true|false}
996 if set to true, returns the relative path to the payload
997 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -0700998 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -0700999 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -07001000 or
joycheneaf4cfc2013-07-02 08:38:57 -07001001 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -07001002
1003 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001004 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1005 http://host:port/static/x86-generic-release/R26-4000.0.0/
1006 If |relative_path| is true, return a relative path the folder where the
1007 payloads are. E.g.,
1008 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -07001009 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001010 if is_deprecated_server():
1011 raise DeprecatedRPCError('xbuddy')
1012
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001013 boolean_string = kwargs.get('return_dir')
1014 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1015 boolean_string = kwargs.get('relative_path')
1016 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001017
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001018 if return_dir and relative_path:
Amin Hassani722e0962019-11-15 15:45:31 -08001019 raise DevServerHTTPError(
Amin Hassanid4e35392019-10-03 11:02:44 -07001020 http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001021 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001022
Amin Hassania4bc5652020-10-19 12:44:24 -07001023 build_id, file_name = self._xbuddy.Get(args)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001024
1025 response = None
1026 if return_dir:
1027 response = os.path.join(cherrypy.request.base, 'static', build_id)
1028 _Log('Directory requested, returning: %s', response)
1029 elif relative_path:
1030 response = build_id
1031 _Log('Relative path requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001032 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001033 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001034 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001035 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001036 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001037
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001038 return response
1039
joychen3cb228e2013-06-12 12:13:13 -07001040 @cherrypy.expose
joychen3cb228e2013-06-12 12:13:13 -07001041 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001042 """Returns the number of images cached by xBuddy."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001043 if is_deprecated_server():
1044 raise DeprecatedRPCError('xbuddy_capacity')
1045
joychen3cb228e2013-06-12 12:13:13 -07001046 return self._xbuddy.Capacity()
1047
1048 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001049 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001050 """Presents a welcome message and documentation links."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001051 if is_deprecated_server():
1052 raise DeprecatedRPCError('index')
1053
Congbin Guo6bc32182019-08-20 17:54:30 -07001054 html_template = (
1055 'Welcome to the Dev Server!<br>\n'
1056 '<br>\n'
1057 'Here are the available methods, click for documentation:<br>\n'
1058 '<br>\n'
1059 '%s')
1060
1061 exposed_methods = []
1062 for app in cherrypy.tree.apps.values():
1063 exposed_methods += _FindExposedMethods(
1064 app.root, app.script_name.lstrip('/'),
1065 unlisted=self._UNLISTED_METHODS)
1066
1067 return html_template % '<br>\n'.join(
1068 ['<a href=doc/%s>%s</a>' % (name, name)
1069 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001070
1071 @cherrypy.expose
1072 def doc(self, *args):
1073 """Shows the documentation for available methods / URLs.
1074
Amin Hassani08e42d22019-06-03 00:31:30 -07001075 Examples:
Amin Hassani2aa34282020-11-18 01:18:19 +00001076 http://myhost/doc/update
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001077 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001078 if is_deprecated_server():
1079 raise DeprecatedRPCError('doc')
1080
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001081 name = '/'.join(args)
Congbin Guo6bc32182019-08-20 17:54:30 -07001082 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001083 if not method:
Amin Hassanid4e35392019-10-03 11:02:44 -07001084 raise DevServerError("No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001085 if not method.__doc__:
Amin Hassanid4e35392019-10-03 11:02:44 -07001086 raise DevServerError("No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001087 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001088
Amin Hassani2aa34282020-11-18 01:18:19 +00001089 @cherrypy.expose
1090 def update(self, *args, **kwargs):
1091 """Handles an update check from a Chrome OS client.
1092
1093 The HTTP request should contain the standard Omaha-style XML blob. The URL
1094 line may contain an additional intermediate path to the update payload.
1095
1096 This request can be handled in one of 4 ways, depending on the devsever
1097 settings and intermediate path.
1098
1099 1. No intermediate path. DEPRECATED
1100
1101 2. Path explicitly invokes XBuddy
1102 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1103 with 'xbuddy'. This path is then used to acquire an image binary for the
1104 devserver to generate an update payload from. Devserver then serves this
1105 payload.
1106
1107 3. Path is left for the devserver to interpret.
1108 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1109 to generate a payload from the test image in that directory and serve it.
1110
1111 Examples:
1112 2. Explicitly invoke xbuddy
1113 update_engine_client --omaha_url=
1114 http://myhost/update/xbuddy/remote/board/version/dev
1115 This would go to GS to download the dev image for the board, from which
1116 the devserver would generate a payload to serve.
1117
1118 3. Give a path for devserver to interpret
1119 update_engine_client --omaha_url=http://myhost/update/some/random/path
1120 This would attempt, in order to:
1121 a) Generate an update from a test image binary if found in
1122 static_dir/some/random/path.
1123 b) Serve an update payload found in static_dir/some/random/path.
1124 c) Hope that some/random/path takes the form "board/version" and
1125 and attempt to download an update payload for that board/version
1126 from GS.
1127 """
1128 label = '/'.join(args)
1129 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
1130 data = cherrypy.request.rfile.read(body_length)
1131
1132 return updater.HandleUpdatePing(data, label, **kwargs)
1133
Dan Shif5ce2de2013-04-25 16:06:32 -07001134
Chris Sosadbc20082012-12-10 13:39:11 -08001135def _CleanCache(cache_dir, wipe):
1136 """Wipes any excess cached items in the cache_dir.
1137
1138 Args:
1139 cache_dir: the directory we are wiping from.
1140 wipe: If True, wipe all the contents -- not just the excess.
1141 """
1142 if wipe:
1143 # Clear the cache and exit on error.
1144 cmd = 'rm -rf %s/*' % cache_dir
1145 if os.system(cmd) != 0:
1146 _Log('Failed to clear the cache with %s' % cmd)
1147 sys.exit(1)
1148 else:
1149 # Clear all but the last N cached updates
1150 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1151 (cache_dir, CACHED_ENTRIES))
1152 if os.system(cmd) != 0:
1153 _Log('Failed to clean up old delta cache files with %s' % cmd)
1154 sys.exit(1)
1155
1156
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001157def _AddTestingOptions(parser):
1158 group = optparse.OptionGroup(
1159 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1160 'developers writing integration tests utilizing the devserver. They are '
1161 'not intended to be really used outside the scope of someone '
1162 'knowledgable about the test.')
1163 group.add_option('--exit',
1164 action='store_true',
Amin Hassanie9ffb862019-09-25 17:10:40 -07001165 help='do not start the server (yet clear cache)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001166 parser.add_option_group(group)
1167
1168
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001169def _AddProductionOptions(parser):
1170 group = optparse.OptionGroup(
1171 parser, 'Advanced Server Options', 'These options can be used to changed '
1172 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001173 group.add_option('--clear_cache',
1174 action='store_true', default=False,
1175 help='At startup, removes all cached entries from the'
Amin Hassanid4e35392019-10-03 11:02:44 -07001176 "devserver's cache.")
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001177 group.add_option('--logfile',
1178 metavar='PATH',
1179 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001180 group.add_option('--pidfile',
1181 metavar='PATH',
1182 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001183 group.add_option('--portfile',
1184 metavar='PATH',
1185 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001186 group.add_option('--production',
1187 action='store_true', default=False,
1188 help='have the devserver use production values when '
1189 'starting up. This includes using more threads and '
1190 'performing less logging.')
1191 parser.add_option_group(group)
1192
1193
Paul Hobbsef4e0702016-06-27 17:01:42 -07001194def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001195 """Create a LogHandler instance used to log all messages."""
1196 hdlr_cls = handlers.TimedRotatingFileHandler
1197 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001198 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001199 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001200 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001201 return hdlr
1202
1203
Chris Sosacde6bf42012-05-31 18:36:39 -07001204def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001205 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001206 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001207
1208 # get directory that the devserver is run from
1209 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001210 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001211 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001212 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001213 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001214 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001215 parser.add_option('--port',
1216 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001217 help=('port for the dev server to use; if zero, binds to '
1218 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001219 parser.add_option('-t', '--test_image',
1220 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001221 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001222 parser.add_option('-x', '--xbuddy_manage_builds',
1223 action='store_true',
1224 default=False,
1225 help='If set, allow xbuddy to manage images in'
1226 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001227 parser.add_option('-a', '--android_build_credential',
1228 default=None,
1229 help='Path to a json file which contains the credential '
1230 'needed to access Android builds.')
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001231 parser.add_option('--infra_removal',
1232 action='store_true', default=False,
1233 help='If option is present, some RPCs will be disabled to '
1234 'help with infra removal efforts. See '
1235 'go/devserver-deprecation')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001236 _AddProductionOptions(parser)
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001237 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001238 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001239
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001240 # Handle options that must be set globally in cherrypy. Do this
1241 # work up front, because calls to _Log() below depend on this
1242 # initialization.
1243 if options.production:
1244 cherrypy.config.update({'environment': 'production'})
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001245 cherrypy.config.update({'infra_removal': options.infra_removal})
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001246 if not options.logfile:
1247 cherrypy.config.update({'log.screen': True})
1248 else:
1249 cherrypy.config.update({'log.error_file': '',
1250 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001251 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001252 # Pylint can't seem to process these two calls properly
1253 # pylint: disable=E1101
1254 cherrypy.log.access_log.addHandler(hdlr)
1255 cherrypy.log.error_log.addHandler(hdlr)
1256 # pylint: enable=E1101
1257
joychened64b222013-06-21 16:39:34 -07001258 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001259 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001260
joychened64b222013-06-21 16:39:34 -07001261 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001262 # If our devserver is only supposed to serve payloads, we shouldn't be
1263 # mucking with the cache at all. If the devserver hadn't previously
1264 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001265 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001266 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001267 else:
1268 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001269
Amin Hassanief523622020-07-06 12:09:23 -07001270 pkgroot_dir = os.path.join(options.static_dir, 'pkgroot')
1271 common_util.SymlinkFile('/build', pkgroot_dir)
1272
Chris Sosadbc20082012-12-10 13:39:11 -08001273 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001274 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001275
Amin Hassanie9ffb862019-09-25 17:10:40 -07001276 _xbuddy = xbuddy.XBuddy(manage_builds=options.xbuddy_manage_builds,
joychen121fc9b2013-08-02 14:30:30 -07001277 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001278 if options.clear_cache and options.xbuddy_manage_builds:
1279 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001280
Amin Hassani2aa34282020-11-18 01:18:19 +00001281 # We allow global use here to share with cherrypy classes.
1282 # pylint: disable=W0603
1283 global updater
1284 updater = autoupdate.Autoupdate(_xbuddy, static_dir=options.static_dir)
1285
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001286 if options.exit:
1287 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001288
Amin Hassani2aa34282020-11-18 01:18:19 +00001289 dev_server = DevServerRoot(_xbuddy)
Congbin Guo3afae6c2019-08-13 16:29:42 -07001290 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 12:13:13 -07001291
Chris Sosa855b8932013-08-21 13:24:55 -07001292 if options.pidfile:
1293 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1294
Gilad Arnold11fbef42014-02-10 11:04:13 -08001295 if options.portfile:
1296 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1297
Dan Shiafd5c6c2016-01-07 10:27:03 -08001298 if (options.android_build_credential and
1299 os.path.exists(options.android_build_credential)):
1300 try:
1301 with open(options.android_build_credential) as f:
1302 android_build.BuildAccessor.credential_info = json.load(f)
1303 except ValueError as e:
1304 _Log('Failed to load the android build credential: %s. Error: %s.' %
1305 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 16:29:42 -07001306
1307 cherrypy.tree.mount(health_checker_app, '/check_health',
1308 config=health_checker.get_config())
joychen3cb228e2013-06-12 12:13:13 -07001309 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001310
1311
1312if __name__ == '__main__':
1313 main()