blob: c25191c546410af49dd2054bf6dae301726088f2 [file] [log] [blame]
David Riley2fcb0122017-11-02 11:25:39 -07001#!/usr/bin/env python2
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -07002# -*- coding: utf-8 -*-
Chris Sosa781ba6d2012-04-11 12:44:43 -07003# Copyright (c) 2009-2012 The Chromium OS Authors. All rights reserved.
rtc@google.comded22402009-10-26 22:36:21 +00004# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Chris Sosa3ae4dc12013-03-29 11:47:00 -07007"""Chromium OS development server that can be used for all forms of update.
8
9This devserver can be used to perform system-wide autoupdate and update
10of specific portage packages on devices running Chromium OS derived operating
Amin Hassanie9ffb862019-09-25 17:10:40 -070011systems.
Chris Sosa3ae4dc12013-03-29 11:47:00 -070012
Amin Hassanie9ffb862019-09-25 17:10:40 -070013The devserver is configured to stage and
Chris Sosa3ae4dc12013-03-29 11:47:00 -070014serve 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
Chris Sosa3ae4dc12013-03-29 11:47:00 -070021For autoupdates, there are many more advanced options that can help specify
22how to update and which payload to give to a requester.
23"""
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
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070048import autoupdate
49import 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
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070056from chromite.lib.xbuddy import android_build
57from chromite.lib.xbuddy import artifact_info
58from chromite.lib.xbuddy import build_artifact
59from chromite.lib.xbuddy import cherrypy_log_util
60from chromite.lib.xbuddy import common_util
61from chromite.lib.xbuddy import devserver_constants
62from chromite.lib.xbuddy import downloader
63from chromite.lib.xbuddy import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070064
Gilad Arnoldc65330c2012-09-20 15:17:48 -070065# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080066def _Log(message, *args):
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070067 return cherrypy_log_util.LogWithTag('DEVSERVER', message, *args)
Frank Farzan40160872011-12-12 18:39:18 -080068
Chris Sosa417e55d2011-01-25 16:40:48 -080069CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080070
Simran Basi4baad082013-02-14 13:39:18 -080071TELEMETRY_FOLDER = 'telemetry_src'
72TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
73 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070074 'dep-chrome_test.tar.bz2',
75 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -080076
Chris Sosa0356d3b2010-09-16 15:46:22 -070077# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +000078updater = None
rtc@google.comded22402009-10-26 22:36:21 +000079
xixuan3d48bff2017-01-30 19:00:09 -080080# Log rotation parameters. These settings correspond to twice a day once
81# devserver is started, with about two weeks (28 backup files) of old logs
82# kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -070083#
xixuan3d48bff2017-01-30 19:00:09 -080084# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 11:05:19 -070085# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-30 19:00:09 -080086_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 16:29:42 -070087_LOG_ROTATION_INTERVAL = 12 # hours
88_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-12 18:39:18 -080089
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -080090# Error msg for deprecated RPC usage.
91DEPRECATED_RPC_ERROR_MSG = ('The %s RPC has been deprecated. Usage of this '
92 'RPC is discouraged. Please go to '
93 'go/devserver-deprecation for more information.')
94
xixuan52c2fba2016-05-20 17:02:48 -070095
Amin Hassanid4e35392019-10-03 11:02:44 -070096class DevServerError(Exception):
97 """Exception class used by DevServer."""
98
99
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800100class DeprecatedRPCError(DevServerError):
101 """Exception class used when an RPC is deprecated but is still being used."""
102
103 def __init__(self, rpc_name):
104 """Constructor for DeprecatedRPCError class.
105
106 :param rpc_name: (str) name of the RPC that has been deprecated.
107 """
Amin Hassani6ecda232020-03-09 19:03:23 -0700108 super(DeprecatedRPCError, self).__init__(
109 DEPRECATED_RPC_ERROR_MSG % rpc_name)
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800110 self.rpc_name = rpc_name
111
112
Amin Hassani722e0962019-11-15 15:45:31 -0800113class DevServerHTTPError(cherrypy.HTTPError):
114 """Exception class to log the HTTPResponse before routing it to cherrypy."""
115 def __init__(self, status, message):
116 """CherryPy error with logging.
117
118 Args:
119 status: HTTPResponse status.
120 message: Message associated with the response.
121 """
122 cherrypy.HTTPError.__init__(self, status, message)
123 _Log('HTTPError status: %s message: %s', status, message)
124
125
Gabe Black3b567202015-09-23 14:07:59 -0700126def _canonicalize_archive_url(archive_url):
127 """Canonicalizes archive_url strings.
128
129 Raises:
130 DevserverError: if archive_url is not set.
131 """
132 if archive_url:
133 if not archive_url.startswith('gs://'):
Amin Hassanid4e35392019-10-03 11:02:44 -0700134 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700135 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 14:07:59 -0700136
137 return archive_url.rstrip('/')
138 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700139 raise DevServerError('Must specify an archive_url in the request')
Gabe Black3b567202015-09-23 14:07:59 -0700140
141
142def _canonicalize_local_path(local_path):
143 """Canonicalizes |local_path| strings.
144
145 Raises:
146 DevserverError: if |local_path| is not set.
147 """
148 # Restrict staging of local content to only files within the static
149 # directory.
150 local_path = os.path.abspath(local_path)
151 if not local_path.startswith(updater.static_dir):
Amin Hassanid4e35392019-10-03 11:02:44 -0700152 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700153 'Local path %s must be a subdirectory of the static'
154 ' directory: %s' % (local_path, updater.static_dir))
Gabe Black3b567202015-09-23 14:07:59 -0700155
156 return local_path.rstrip('/')
157
158
159def _get_artifacts(kwargs):
160 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
161
162 Raises:
163 DevserverError if no artifacts would be returned.
164 """
165 artifacts = kwargs.get('artifacts')
166 files = kwargs.get('files')
167 if not artifacts and not files:
Amin Hassanid4e35392019-10-03 11:02:44 -0700168 raise DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700169
170 # Note we NEED to coerce files to a string as we get raw unicode from
171 # cherrypy and we treat files as strings elsewhere in the code.
172 return (str(artifacts).split(',') if artifacts else [],
173 str(files).split(',') if files else [])
174
175
Dan Shi61305df2015-10-26 16:52:35 -0700176def _is_android_build_request(kwargs):
177 """Check if a devserver call is for Android build, based on the arguments.
178
179 This method exams the request's arguments (os_type) to determine if the
180 request is for Android build. If os_type is set to `android`, returns True.
181 If os_type is not set or has other values, returns False.
182
183 Args:
184 kwargs: Keyword arguments for the request.
185
186 Returns:
187 True if the request is for Android build. False otherwise.
188 """
189 os_type = kwargs.get('os_type', None)
190 return os_type == 'android'
191
192
Gabe Black3b567202015-09-23 14:07:59 -0700193def _get_downloader(kwargs):
194 """Returns the downloader based on passed in arguments.
195
196 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700197 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700198 """
199 local_path = kwargs.get('local_path')
200 if local_path:
201 local_path = _canonicalize_local_path(local_path)
202
203 dl = None
204 if local_path:
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800205 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
206 dl = downloader.LocalDownloader(updater.static_dir, local_path,
207 delete_source=delete_source)
Gabe Black3b567202015-09-23 14:07:59 -0700208
Dan Shi61305df2015-10-26 16:52:35 -0700209 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700210 archive_url = kwargs.get('archive_url')
211 if not archive_url and not local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700212 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700213 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700214 if archive_url and local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700215 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700216 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700217 if not dl:
218 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700219 dl = downloader.GoogleStorageDownloader(
220 updater.static_dir, archive_url,
221 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
222 archive_url))
Gabe Black3b567202015-09-23 14:07:59 -0700223 elif not dl:
224 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 12:10:33 -0700225 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 16:52:35 -0700226 build_id = kwargs.get('build_id', None)
227 if not target or not branch or not build_id:
Amin Hassanid4e35392019-10-03 11:02:44 -0700228 raise DevServerError('target, branch, build ID must all be specified for '
229 'downloading Android build.')
Dan Shi72b16132015-10-08 12:10:33 -0700230 dl = downloader.AndroidBuildDownloader(updater.static_dir, branch, build_id,
231 target)
Gabe Black3b567202015-09-23 14:07:59 -0700232
233 return dl
234
235
236def _get_downloader_and_factory(kwargs):
237 """Returns the downloader and artifact factory based on passed in arguments.
238
239 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700240 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700241 """
242 artifacts, files = _get_artifacts(kwargs)
243 dl = _get_downloader(kwargs)
244
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700245 if (isinstance(dl, (downloader.GoogleStorageDownloader,
246 downloader.LocalDownloader))):
Gabe Black3b567202015-09-23 14:07:59 -0700247 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 12:10:33 -0700248 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 14:07:59 -0700249 factory_class = build_artifact.AndroidArtifactFactory
250 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700251 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700252 'Unrecognized value for downloader type: %s' % type(dl))
Gabe Black3b567202015-09-23 14:07:59 -0700253
254 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
255
256 return dl, factory
257
258
Scott Zawalski4647ce62012-01-03 17:17:28 -0500259def _LeadingWhiteSpaceCount(string):
260 """Count the amount of leading whitespace in a string.
261
262 Args:
263 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800264
Scott Zawalski4647ce62012-01-03 17:17:28 -0500265 Returns:
266 number of white space chars before characters start.
267 """
Gabe Black3b567202015-09-23 14:07:59 -0700268 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500269 if matched:
270 return len(matched.group())
271
272 return 0
273
274
275def _PrintDocStringAsHTML(func):
276 """Make a functions docstring somewhat HTML style.
277
278 Args:
279 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800280
Scott Zawalski4647ce62012-01-03 17:17:28 -0500281 Returns:
282 A string that is somewhat formated for a web browser.
283 """
284 # TODO(scottz): Make this parse Args/Returns in a prettier way.
285 # Arguments could be bolded and indented etc.
286 html_doc = []
287 for line in func.__doc__.splitlines():
288 leading_space = _LeadingWhiteSpaceCount(line)
289 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700290 line = ' ' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500291
292 html_doc.append('<BR>%s' % line)
293
294 return '\n'.join(html_doc)
295
296
Simran Basief83d6a2014-08-28 14:32:01 -0700297def _GetUpdateTimestampHandler(static_dir):
298 """Returns a handler to update directory staged.timestamp.
299
300 This handler resets the stage.timestamp whenever static content is accessed.
301
302 Args:
303 static_dir: Directory from which static content is being staged.
304
305 Returns:
Amin Hassani08e42d22019-06-03 00:31:30 -0700306 A cherrypy handler to update the timestamp of accessed content.
Simran Basief83d6a2014-08-28 14:32:01 -0700307 """
308 def UpdateTimestampHandler():
309 if not '404' in cherrypy.response.status:
310 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
311 cherrypy.request.path_info)
312 if build_match:
313 build_dir = os.path.join(static_dir, build_match.group('build'))
314 downloader.Downloader.TouchTimestampForStaged(build_dir)
315 return UpdateTimestampHandler
316
317
Chris Sosa7c931362010-10-11 19:49:01 -0700318def _GetConfig(options):
319 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800320
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800321 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800322 # Fall back to IPv4 when python is not configured with IPv6.
323 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800324 socket_host = '0.0.0.0'
325
Simran Basief83d6a2014-08-28 14:32:01 -0700326 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
327 # on the on_end_resource hook. This hook is called once processing is
328 # complete and the response is ready to be returned.
329 cherrypy.tools.update_timestamp = cherrypy.Tool(
330 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
331
David Riley2fcb0122017-11-02 11:25:39 -0700332 base_config = {
333 'global': {
334 'server.log_request_headers': True,
335 'server.protocol_version': 'HTTP/1.1',
336 'server.socket_host': socket_host,
337 'server.socket_port': int(options.port),
338 'response.timeout': 6000,
339 'request.show_tracebacks': True,
340 'server.socket_timeout': 60,
341 'server.thread_pool': 2,
342 'engine.autoreload.on': False,
343 },
David Riley2fcb0122017-11-02 11:25:39 -0700344 '/build': {
345 'response.timeout': 100000,
346 },
347 '/update': {
348 # Gets rid of cherrypy parsing post file for args.
349 'request.process_request_body': False,
350 'response.timeout': 10000,
351 },
352 # Sets up the static dir for file hosting.
353 '/static': {
354 'tools.staticdir.dir': options.static_dir,
355 'tools.staticdir.on': True,
356 'response.timeout': 10000,
357 'tools.update_timestamp.on': True,
358 },
359 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700360 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700361 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500362
Chris Sosa7c931362010-10-11 19:49:01 -0700363 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000364
Darin Petkove17164a2010-08-11 13:24:41 -0700365
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700366def _GetRecursiveMemberObject(root, member_list):
367 """Returns an object corresponding to a nested member list.
368
369 Args:
370 root: the root object to search
371 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800372
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700373 Returns:
374 An object corresponding to the member name list; None otherwise.
375 """
376 for member in member_list:
377 next_root = root.__class__.__dict__.get(member)
378 if not next_root:
379 return None
380 root = next_root
381 return root
382
383
384def _IsExposed(name):
385 """Returns True iff |name| has an `exposed' attribute and it is set."""
386 return hasattr(name, 'exposed') and name.exposed
387
388
Congbin Guo6bc32182019-08-20 17:54:30 -0700389def _GetExposedMethod(nested_member):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700390 """Returns a CherryPy-exposed method, if such exists.
391
392 Args:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700393 nested_member: a slash-joined path to the nested member
Don Garrettf84631a2014-01-07 18:21:26 -0800394
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700395 Returns:
Congbin Guo6bc32182019-08-20 17:54:30 -0700396 A function object corresponding to the path defined by |nested_member| from
397 the app root object registered, if the function is exposed; None otherwise.
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700398 """
Congbin Guo6bc32182019-08-20 17:54:30 -0700399 for app in cherrypy.tree.apps.values():
400 # Use the 'index' function doc as the doc of the app.
401 if nested_member == app.script_name.lstrip('/'):
402 nested_member = 'index'
403
404 method = _GetRecursiveMemberObject(app.root, nested_member.split('/'))
405 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
406 return method
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700407
408
Gilad Arnold748c8322012-10-12 09:51:35 -0700409def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700410 """Finds exposed CherryPy methods.
411
412 Args:
413 root: the root object for searching
414 prefix: slash-joined chain of members leading to current object
415 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800416
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700417 Returns:
418 List of exposed URLs that are not unlisted.
419 """
420 method_list = []
Congbin Guo6bc32182019-08-20 17:54:30 -0700421 for member in root.__class__.__dict__.keys():
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700422 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700423 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700424 continue
425 member_obj = root.__class__.__dict__[member]
426 if _IsExposed(member_obj):
Amin Hassani08e42d22019-06-03 00:31:30 -0700427 if isinstance(member_obj, types.FunctionType):
Congbin Guo6bc32182019-08-20 17:54:30 -0700428 # Regard the app name as exposed "method" name if it exposed 'index'
429 # function.
430 if prefix and member == 'index':
431 method_list.append(prefix)
432 else:
433 method_list.append(prefixed_member)
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700434 else:
435 method_list += _FindExposedMethods(
436 member_obj, prefixed_member, unlisted)
437 return method_list
438
439
xixuan52c2fba2016-05-20 17:02:48 -0700440def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-11-30 16:48:20 -0800441 """Parse boolean arg from kwargs.
442
443 Args:
444 kwargs: the parameters to be checked.
445 key: the key to be parsed.
446
447 Returns:
448 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
449
450 Raises:
451 DevServerHTTPError if kwargs[key] is not a boolean variable.
452 """
xixuan52c2fba2016-05-20 17:02:48 -0700453 if key in kwargs:
454 if kwargs[key] == 'True':
455 return True
456 elif kwargs[key] == 'False':
457 return False
458 else:
Amin Hassani722e0962019-11-15 15:45:31 -0800459 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
460 'The value for key %s is not boolean.' % key)
xixuan52c2fba2016-05-20 17:02:48 -0700461 else:
462 return False
463
xixuan447ad9d2017-02-28 14:46:20 -0800464
xixuanac89ce82016-11-30 16:48:20 -0800465def _parse_string_arg(kwargs, key):
466 """Parse string arg from kwargs.
467
468 Args:
469 kwargs: the parameters to be checked.
470 key: the key to be parsed.
471
472 Returns:
473 The string value of kwargs[key], or None if key doesn't exist in kwargs.
474 """
475 if key in kwargs:
476 return kwargs[key]
477 else:
478 return None
479
xixuan447ad9d2017-02-28 14:46:20 -0800480
xixuanac89ce82016-11-30 16:48:20 -0800481def _build_uri_from_build_name(build_name):
482 """Get build url from a given build name.
483
484 Args:
485 build_name: the build name to be parsed, whose format is
486 'board/release_version'.
487
488 Returns:
489 The release_archive_url on Google Storage for this build name.
490 """
Amin Hassani08e42d22019-06-03 00:31:30 -0700491 # TODO(ahassani): This function doesn't seem to be used anywhere since its
492 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
493 # causing any runtime issues. So deprecate this in the future.
494 tokens = build_name.split('/')
495 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-20 17:02:48 -0700496
xixuan447ad9d2017-02-28 14:46:20 -0800497
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800498def is_deprecated_server():
499 """Gets whether the devserver has deprecated RPCs."""
500 return cherrypy.config.get('infra_removal', False)
501
502
David Rochberg7c79a812011-01-19 14:24:45 -0500503class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700504 """The Root Class for the Dev Server.
505
506 CherryPy works as follows:
507 For each method in this class, cherrpy interprets root/path
508 as a call to an instance of DevServerRoot->method_name. For example,
509 a call to http://myhost/build will call build. CherryPy automatically
510 parses http args and places them as keyword arguments in each method.
511 For paths http://myhost/update/dir1/dir2, you can use *args so that
512 cherrypy uses the update method and puts the extra paths in args.
513 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700514 # Method names that should not be listed on the index page.
515 _UNLISTED_METHODS = ['index', 'doc']
516
Dan Shi59ae7092013-06-04 14:37:27 -0700517 # Number of threads that devserver is staging images.
518 _staging_thread_count = 0
519 # Lock used to lock increasing/decreasing count.
520 _staging_thread_count_lock = threading.Lock()
521
joychen3cb228e2013-06-12 12:13:13 -0700522 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700523 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800524 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700525 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500526
Congbin Guo3afae6c2019-08-13 16:29:42 -0700527 @property
528 def staging_thread_count(self):
529 """Get the staging thread count."""
530 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700531
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700532 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500533 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700534 """Builds the package specified."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800535 if is_deprecated_server():
536 raise DeprecatedRPCError('build')
537
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700538 import builder
539 if self._builder is None:
540 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500541 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700542
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700543 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700544 def is_staged(self, **kwargs):
545 """Check if artifacts have been downloaded.
546
Congbin Guo3afae6c2019-08-13 16:29:42 -0700547 Examples:
548 To check if autotest and test_suites are staged:
549 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
550 artifacts=autotest,test_suites
551
Amin Hassani08e42d22019-06-03 00:31:30 -0700552 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700553 async: True to return without waiting for download to complete.
554 artifacts: Comma separated list of named artifacts to download.
555 These are defined in artifact_info and have their implementation
556 in build_artifact.py.
557 files: Comma separated list of file artifacts to stage. These
558 will be available as is in the corresponding static directory with no
559 custom post-processing.
560
Congbin Guo3afae6c2019-08-13 16:29:42 -0700561 Returns:
562 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700563 """
Gabe Black3b567202015-09-23 14:07:59 -0700564 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700565 response = str(dl.IsStaged(factory))
566 _Log('Responding to is_staged %s request with %r', kwargs, response)
567 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700568
Chris Sosa76e44b92013-01-31 12:11:38 -0800569 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800570 def list_image_dir(self, **kwargs):
571 """Take an archive url and list the contents in its staged directory.
572
Amin Hassani08e42d22019-06-03 00:31:30 -0700573 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800574 To list the contents of where this devserver should have staged
575 gs://image-archive/<board>-release/<build> call:
576 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
577
Congbin Guo3afae6c2019-08-13 16:29:42 -0700578 Args:
579 archive_url: Google Storage URL for the build.
580
Prashanth Ba06d2d22014-03-07 15:35:19 -0800581 Returns:
582 A string with information about the contents of the image directory.
583 """
Gabe Black3b567202015-09-23 14:07:59 -0700584 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800585 try:
Gabe Black3b567202015-09-23 14:07:59 -0700586 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800587 except build_artifact.ArtifactDownloadError as e:
588 return 'Cannot list the contents of staged artifacts. %s' % e
589 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700590 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800591 return image_dir_contents
592
593 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800594 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700595 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800596
Gabe Black3b567202015-09-23 14:07:59 -0700597 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700598 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700599 on the devserver. A call to this will attempt to cache non-specified
600 artifacts in the background for the given from the given URL following
601 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800602 artifacts is explicitly defined in the build_artifact module.
603
604 These artifacts will then be available from the static/ sub-directory of
605 the devserver.
606
Amin Hassani08e42d22019-06-03 00:31:30 -0700607 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800608 To download the autotest and test suites tarballs:
609 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
610 artifacts=autotest,test_suites
611 To download the full update payload:
612 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
613 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700614 To download just a file called blah.bin:
615 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
616 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800617
618 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700619 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800620
621 Note for this example, relative path is the archive_url stripped of its
622 basename i.e. path/ in the examples above. Specific example:
623
624 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
625
626 Will get staged to:
627
joychened64b222013-06-21 16:39:34 -0700628 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700629
630 Args:
631 archive_url: Google Storage URL for the build.
632 local_path: Local path for the build.
633 delete_source: Only meaningful with local_path. bool to indicate if the
634 source files should be deleted. This is especially useful when staging
635 a file locally in resource constrained environments as it allows us to
636 move the relevant files locally instead of copying them.
637 async: True to return without waiting for download to complete.
638 artifacts: Comma separated list of named artifacts to download.
639 These are defined in artifact_info and have their implementation
640 in build_artifact.py.
641 files: Comma separated list of files to stage. These
642 will be available as is in the corresponding static directory with no
643 custom post-processing.
644 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800645 """
Gabe Black3b567202015-09-23 14:07:59 -0700646 dl, factory = _get_downloader_and_factory(kwargs)
647
Dan Shi59ae7092013-06-04 14:37:27 -0700648 with DevServerRoot._staging_thread_count_lock:
649 DevServerRoot._staging_thread_count += 1
650 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800651 boolean_string = kwargs.get('clean')
652 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
653 if clean and os.path.exists(dl.GetBuildDir()):
654 _Log('Removing %s' % dl.GetBuildDir())
655 shutil.rmtree(dl.GetBuildDir())
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700656 is_async = kwargs.get('async', False)
657 dl.Download(factory, is_async=is_async)
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
Dan Shi2f136862016-02-11 15:38:38 -0800683 dl, _ = _get_downloader_and_factory(kwargs)
684 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 """
Gabe Black3b567202015-09-23 14:07:59 -0700717 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:
738 common_util.ExtractTarball(dep_path, telemetry_path)
739 except common_util.CommonUtilError as e:
740 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
777 dl = _get_downloader(kwargs)
778
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(
Don Garrettf84631a2014-01-07 18:21:26 -0800848 updater.static_dir, kwargs['target'],
849 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(
886 updater.static_dir, kwargs['build'],
887 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(
xixuan7efd0002016-04-14 15:34:01 -0700892 updater.static_dir, kwargs['build'], control_path))
893
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(
Don Garrettf84631a2014-01-07 18:21:26 -0800935 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -0700936 else:
937 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-07 18:21:26 -0800938 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(
Don Garrettf84631a2014-01-07 18:21:26 -0800941 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:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001076 http://myhost/doc/update
1077 """
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
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07001089 @cherrypy.expose
Amin Hassani6eec8792020-01-09 14:06:48 -08001090 def update(self, *args, **kwargs):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001091 """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
joychen121fc9b2013-08-02 14:30:30 -07001096 This request can be handled in one of 4 ways, depending on the devsever
1097 settings and intermediate path.
joychenb0dfe552013-07-30 10:02:06 -07001098
Amin Hassanie9ffb862019-09-25 17:10:40 -07001099 1. No intermediate path. DEPRECATED
joychen121fc9b2013-08-02 14:30:30 -07001100
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
joychen121fc9b2013-08-02 14:30:30 -07001111 Examples:
joychen121fc9b2013-08-02 14:30:30 -07001112 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.
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001127 """
joychen121fc9b2013-08-02 14:30:30 -07001128 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -08001129 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -07001130 data = cherrypy.request.rfile.read(body_length)
Amin Hassani6aa075c2020-02-21 18:36:44 +00001131
Amin Hassani6eec8792020-01-09 14:06:48 -08001132 return updater.HandleUpdatePing(data, label, **kwargs)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001133
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
Chris Sosa6a3697f2013-01-29 16:44:43 -08001281 # We allow global use here to share with cherrypy classes.
1282 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001283 global updater
Amin Hassanie5612032020-03-25 14:50:42 -07001284 updater = autoupdate.Autoupdate(_xbuddy, static_dir=options.static_dir)
Chris Sosa7c931362010-10-11 19:49:01 -07001285
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001286 if options.exit:
1287 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001288
joychen3cb228e2013-06-12 12:13:13 -07001289 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()