blob: ceec4018b828ab7674d4ad54509d6ecd779932f0 [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
Amin Hassanic5af4262019-11-13 13:37:20 -080027import distutils.version # pylint: disable=import-error,no-name-in-module
Gilad Arnold55a2a372012-10-02 09:46:32 -070028import json
David Riley2fcb0122017-11-02 11:25:39 -070029import optparse # pylint: disable=deprecated-module
rtc@google.comded22402009-10-26 22:36:21 +000030import os
Scott Zawalski4647ce62012-01-03 17:17:28 -050031import re
Simran Basi4baad082013-02-14 13:39:18 -080032import shutil
xixuan52c2fba2016-05-20 17:02:48 -070033import signal
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -080034import socket
Chris Masone816e38c2012-05-02 12:22:36 -070035import subprocess
J. Richard Barnette3d977b82013-04-23 11:05:19 -070036import sys
Chris Masone816e38c2012-05-02 12:22:36 -070037import tempfile
Dan Shi59ae7092013-06-04 14:37:27 -070038import threading
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070039import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070040from logging import handlers
41
Amin Hassanid4e35392019-10-03 11:02:44 -070042from six.moves import http_client
43
44# pylint: disable=no-name-in-module, import-error
J. Richard Barnette3d977b82013-04-23 11:05:19 -070045import cherrypy
Chris Sosa855b8932013-08-21 13:24:55 -070046from cherrypy import _cplogging as cplogging
Amin Hassanid4e35392019-10-03 11:02:44 -070047from cherrypy.process import plugins
48# pylint: enable=no-name-in-module, import-error
rtc@google.comded22402009-10-26 22:36:21 +000049
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070050import autoupdate
51import cherrypy_ext
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070052import health_checker
53
Richard Barnettedf35c322017-08-18 17:02:13 -070054# This must happen before any local modules get a chance to import
55# anything from chromite. Otherwise, really bad things will happen, and
56# you will _not_ understand why.
Congbin Guo3afae6c2019-08-13 16:29:42 -070057import setup_chromite # pylint: disable=unused-import
Amin Hassanie427e212019-10-28 11:04:27 -070058from chromite.lib import cros_update_progress
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070059from chromite.lib.xbuddy import android_build
60from chromite.lib.xbuddy import artifact_info
61from chromite.lib.xbuddy import build_artifact
62from chromite.lib.xbuddy import cherrypy_log_util
63from chromite.lib.xbuddy import common_util
64from chromite.lib.xbuddy import devserver_constants
65from chromite.lib.xbuddy import downloader
66from chromite.lib.xbuddy import xbuddy
Allen Li1dff8612020-03-04 20:21:17 +000067from chromite.scripts import cros_update
Gilad Arnoldc65330c2012-09-20 15:17:48 -070068
Gilad Arnoldc65330c2012-09-20 15:17:48 -070069# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080070def _Log(message, *args):
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070071 return cherrypy_log_util.LogWithTag('DEVSERVER', message, *args)
Frank Farzan40160872011-12-12 18:39:18 -080072
Chris Sosa417e55d2011-01-25 16:40:48 -080073CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080074
Simran Basi4baad082013-02-14 13:39:18 -080075TELEMETRY_FOLDER = 'telemetry_src'
76TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
77 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070078 'dep-chrome_test.tar.bz2',
79 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -080080
Chris Sosa0356d3b2010-09-16 15:46:22 -070081# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +000082updater = None
rtc@google.comded22402009-10-26 22:36:21 +000083
xixuan3d48bff2017-01-30 19:00:09 -080084# Log rotation parameters. These settings correspond to twice a day once
85# devserver is started, with about two weeks (28 backup files) of old logs
86# kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -070087#
xixuan3d48bff2017-01-30 19:00:09 -080088# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 11:05:19 -070089# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-30 19:00:09 -080090_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 16:29:42 -070091_LOG_ROTATION_INTERVAL = 12 # hours
92_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-12 18:39:18 -080093
xixuan52c2fba2016-05-20 17:02:48 -070094# Auto-update parameters
95
96# Error msg for missing key in CrOS auto-update.
Xixuan Wu32af9f12017-11-13 14:11:44 -080097KEY_ERROR_MSG = 'Key Error in RPC: %s= is required'
xixuan52c2fba2016-05-20 17:02:48 -070098
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -080099# Error msg for deprecated RPC usage.
100DEPRECATED_RPC_ERROR_MSG = ('The %s RPC has been deprecated. Usage of this '
101 'RPC is discouraged. Please go to '
102 'go/devserver-deprecation for more information.')
103
xixuan52c2fba2016-05-20 17:02:48 -0700104
Amin Hassanid4e35392019-10-03 11:02:44 -0700105class DevServerError(Exception):
106 """Exception class used by DevServer."""
107
108
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800109class DeprecatedRPCError(DevServerError):
110 """Exception class used when an RPC is deprecated but is still being used."""
111
112 def __init__(self, rpc_name):
113 """Constructor for DeprecatedRPCError class.
114
115 :param rpc_name: (str) name of the RPC that has been deprecated.
116 """
117 super(DeprecatedRPCError, self).__init__(DEPRECATED_RPC_ERROR_MSG % rpc_name)
118 self.rpc_name = rpc_name
119
120
Amin Hassani722e0962019-11-15 15:45:31 -0800121class DevServerHTTPError(cherrypy.HTTPError):
122 """Exception class to log the HTTPResponse before routing it to cherrypy."""
123 def __init__(self, status, message):
124 """CherryPy error with logging.
125
126 Args:
127 status: HTTPResponse status.
128 message: Message associated with the response.
129 """
130 cherrypy.HTTPError.__init__(self, status, message)
131 _Log('HTTPError status: %s message: %s', status, message)
132
133
Gabe Black3b567202015-09-23 14:07:59 -0700134def _canonicalize_archive_url(archive_url):
135 """Canonicalizes archive_url strings.
136
137 Raises:
138 DevserverError: if archive_url is not set.
139 """
140 if archive_url:
141 if not archive_url.startswith('gs://'):
Amin Hassanid4e35392019-10-03 11:02:44 -0700142 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700143 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 14:07:59 -0700144
145 return archive_url.rstrip('/')
146 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700147 raise DevServerError('Must specify an archive_url in the request')
Gabe Black3b567202015-09-23 14:07:59 -0700148
149
150def _canonicalize_local_path(local_path):
151 """Canonicalizes |local_path| strings.
152
153 Raises:
154 DevserverError: if |local_path| is not set.
155 """
156 # Restrict staging of local content to only files within the static
157 # directory.
158 local_path = os.path.abspath(local_path)
159 if not local_path.startswith(updater.static_dir):
Amin Hassanid4e35392019-10-03 11:02:44 -0700160 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700161 'Local path %s must be a subdirectory of the static'
162 ' directory: %s' % (local_path, updater.static_dir))
Gabe Black3b567202015-09-23 14:07:59 -0700163
164 return local_path.rstrip('/')
165
166
167def _get_artifacts(kwargs):
168 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
169
170 Raises:
171 DevserverError if no artifacts would be returned.
172 """
173 artifacts = kwargs.get('artifacts')
174 files = kwargs.get('files')
175 if not artifacts and not files:
Amin Hassanid4e35392019-10-03 11:02:44 -0700176 raise DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700177
178 # Note we NEED to coerce files to a string as we get raw unicode from
179 # cherrypy and we treat files as strings elsewhere in the code.
180 return (str(artifacts).split(',') if artifacts else [],
181 str(files).split(',') if files else [])
182
183
Dan Shi61305df2015-10-26 16:52:35 -0700184def _is_android_build_request(kwargs):
185 """Check if a devserver call is for Android build, based on the arguments.
186
187 This method exams the request's arguments (os_type) to determine if the
188 request is for Android build. If os_type is set to `android`, returns True.
189 If os_type is not set or has other values, returns False.
190
191 Args:
192 kwargs: Keyword arguments for the request.
193
194 Returns:
195 True if the request is for Android build. False otherwise.
196 """
197 os_type = kwargs.get('os_type', None)
198 return os_type == 'android'
199
200
Gabe Black3b567202015-09-23 14:07:59 -0700201def _get_downloader(kwargs):
202 """Returns the downloader based on passed in arguments.
203
204 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700205 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700206 """
207 local_path = kwargs.get('local_path')
208 if local_path:
209 local_path = _canonicalize_local_path(local_path)
210
211 dl = None
212 if local_path:
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800213 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
214 dl = downloader.LocalDownloader(updater.static_dir, local_path,
215 delete_source=delete_source)
Gabe Black3b567202015-09-23 14:07:59 -0700216
Dan Shi61305df2015-10-26 16:52:35 -0700217 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700218 archive_url = kwargs.get('archive_url')
219 if not archive_url and not local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700220 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700221 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700222 if archive_url and local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700223 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700224 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700225 if not dl:
226 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700227 dl = downloader.GoogleStorageDownloader(
228 updater.static_dir, archive_url,
229 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
230 archive_url))
Gabe Black3b567202015-09-23 14:07:59 -0700231 elif not dl:
232 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 12:10:33 -0700233 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 16:52:35 -0700234 build_id = kwargs.get('build_id', None)
235 if not target or not branch or not build_id:
Amin Hassanid4e35392019-10-03 11:02:44 -0700236 raise DevServerError('target, branch, build ID must all be specified for '
237 'downloading Android build.')
Dan Shi72b16132015-10-08 12:10:33 -0700238 dl = downloader.AndroidBuildDownloader(updater.static_dir, branch, build_id,
239 target)
Gabe Black3b567202015-09-23 14:07:59 -0700240
241 return dl
242
243
244def _get_downloader_and_factory(kwargs):
245 """Returns the downloader and artifact factory based on passed in arguments.
246
247 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700248 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700249 """
250 artifacts, files = _get_artifacts(kwargs)
251 dl = _get_downloader(kwargs)
252
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700253 if (isinstance(dl, (downloader.GoogleStorageDownloader,
254 downloader.LocalDownloader))):
Gabe Black3b567202015-09-23 14:07:59 -0700255 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 12:10:33 -0700256 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 14:07:59 -0700257 factory_class = build_artifact.AndroidArtifactFactory
258 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700259 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700260 'Unrecognized value for downloader type: %s' % type(dl))
Gabe Black3b567202015-09-23 14:07:59 -0700261
262 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
263
264 return dl, factory
265
266
Scott Zawalski4647ce62012-01-03 17:17:28 -0500267def _LeadingWhiteSpaceCount(string):
268 """Count the amount of leading whitespace in a string.
269
270 Args:
271 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800272
Scott Zawalski4647ce62012-01-03 17:17:28 -0500273 Returns:
274 number of white space chars before characters start.
275 """
Gabe Black3b567202015-09-23 14:07:59 -0700276 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500277 if matched:
278 return len(matched.group())
279
280 return 0
281
282
283def _PrintDocStringAsHTML(func):
284 """Make a functions docstring somewhat HTML style.
285
286 Args:
287 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800288
Scott Zawalski4647ce62012-01-03 17:17:28 -0500289 Returns:
290 A string that is somewhat formated for a web browser.
291 """
292 # TODO(scottz): Make this parse Args/Returns in a prettier way.
293 # Arguments could be bolded and indented etc.
294 html_doc = []
295 for line in func.__doc__.splitlines():
296 leading_space = _LeadingWhiteSpaceCount(line)
297 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700298 line = ' ' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500299
300 html_doc.append('<BR>%s' % line)
301
302 return '\n'.join(html_doc)
303
304
Simran Basief83d6a2014-08-28 14:32:01 -0700305def _GetUpdateTimestampHandler(static_dir):
306 """Returns a handler to update directory staged.timestamp.
307
308 This handler resets the stage.timestamp whenever static content is accessed.
309
310 Args:
311 static_dir: Directory from which static content is being staged.
312
313 Returns:
Amin Hassani08e42d22019-06-03 00:31:30 -0700314 A cherrypy handler to update the timestamp of accessed content.
Simran Basief83d6a2014-08-28 14:32:01 -0700315 """
316 def UpdateTimestampHandler():
317 if not '404' in cherrypy.response.status:
318 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
319 cherrypy.request.path_info)
320 if build_match:
321 build_dir = os.path.join(static_dir, build_match.group('build'))
322 downloader.Downloader.TouchTimestampForStaged(build_dir)
323 return UpdateTimestampHandler
324
325
Chris Sosa7c931362010-10-11 19:49:01 -0700326def _GetConfig(options):
327 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800328
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800329 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800330 # Fall back to IPv4 when python is not configured with IPv6.
331 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800332 socket_host = '0.0.0.0'
333
Simran Basief83d6a2014-08-28 14:32:01 -0700334 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
335 # on the on_end_resource hook. This hook is called once processing is
336 # complete and the response is ready to be returned.
337 cherrypy.tools.update_timestamp = cherrypy.Tool(
338 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
339
David Riley2fcb0122017-11-02 11:25:39 -0700340 base_config = {
341 'global': {
342 'server.log_request_headers': True,
343 'server.protocol_version': 'HTTP/1.1',
344 'server.socket_host': socket_host,
345 'server.socket_port': int(options.port),
346 'response.timeout': 6000,
347 'request.show_tracebacks': True,
348 'server.socket_timeout': 60,
349 'server.thread_pool': 2,
350 'engine.autoreload.on': False,
351 },
352 '/api': {
353 # Gets rid of cherrypy parsing post file for args.
354 'request.process_request_body': False,
355 },
356 '/build': {
357 'response.timeout': 100000,
358 },
359 '/update': {
360 # Gets rid of cherrypy parsing post file for args.
361 'request.process_request_body': False,
362 'response.timeout': 10000,
363 },
364 # Sets up the static dir for file hosting.
365 '/static': {
366 'tools.staticdir.dir': options.static_dir,
367 'tools.staticdir.on': True,
368 'response.timeout': 10000,
369 'tools.update_timestamp.on': True,
370 },
371 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700372 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700373 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500374
Chris Sosa7c931362010-10-11 19:49:01 -0700375 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000376
Darin Petkove17164a2010-08-11 13:24:41 -0700377
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700378def _GetRecursiveMemberObject(root, member_list):
379 """Returns an object corresponding to a nested member list.
380
381 Args:
382 root: the root object to search
383 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800384
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700385 Returns:
386 An object corresponding to the member name list; None otherwise.
387 """
388 for member in member_list:
389 next_root = root.__class__.__dict__.get(member)
390 if not next_root:
391 return None
392 root = next_root
393 return root
394
395
396def _IsExposed(name):
397 """Returns True iff |name| has an `exposed' attribute and it is set."""
398 return hasattr(name, 'exposed') and name.exposed
399
400
Congbin Guo6bc32182019-08-20 17:54:30 -0700401def _GetExposedMethod(nested_member):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700402 """Returns a CherryPy-exposed method, if such exists.
403
404 Args:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700405 nested_member: a slash-joined path to the nested member
Don Garrettf84631a2014-01-07 18:21:26 -0800406
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700407 Returns:
Congbin Guo6bc32182019-08-20 17:54:30 -0700408 A function object corresponding to the path defined by |nested_member| from
409 the app root object registered, if the function is exposed; None otherwise.
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700410 """
Congbin Guo6bc32182019-08-20 17:54:30 -0700411 for app in cherrypy.tree.apps.values():
412 # Use the 'index' function doc as the doc of the app.
413 if nested_member == app.script_name.lstrip('/'):
414 nested_member = 'index'
415
416 method = _GetRecursiveMemberObject(app.root, nested_member.split('/'))
417 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
418 return method
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700419
420
Gilad Arnold748c8322012-10-12 09:51:35 -0700421def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700422 """Finds exposed CherryPy methods.
423
424 Args:
425 root: the root object for searching
426 prefix: slash-joined chain of members leading to current object
427 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800428
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700429 Returns:
430 List of exposed URLs that are not unlisted.
431 """
432 method_list = []
Congbin Guo6bc32182019-08-20 17:54:30 -0700433 for member in root.__class__.__dict__.keys():
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700434 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700435 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700436 continue
437 member_obj = root.__class__.__dict__[member]
438 if _IsExposed(member_obj):
Amin Hassani08e42d22019-06-03 00:31:30 -0700439 if isinstance(member_obj, types.FunctionType):
Congbin Guo6bc32182019-08-20 17:54:30 -0700440 # Regard the app name as exposed "method" name if it exposed 'index'
441 # function.
442 if prefix and member == 'index':
443 method_list.append(prefix)
444 else:
445 method_list.append(prefixed_member)
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700446 else:
447 method_list += _FindExposedMethods(
448 member_obj, prefixed_member, unlisted)
449 return method_list
450
451
xixuan52c2fba2016-05-20 17:02:48 -0700452def _check_base_args_for_auto_update(kwargs):
xixuanac89ce82016-11-30 16:48:20 -0800453 """Check basic args required for auto-update.
454
455 Args:
456 kwargs: the parameters to be checked.
457
458 Raises:
459 DevServerHTTPError if required parameters don't exist in kwargs.
460 """
xixuan52c2fba2016-05-20 17:02:48 -0700461 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800462 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
463 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700464
465 if 'build_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800466 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
467 KEY_ERROR_MSG % 'build_name')
xixuan52c2fba2016-05-20 17:02:48 -0700468
469
470def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-11-30 16:48:20 -0800471 """Parse boolean arg from kwargs.
472
473 Args:
474 kwargs: the parameters to be checked.
475 key: the key to be parsed.
476
477 Returns:
478 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
479
480 Raises:
481 DevServerHTTPError if kwargs[key] is not a boolean variable.
482 """
xixuan52c2fba2016-05-20 17:02:48 -0700483 if key in kwargs:
484 if kwargs[key] == 'True':
485 return True
486 elif kwargs[key] == 'False':
487 return False
488 else:
Amin Hassani722e0962019-11-15 15:45:31 -0800489 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
490 'The value for key %s is not boolean.' % key)
xixuan52c2fba2016-05-20 17:02:48 -0700491 else:
492 return False
493
xixuan447ad9d2017-02-28 14:46:20 -0800494
xixuanac89ce82016-11-30 16:48:20 -0800495def _parse_string_arg(kwargs, key):
496 """Parse string arg from kwargs.
497
498 Args:
499 kwargs: the parameters to be checked.
500 key: the key to be parsed.
501
502 Returns:
503 The string value of kwargs[key], or None if key doesn't exist in kwargs.
504 """
505 if key in kwargs:
506 return kwargs[key]
507 else:
508 return None
509
xixuan447ad9d2017-02-28 14:46:20 -0800510
xixuanac89ce82016-11-30 16:48:20 -0800511def _build_uri_from_build_name(build_name):
512 """Get build url from a given build name.
513
514 Args:
515 build_name: the build name to be parsed, whose format is
516 'board/release_version'.
517
518 Returns:
519 The release_archive_url on Google Storage for this build name.
520 """
Amin Hassani08e42d22019-06-03 00:31:30 -0700521 # TODO(ahassani): This function doesn't seem to be used anywhere since its
522 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
523 # causing any runtime issues. So deprecate this in the future.
524 tokens = build_name.split('/')
525 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-20 17:02:48 -0700526
xixuan447ad9d2017-02-28 14:46:20 -0800527
528def _clear_process(host_name, pid):
529 """Clear AU process for given hostname and pid.
530
531 This clear includes:
532 1. kill process if it's alive.
533 2. delete the track status file of this process.
534 3. delete the executing log file of this process.
535
536 Args:
537 host_name: the host to execute auto-update.
538 pid: the background auto-update process id.
539 """
540 if cros_update_progress.IsProcessAlive(pid):
541 os.killpg(int(pid), signal.SIGKILL)
542
543 cros_update_progress.DelTrackStatusFile(host_name, pid)
544 cros_update_progress.DelExecuteLogFile(host_name, pid)
545
546
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800547def is_deprecated_server():
548 """Gets whether the devserver has deprecated RPCs."""
549 return cherrypy.config.get('infra_removal', False)
550
551
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700552class ApiRoot(object):
553 """RESTful API for Dev Server information."""
554 exposed = True
555
556 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800557 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700558 """Returns a JSON object containing a log of host event.
559
560 Args:
561 ip: address of host whose event log is requested, or `all'
Don Garrettf84631a2014-01-07 18:21:26 -0800562
Gilad Arnold1b908392012-10-05 11:36:27 -0700563 Returns:
Amin Hassani7c447852019-09-26 15:01:48 -0700564 A JSON dictionary containing all or some of the following fields:
Amin Hassanie7ead902019-10-11 16:42:43 -0700565 version: The Chromium OS version the device is running.
566 track: The channel the device is running on.
567 board: The device's board.
568 event_result: The event result of Omaha request.
569 event_type: The event type of Omaha request.
570 previous_version: The Chromium OS version we updated and rebooted from.
571 timestamp: The timestamp the event was received.
Amin Hassani7c447852019-09-26 15:01:48 -0700572 See the OmahaEvent class in update_engine/omaha_request_action.h for
573 event type and status code definitions. If the ip does not exist an empty
574 string is returned.
Gilad Arnold1b908392012-10-05 11:36:27 -0700575
576 Example URL:
577 http://myhost/api/hostlog?ip=192.168.1.5
578 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800579 return updater.HandleHostLogPing(ip)
580
581 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800582 def fileinfo(self, *args):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700583 """Returns information about a given staged file.
584
585 Args:
Don Garrettf84631a2014-01-07 18:21:26 -0800586 args: path to the file inside the server's static staging directory
587
Gilad Arnold55a2a372012-10-02 09:46:32 -0700588 Returns:
589 A JSON encoded dictionary with information about the said file, which may
590 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700591 size (int): the file size in bytes
Gilad Arnold1b908392012-10-05 11:36:27 -0700592 sha256 (string): a base64 encoded SHA256 hash
593
594 Example URL:
595 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700596 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800597 if is_deprecated_server():
598 raise DeprecatedRPCError('fileinfo')
599
Amin Hassani28df4212019-10-28 10:16:50 -0700600 # TODO(ahassani): A better way of doing this is to just return the the
601 # content of the payloads' property file instead. That has all this info
602 # except that the key for sha256 is 'sha256_hex', but still base64 encdoed.
603
Don Garrettf84631a2014-01-07 18:21:26 -0800604 file_path = os.path.join(updater.static_dir, *args)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700605 if not os.path.exists(file_path):
Amin Hassanid4e35392019-10-03 11:02:44 -0700606 raise DevServerError('file not found: %s' % file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700607 try:
608 file_size = os.path.getsize(file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700609 file_sha256 = common_util.GetFileSha256(file_path)
Amin Hassani469f5702019-10-21 15:35:06 -0700610 except os.error as e:
Amin Hassanid4e35392019-10-03 11:02:44 -0700611 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700612 'failed to get info for file %s: %s' % (file_path, e))
Gilad Arnolde74b3812013-04-22 11:27:38 -0700613
Gilad Arnolde74b3812013-04-22 11:27:38 -0700614 return json.dumps({
Amin Hassani28df4212019-10-28 10:16:50 -0700615 'size': file_size,
616 'sha256': file_sha256,
617 }, sort_keys=True)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700618
Chris Sosa76e44b92013-01-31 12:11:38 -0800619
David Rochberg7c79a812011-01-19 14:24:45 -0500620class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700621 """The Root Class for the Dev Server.
622
623 CherryPy works as follows:
624 For each method in this class, cherrpy interprets root/path
625 as a call to an instance of DevServerRoot->method_name. For example,
626 a call to http://myhost/build will call build. CherryPy automatically
627 parses http args and places them as keyword arguments in each method.
628 For paths http://myhost/update/dir1/dir2, you can use *args so that
629 cherrypy uses the update method and puts the extra paths in args.
630 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700631 # Method names that should not be listed on the index page.
632 _UNLISTED_METHODS = ['index', 'doc']
633
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700634 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700635
Dan Shi59ae7092013-06-04 14:37:27 -0700636 # Number of threads that devserver is staging images.
637 _staging_thread_count = 0
638 # Lock used to lock increasing/decreasing count.
639 _staging_thread_count_lock = threading.Lock()
640
joychen3cb228e2013-06-12 12:13:13 -0700641 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700642 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800643 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700644 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500645
Congbin Guo3afae6c2019-08-13 16:29:42 -0700646 @property
647 def staging_thread_count(self):
648 """Get the staging thread count."""
649 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700650
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700651 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500652 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700653 """Builds the package specified."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800654 if is_deprecated_server():
655 raise DeprecatedRPCError('build')
656
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700657 import builder
658 if self._builder is None:
659 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500660 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700661
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700662 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700663 def is_staged(self, **kwargs):
664 """Check if artifacts have been downloaded.
665
Congbin Guo3afae6c2019-08-13 16:29:42 -0700666 Examples:
667 To check if autotest and test_suites are staged:
668 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
669 artifacts=autotest,test_suites
670
Amin Hassani08e42d22019-06-03 00:31:30 -0700671 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700672 async: True to return without waiting for download to complete.
673 artifacts: Comma separated list of named artifacts to download.
674 These are defined in artifact_info and have their implementation
675 in build_artifact.py.
676 files: Comma separated list of file artifacts to stage. These
677 will be available as is in the corresponding static directory with no
678 custom post-processing.
679
Congbin Guo3afae6c2019-08-13 16:29:42 -0700680 Returns:
681 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700682 """
Gabe Black3b567202015-09-23 14:07:59 -0700683 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700684 response = str(dl.IsStaged(factory))
685 _Log('Responding to is_staged %s request with %r', kwargs, response)
686 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700687
Chris Sosa76e44b92013-01-31 12:11:38 -0800688 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800689 def list_image_dir(self, **kwargs):
690 """Take an archive url and list the contents in its staged directory.
691
Amin Hassani08e42d22019-06-03 00:31:30 -0700692 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800693 To list the contents of where this devserver should have staged
694 gs://image-archive/<board>-release/<build> call:
695 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
696
Congbin Guo3afae6c2019-08-13 16:29:42 -0700697 Args:
698 archive_url: Google Storage URL for the build.
699
Prashanth Ba06d2d22014-03-07 15:35:19 -0800700 Returns:
701 A string with information about the contents of the image directory.
702 """
Gabe Black3b567202015-09-23 14:07:59 -0700703 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800704 try:
Gabe Black3b567202015-09-23 14:07:59 -0700705 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800706 except build_artifact.ArtifactDownloadError as e:
707 return 'Cannot list the contents of staged artifacts. %s' % e
708 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700709 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800710 return image_dir_contents
711
712 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800713 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700714 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800715
Gabe Black3b567202015-09-23 14:07:59 -0700716 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700717 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700718 on the devserver. A call to this will attempt to cache non-specified
719 artifacts in the background for the given from the given URL following
720 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800721 artifacts is explicitly defined in the build_artifact module.
722
723 These artifacts will then be available from the static/ sub-directory of
724 the devserver.
725
Amin Hassani08e42d22019-06-03 00:31:30 -0700726 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800727 To download the autotest and test suites tarballs:
728 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
729 artifacts=autotest,test_suites
730 To download the full update payload:
731 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
732 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700733 To download just a file called blah.bin:
734 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
735 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800736
737 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700738 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800739
740 Note for this example, relative path is the archive_url stripped of its
741 basename i.e. path/ in the examples above. Specific example:
742
743 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
744
745 Will get staged to:
746
joychened64b222013-06-21 16:39:34 -0700747 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700748
749 Args:
750 archive_url: Google Storage URL for the build.
751 local_path: Local path for the build.
752 delete_source: Only meaningful with local_path. bool to indicate if the
753 source files should be deleted. This is especially useful when staging
754 a file locally in resource constrained environments as it allows us to
755 move the relevant files locally instead of copying them.
756 async: True to return without waiting for download to complete.
757 artifacts: Comma separated list of named artifacts to download.
758 These are defined in artifact_info and have their implementation
759 in build_artifact.py.
760 files: Comma separated list of files to stage. These
761 will be available as is in the corresponding static directory with no
762 custom post-processing.
763 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800764 """
Gabe Black3b567202015-09-23 14:07:59 -0700765 dl, factory = _get_downloader_and_factory(kwargs)
766
Dan Shi59ae7092013-06-04 14:37:27 -0700767 with DevServerRoot._staging_thread_count_lock:
768 DevServerRoot._staging_thread_count += 1
769 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800770 boolean_string = kwargs.get('clean')
771 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
772 if clean and os.path.exists(dl.GetBuildDir()):
773 _Log('Removing %s' % dl.GetBuildDir())
774 shutil.rmtree(dl.GetBuildDir())
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700775 is_async = kwargs.get('async', False)
776 dl.Download(factory, is_async=is_async)
Dan Shi59ae7092013-06-04 14:37:27 -0700777 finally:
778 with DevServerRoot._staging_thread_count_lock:
779 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800780 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700781
782 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700783 def cros_au(self, **kwargs):
784 """Auto-update a CrOS DUT.
785
786 Args:
787 kwargs:
788 host_name: the hostname of the DUT to auto-update.
789 build_name: the build name for update the DUT.
790 force_update: Force an update even if the version installed is the
791 same. Default: False.
792 full_update: If True, do not run stateful update, directly force a full
793 reimage. If False, try stateful update first if the dut is already
794 installed with the same version.
795 async: Whether the auto_update function is ran in the background.
David Rileyee75de22017-11-02 10:48:15 -0700796 quick_provision: Whether the quick provision path is attempted first.
xixuan52c2fba2016-05-20 17:02:48 -0700797
798 Returns:
799 A tuple includes two elements:
800 a boolean variable represents whether the auto-update process is
801 successfully started.
802 an integer represents the background auto-update process id.
803 """
804 _check_base_args_for_auto_update(kwargs)
805
806 host_name = kwargs['host_name']
807 build_name = kwargs['build_name']
808 force_update = _parse_boolean_arg(kwargs, 'force_update')
809 full_update = _parse_boolean_arg(kwargs, 'full_update')
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700810 is_async = _parse_boolean_arg(kwargs, 'async')
xixuanac89ce82016-11-30 16:48:20 -0800811 original_build = _parse_string_arg(kwargs, 'original_build')
David Haddock90e49442017-04-07 19:14:09 -0700812 payload_filename = _parse_string_arg(kwargs, 'payload_filename')
David Haddock20559612017-06-28 22:15:08 -0700813 clobber_stateful = _parse_boolean_arg(kwargs, 'clobber_stateful')
David Rileyee75de22017-11-02 10:48:15 -0700814 quick_provision = _parse_boolean_arg(kwargs, 'quick_provision')
815
816 devserver_url = updater.GetDevserverUrl()
817 static_url = updater.GetStaticUrl()
xixuan52c2fba2016-05-20 17:02:48 -0700818
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700819 if is_async:
Allen Li1dff8612020-03-04 20:21:17 +0000820 # Command of running auto-update.
821 cmd = ['cros_update', '--hostname', host_name, '-b', build_name,
822 '--static_dir', updater.static_dir]
823
824 # The original_build's format is like: link/3428.210.0
825 # The corresponding release_archive_url's format is like:
826 # gs://chromeos-releases/stable-channel/link/3428.210.0
827 if original_build:
828 release_archive_url = _build_uri_from_build_name(original_build)
829 # First staging the stateful.tgz synchronousely.
830 self.stage(files='stateful.tgz', is_async=False,
831 archive_url=release_archive_url)
832 cmd += ['--original_build', original_build]
833
834 if force_update:
835 cmd += ['--force_update']
836
837 if full_update:
838 cmd += ['--full_update']
839
840 if payload_filename:
841 cmd += ['--payload_filename', payload_filename]
842
843 if clobber_stateful:
844 cmd += ['--clobber_stateful']
845
846 if quick_provision:
847 cmd += ['--quick_provision']
848
849 if devserver_url:
850 cmd += ['--devserver_url', devserver_url]
851
852 if static_url:
853 cmd += ['--static_url', static_url]
854
Amin Hassani78520ae2019-10-29 13:26:51 -0700855 p = subprocess.Popen(cmd, preexec_fn=os.setsid)
xixuan2a0970a2016-08-10 12:12:44 -0700856 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-20 17:02:48 -0700857
858 # Pre-write status in the track_status_file before the first call of
859 # 'get_au_status' to make sure that the track_status_file exists.
xixuan2a0970a2016-08-10 12:12:44 -0700860 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700861 progress_tracker.WriteStatus('CrOS update is just started.')
862
xixuan2a0970a2016-08-10 12:12:44 -0700863 return json.dumps((True, pid))
xixuan52c2fba2016-05-20 17:02:48 -0700864 else:
Allen Li1dff8612020-03-04 20:21:17 +0000865 cros_update_trigger = cros_update.CrOSUpdateTrigger(
866 host_name, build_name, updater.static_dir, force_update=force_update,
867 full_update=full_update, original_build=original_build,
868 payload_filename=payload_filename, quick_provision=quick_provision,
869 devserver_url=devserver_url, static_url=static_url)
870 cros_update_trigger.TriggerAU()
xixuan27d50442017-08-09 10:38:25 -0700871 return json.dumps((True, -1))
xixuan52c2fba2016-05-20 17:02:48 -0700872
873 @cherrypy.expose
874 def get_au_status(self, **kwargs):
875 """Check if the auto-update task is finished.
876
877 It handles 4 cases:
878 1. If an error exists in the track_status_file, delete the track file and
879 raise it.
880 2. If cros-update process is finished, delete the file and return the
881 success result.
882 3. If the process is not running, delete the track file and raise an error
883 about 'the process is terminated due to unknown reason'.
884 4. If the track_status_file does not exist, kill the process if it exists,
885 and raise the IOError.
886
887 Args:
888 kwargs:
889 host_name: the hostname of the DUT to auto-update.
890 pid: the background process id of cros-update.
891
892 Returns:
xixuan28d99072016-10-06 12:24:16 -0700893 A dict with three elements:
xixuan52c2fba2016-05-20 17:02:48 -0700894 a boolean variable represents whether the auto-update process is
895 finished.
896 a string represents the current auto-update process status.
897 For example, 'Transfer Devserver/Stateful Update Package'.
xixuan28d99072016-10-06 12:24:16 -0700898 a detailed error message paragraph if there exists an Auto-Update
899 error, in which the last line shows the main exception. Empty
900 string otherwise.
xixuan52c2fba2016-05-20 17:02:48 -0700901 """
902 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800903 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
904 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700905
906 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800907 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
908 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700909
910 host_name = kwargs['host_name']
911 pid = kwargs['pid']
912 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
913
xixuan28d99072016-10-06 12:24:16 -0700914 result_dict = {'finished': False, 'status': '', 'detailed_error_msg': ''}
xixuan52c2fba2016-05-20 17:02:48 -0700915 try:
916 result = progress_tracker.ReadStatus()
917 if result.startswith(cros_update_progress.ERROR_TAG):
xixuan28d99072016-10-06 12:24:16 -0700918 result_dict['detailed_error_msg'] = result[len(
919 cros_update_progress.ERROR_TAG):]
xixuan28681fd2016-11-23 11:13:56 -0800920 elif result == cros_update_progress.FINISHED:
xixuan28d99072016-10-06 12:24:16 -0700921 result_dict['finished'] = True
922 result_dict['status'] = result
xixuan28681fd2016-11-23 11:13:56 -0800923 elif not cros_update_progress.IsProcessAlive(pid):
xixuan28d99072016-10-06 12:24:16 -0700924 result_dict['detailed_error_msg'] = (
925 'Cros_update process terminated midway due to unknown reason. '
926 'Last update status was %s' % result)
xixuan28681fd2016-11-23 11:13:56 -0800927 else:
928 result_dict['status'] = result
929 except IOError as e:
930 if pid and cros_update_progress.IsProcessAlive(pid):
xixuan2a0970a2016-08-10 12:12:44 -0700931 os.killpg(int(pid), signal.SIGKILL)
xixuan52c2fba2016-05-20 17:02:48 -0700932
xixuan28681fd2016-11-23 11:13:56 -0800933 result_dict['detailed_error_msg'] = str(e)
934
935 return json.dumps(result_dict)
xixuan52c2fba2016-05-20 17:02:48 -0700936
937 @cherrypy.expose
David Riley6d5fca02017-10-31 10:35:47 -0700938 def post_au_status(self, status, **kwargs):
939 """Updates the status of an auto-update task.
940
941 Callers will need to POST to this URL with a body of MIME-type
942 "multipart/form-data".
943 The body should include a single argument, 'status', containing the
944 AU status to record.
945
946 Args:
947 status: The updated status.
948 kwargs:
949 host_name: the hostname of the DUT to auto-update.
950 pid: the background process id of cros-update.
951 """
952 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800953 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
954 KEY_ERROR_MSG % 'host_name')
David Riley6d5fca02017-10-31 10:35:47 -0700955
956 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800957 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
958 KEY_ERROR_MSG % 'pid')
David Riley6d5fca02017-10-31 10:35:47 -0700959
960 host_name = kwargs['host_name']
961 pid = kwargs['pid']
David Riley3cea2582017-11-24 22:03:01 -0800962 status = status.rstrip()
963 _Log('Recording status for %s (%s): %s' % (host_name, pid, status))
David Riley6d5fca02017-10-31 10:35:47 -0700964 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
965
David Riley3cea2582017-11-24 22:03:01 -0800966 progress_tracker.WriteStatus(status)
David Riley6d5fca02017-10-31 10:35:47 -0700967
968 return 'True'
969
970 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700971 def handler_cleanup(self, **kwargs):
xixuan3bc974e2016-10-18 17:21:43 -0700972 """Clean track status log and temp directory for CrOS auto-update process.
xixuan52c2fba2016-05-20 17:02:48 -0700973
974 Args:
975 kwargs:
976 host_name: the hostname of the DUT to auto-update.
977 pid: the background process id of cros-update.
978 """
979 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800980 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
981 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700982
983 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800984 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
985 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700986
987 host_name = kwargs['host_name']
988 pid = kwargs['pid']
989 cros_update_progress.DelTrackStatusFile(host_name, pid)
xixuan3bc974e2016-10-18 17:21:43 -0700990 cros_update_progress.DelAUTempDirectory(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700991
992 @cherrypy.expose
993 def kill_au_proc(self, **kwargs):
994 """Kill CrOS auto-update process using given process id.
995
996 Args:
997 kwargs:
998 host_name: Kill all the CrOS auto-update process of this host.
999
1000 Returns:
1001 True if all processes are killed properly.
1002 """
1003 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001004 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1005 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001006
xixuan447ad9d2017-02-28 14:46:20 -08001007 cur_pid = kwargs.get('pid')
1008
xixuan52c2fba2016-05-20 17:02:48 -07001009 host_name = kwargs['host_name']
xixuan3bc974e2016-10-18 17:21:43 -07001010 track_log_list = cros_update_progress.GetAllTrackStatusFileByHostName(
1011 host_name)
xixuan52c2fba2016-05-20 17:02:48 -07001012 for log in track_log_list:
1013 # The track log's full path is: path/host_name_pid.log
1014 # Use splitext to remove file extension, then parse pid from the
1015 # filename.
Congbin Guo3afae6c2019-08-13 16:29:42 -07001016 pid = os.path.splitext(os.path.basename(log))[0][len(host_name) + 1:]
xixuan447ad9d2017-02-28 14:46:20 -08001017 _clear_process(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -07001018
xixuan447ad9d2017-02-28 14:46:20 -08001019 if cur_pid:
1020 _clear_process(host_name, cur_pid)
xixuan52c2fba2016-05-20 17:02:48 -07001021
1022 return 'True'
1023
1024 @cherrypy.expose
1025 def collect_cros_au_log(self, **kwargs):
1026 """Collect CrOS auto-update log.
1027
1028 Args:
1029 kwargs:
1030 host_name: the hostname of the DUT to auto-update.
1031 pid: the background process id of cros-update.
1032
1033 Returns:
David Haddock9f459632017-05-11 14:45:46 -07001034 A dictionary containing the execute log file and any hostlog files.
xixuan52c2fba2016-05-20 17:02:48 -07001035 """
1036 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001037 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1038 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001039
1040 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001041 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1042 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -07001043
1044 host_name = kwargs['host_name']
1045 pid = kwargs['pid']
xixuan3bc974e2016-10-18 17:21:43 -07001046
1047 # Fetch the execute log recorded by cros_update_progress.
xixuan1bbfaba2016-10-13 17:53:22 -07001048 au_log = cros_update_progress.ReadExecuteLogFile(host_name, pid)
1049 cros_update_progress.DelExecuteLogFile(host_name, pid)
David Haddock9f459632017-05-11 14:45:46 -07001050 # Fetch the cros_au host_logs if they exist
1051 au_hostlogs = cros_update_progress.ReadAUHostLogFiles(host_name, pid)
1052 return json.dumps({'cros_au_log': au_log, 'host_logs': au_hostlogs})
xixuan1bbfaba2016-10-13 17:53:22 -07001053
xixuan52c2fba2016-05-20 17:02:48 -07001054 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -08001055 def locate_file(self, **kwargs):
1056 """Get the path to the given file name.
1057
1058 This method looks up the given file name inside specified build artifacts.
1059 One use case is to help caller to locate an apk file inside a build
1060 artifact. The location of the apk file could be different based on the
1061 branch and target.
1062
1063 Args:
1064 file_name: Name of the file to look for.
1065 artifacts: A list of artifact names to search for the file.
1066
1067 Returns:
1068 Path to the file with the given name. It's relative to the folder for the
1069 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -08001070 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001071 if is_deprecated_server():
1072 raise DeprecatedRPCError('locate_file')
1073
Dan Shi2f136862016-02-11 15:38:38 -08001074 dl, _ = _get_downloader_and_factory(kwargs)
1075 try:
Joe Brennan1691f8e2017-03-15 15:53:36 -07001076 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 15:38:38 -08001077 artifacts = kwargs['artifacts']
1078 except KeyError:
Amin Hassanid4e35392019-10-03 11:02:44 -07001079 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001080 '`file_name` and `artifacts` are required to search '
1081 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 15:38:38 -08001082 build_path = dl.GetBuildDir()
1083 for artifact in artifacts:
1084 # Get the unzipped folder of the artifact. If it's not defined in
1085 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
1086 # directory directly.
1087 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
1088 artifact_path = os.path.join(build_path, folder)
1089 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 15:53:36 -07001090 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 15:38:38 -08001091 return os.path.relpath(os.path.join(root, file_name), build_path)
Amin Hassanid4e35392019-10-03 11:02:44 -07001092 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001093 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 15:38:38 -08001094
1095 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -08001096 def setup_telemetry(self, **kwargs):
1097 """Extracts and sets up telemetry
1098
1099 This method goes through the telemetry deps packages, and stages them on
1100 the devserver to be used by the drones and the telemetry tests.
1101
1102 Args:
1103 archive_url: Google Storage URL for the build.
1104
1105 Returns:
1106 Path to the source folder for the telemetry codebase once it is staged.
1107 """
Gabe Black3b567202015-09-23 14:07:59 -07001108 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 13:39:18 -08001109
Gabe Black3b567202015-09-23 14:07:59 -07001110 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -08001111 deps_path = os.path.join(build_path, 'autotest/packages')
1112 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
1113 src_folder = os.path.join(telemetry_path, 'src')
1114
1115 with self._telemetry_lock_dict.lock(telemetry_path):
1116 if os.path.exists(src_folder):
1117 # Telemetry is already fully stage return
1118 return src_folder
1119
1120 common_util.MkDirP(telemetry_path)
1121
1122 # Copy over the required deps tar balls to the telemetry directory.
1123 for dep in TELEMETRY_DEPS:
1124 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -07001125 if not os.path.exists(dep_path):
1126 # This dep does not exist (could be new), do not extract it.
1127 continue
Simran Basi4baad082013-02-14 13:39:18 -08001128 try:
1129 common_util.ExtractTarball(dep_path, telemetry_path)
1130 except common_util.CommonUtilError as e:
1131 shutil.rmtree(telemetry_path)
Amin Hassanid4e35392019-10-03 11:02:44 -07001132 raise DevServerError(str(e))
Simran Basi4baad082013-02-14 13:39:18 -08001133
1134 # By default all the tarballs extract to test_src but some parts of
1135 # the telemetry code specifically hardcoded to exist inside of 'src'.
1136 test_src = os.path.join(telemetry_path, 'test_src')
1137 try:
1138 shutil.move(test_src, src_folder)
1139 except shutil.Error:
1140 # This can occur if src_folder already exists. Remove and retry move.
1141 shutil.rmtree(src_folder)
Amin Hassanid4e35392019-10-03 11:02:44 -07001142 raise DevServerError(
Gabe Black3b567202015-09-23 14:07:59 -07001143 'Failure in telemetry setup for build %s. Appears that the '
1144 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -08001145
1146 return src_folder
1147
1148 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -08001149 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -07001150 """Symbolicates a minidump using pre-downloaded symbols, returns it.
1151
1152 Callers will need to POST to this URL with a body of MIME-type
1153 "multipart/form-data".
1154 The body should include a single argument, 'minidump', containing the
1155 binary-formatted minidump to symbolicate.
1156
Chris Masone816e38c2012-05-02 12:22:36 -07001157 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -08001158 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -07001159 minidump: The binary minidump file to symbolicate.
1160 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001161 if is_deprecated_server():
1162 raise DeprecatedRPCError('symbolicate_dump')
1163
Chris Sosa76e44b92013-01-31 12:11:38 -08001164 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -07001165 # Try debug.tar.xz first, then debug.tgz
1166 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
1167 kwargs['artifacts'] = artifact
1168 dl = _get_downloader(kwargs)
1169
1170 try:
1171 if self.stage(**kwargs) == 'Success':
1172 break
1173 except build_artifact.ArtifactDownloadError:
1174 continue
1175 else:
Amin Hassanid4e35392019-10-03 11:02:44 -07001176 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001177 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -08001178
Chris Masone816e38c2012-05-02 12:22:36 -07001179 to_return = ''
1180 with tempfile.NamedTemporaryFile() as local:
1181 while True:
1182 data = minidump.file.read(8192)
1183 if not data:
1184 break
1185 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -08001186
Chris Masone816e38c2012-05-02 12:22:36 -07001187 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -08001188
Gabe Black3b567202015-09-23 14:07:59 -07001189 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -08001190
xixuanab744382017-04-27 10:41:27 -07001191 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 12:11:38 -08001192 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 10:41:27 -07001193 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 12:11:38 -08001194 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1195
Chris Masone816e38c2012-05-02 12:22:36 -07001196 to_return, error_text = stackwalk.communicate()
1197 if stackwalk.returncode != 0:
Amin Hassanid4e35392019-10-03 11:02:44 -07001198 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001199 "Can't generate stack trace: %s (rc=%d)" % (error_text,
1200 stackwalk.returncode))
Chris Masone816e38c2012-05-02 12:22:36 -07001201
1202 return to_return
1203
1204 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001205 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -04001206 """Return a string representing the latest build for a given target.
1207
1208 Args:
1209 target: The build target, typically a combination of the board and the
1210 type of build e.g. x86-mario-release.
1211 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
1212 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -08001213
Scott Zawalski16954532012-03-20 15:31:36 -04001214 Returns:
1215 A string representation of the latest build if one exists, i.e.
1216 R19-1993.0.0-a1-b1480.
1217 An empty string if no latest could be found.
1218 """
Don Garrettf84631a2014-01-07 18:21:26 -08001219 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -04001220 return _PrintDocStringAsHTML(self.latestbuild)
1221
Don Garrettf84631a2014-01-07 18:21:26 -08001222 if 'target' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001223 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1224 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -07001225
1226 if _is_android_build_request(kwargs):
1227 branch = kwargs.get('branch', None)
1228 target = kwargs.get('target', None)
1229 if not target or not branch:
Amin Hassanid4e35392019-10-03 11:02:44 -07001230 raise DevServerError('Both target and branch must be specified to query'
1231 ' for the latest Android build.')
Dan Shi61305df2015-10-26 16:52:35 -07001232 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
1233
Scott Zawalski16954532012-03-20 15:31:36 -04001234 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001235 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-07 18:21:26 -08001236 updater.static_dir, kwargs['target'],
1237 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -07001238 except common_util.CommonUtilError as errmsg:
Amin Hassani722e0962019-11-15 15:45:31 -08001239 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1240 str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -04001241
1242 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -07001243 def list_suite_controls(self, **kwargs):
1244 """Return a list of contents of all known control files.
1245
1246 Example URL:
1247 To List all control files' content:
1248 http://dev-server/list_suite_controls?suite_name=bvt&
1249 build=daisy_spring-release/R29-4279.0.0
1250
1251 Args:
1252 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
1253 suite_name: List the control files belonging to that suite.
1254
1255 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -07001256 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -07001257 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001258 if is_deprecated_server():
1259 raise DeprecatedRPCError('list_suite_controls')
1260
xixuan7efd0002016-04-14 15:34:01 -07001261 if not kwargs:
1262 return _PrintDocStringAsHTML(self.controlfiles)
1263
1264 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001265 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1266 'Error: build= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001267
1268 if 'suite_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001269 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1270 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001271
1272 control_file_list = [
1273 line.rstrip() for line in common_util.GetControlFileListForSuite(
1274 updater.static_dir, kwargs['build'],
1275 kwargs['suite_name']).splitlines()]
1276
Dan Shia1cd6522016-04-18 16:07:21 -07001277 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -07001278 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -07001279 control_file_content_dict[control_path] = (common_util.GetControlFile(
xixuan7efd0002016-04-14 15:34:01 -07001280 updater.static_dir, kwargs['build'], control_path))
1281
Dan Shia1cd6522016-04-18 16:07:21 -07001282 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -07001283
1284 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001285 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -05001286 """Return a control file or a list of all known control files.
1287
1288 Example URL:
1289 To List all control files:
beepsbd337242013-07-09 22:44:06 -07001290 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
1291 To List all control files for, say, the bvt suite:
1292 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -05001293 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001294 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 -05001295
1296 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001297 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -05001298 control_path: If you want the contents of a control file set this
1299 to the path. E.g. client/site_tests/sleeptest/control
1300 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -07001301 suite_name: If control_path is not specified but a suite_name is
1302 specified, list the control files belonging to that suite instead of
1303 all control files. The empty string for suite_name will list all control
1304 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -08001305
Scott Zawalski4647ce62012-01-03 17:17:28 -05001306 Returns:
1307 Contents of a control file if control_path is provided.
1308 A list of control files if no control_path is provided.
1309 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001310 if is_deprecated_server():
1311 raise DeprecatedRPCError('controlfiles')
1312
Don Garrettf84631a2014-01-07 18:21:26 -08001313 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -05001314 return _PrintDocStringAsHTML(self.controlfiles)
1315
Don Garrettf84631a2014-01-07 18:21:26 -08001316 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001317 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1318 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -05001319
Don Garrettf84631a2014-01-07 18:21:26 -08001320 if 'control_path' not in kwargs:
1321 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -07001322 return common_util.GetControlFileListForSuite(
Don Garrettf84631a2014-01-07 18:21:26 -08001323 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -07001324 else:
1325 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-07 18:21:26 -08001326 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -05001327 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001328 return common_util.GetControlFile(
Don Garrettf84631a2014-01-07 18:21:26 -08001329 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -08001330
1331 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -07001332 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001333 """Translates an xBuddy path to a real path to artifact if it exists.
1334
1335 Args:
Simran Basi99e63c02014-05-20 10:39:52 -07001336 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
1337 Local searches the devserver's static directory. Remote searches a
1338 Google Storage image archive.
1339
1340 Kwargs:
1341 image_dir: Google Storage image archive to search in if requesting a
1342 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001343
1344 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -07001345 String in the format of build_id/artifact as stored on the local server
1346 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001347 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001348 if is_deprecated_server():
1349 raise DeprecatedRPCError('xbuddy_translate')
1350
Simran Basi99e63c02014-05-20 10:39:52 -07001351 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -07001352 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001353 response = os.path.join(build_id, filename)
1354 _Log('Path translation requested, returning: %s', response)
1355 return response
1356
1357 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -07001358 def xbuddy(self, *args, **kwargs):
1359 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -07001360
1361 Args:
joycheneaf4cfc2013-07-02 08:38:57 -07001362 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -07001363 components of the path. The path can be understood as
1364 "{local|remote}/build_id/artifact" where build_id is composed of
1365 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -07001366
joychen121fc9b2013-08-02 14:30:30 -07001367 The first path element is optional, and can be "remote" or "local"
1368 If local (the default), devserver will not attempt to access Google
1369 Storage, and will only search the static directory for the files.
1370 If remote, devserver will try to obtain the artifact off GS if it's
1371 not found locally.
1372 The board is the familiar board name, optionally suffixed.
1373 The version can be the google storage version number, and may also be
1374 any of a number of xBuddy defined version aliases that will be
1375 translated into the latest built image that fits the description.
1376 Defaults to latest.
1377 The artifact is one of a number of image or artifact aliases used by
1378 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -07001379
1380 Kwargs:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001381 for_update: {true|false}
Amin Hassanie9ffb862019-09-25 17:10:40 -07001382 if true, prepares the update payloads for the image,
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001383 and returns the update uri to pass to the
1384 update_engine_client.
joychen3cb228e2013-06-12 12:13:13 -07001385 return_dir: {true|false}
1386 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001387 relative_path: {true|false}
1388 if set to true, returns the relative path to the payload
1389 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -07001390 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -07001391 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -07001392 or
joycheneaf4cfc2013-07-02 08:38:57 -07001393 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -07001394
1395 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001396 If |for_update|, returns a redirect to the image or update file
1397 on the devserver. E.g.,
1398 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
1399 chromium-test-image.bin
1400 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1401 http://host:port/static/x86-generic-release/R26-4000.0.0/
1402 If |relative_path| is true, return a relative path the folder where the
1403 payloads are. E.g.,
1404 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -07001405 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001406 if is_deprecated_server():
1407 raise DeprecatedRPCError('xbuddy')
1408
Chris Sosa75490802013-09-30 17:21:45 -07001409 boolean_string = kwargs.get('for_update')
1410 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001411 boolean_string = kwargs.get('return_dir')
1412 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1413 boolean_string = kwargs.get('relative_path')
1414 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001415
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001416 if return_dir and relative_path:
Amin Hassani722e0962019-11-15 15:45:31 -08001417 raise DevServerHTTPError(
Amin Hassanid4e35392019-10-03 11:02:44 -07001418 http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001419 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001420
1421 # For updates, we optimize downloading of test images.
1422 file_name = None
1423 build_id = None
1424 if for_update:
1425 try:
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001426 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-09-30 17:21:45 -07001427 except build_artifact.ArtifactDownloadError:
1428 build_id = None
1429
1430 if not build_id:
1431 build_id, file_name = self._xbuddy.Get(args)
1432
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001433 if for_update:
Amin Hassanie9ffb862019-09-25 17:10:40 -07001434 _Log('Payloads requested.')
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001435 # Forces payload to be in cache and symlinked into build_id dir.
Amin Hassanie9ffb862019-09-25 17:10:40 -07001436 updater.GetUpdateForLabel(build_id)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001437
1438 response = None
1439 if return_dir:
1440 response = os.path.join(cherrypy.request.base, 'static', build_id)
1441 _Log('Directory requested, returning: %s', response)
1442 elif relative_path:
1443 response = build_id
1444 _Log('Relative path requested, returning: %s', response)
1445 elif for_update:
1446 response = os.path.join(cherrypy.request.base, 'update', build_id)
1447 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001448 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001449 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001450 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001451 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001452 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001453
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001454 return response
1455
joychen3cb228e2013-06-12 12:13:13 -07001456 @cherrypy.expose
1457 def xbuddy_list(self):
1458 """Lists the currently available images & time since last access.
1459
Gilad Arnold452fd272014-02-04 11:09:28 -08001460 Returns:
1461 A string representation of a list of tuples [(build_id, time since last
1462 access),...]
joychen3cb228e2013-06-12 12:13:13 -07001463 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001464 if is_deprecated_server():
1465 raise DeprecatedRPCError('xbuddy')
1466
joychen3cb228e2013-06-12 12:13:13 -07001467 return self._xbuddy.List()
1468
1469 @cherrypy.expose
1470 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001471 """Returns the number of images cached by xBuddy."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001472 if is_deprecated_server():
1473 raise DeprecatedRPCError('xbuddy_capacity')
1474
joychen3cb228e2013-06-12 12:13:13 -07001475 return self._xbuddy.Capacity()
1476
1477 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001478 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001479 """Presents a welcome message and documentation links."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001480 if is_deprecated_server():
1481 raise DeprecatedRPCError('index')
1482
Congbin Guo6bc32182019-08-20 17:54:30 -07001483 html_template = (
1484 'Welcome to the Dev Server!<br>\n'
1485 '<br>\n'
1486 'Here are the available methods, click for documentation:<br>\n'
1487 '<br>\n'
1488 '%s')
1489
1490 exposed_methods = []
1491 for app in cherrypy.tree.apps.values():
1492 exposed_methods += _FindExposedMethods(
1493 app.root, app.script_name.lstrip('/'),
1494 unlisted=self._UNLISTED_METHODS)
1495
1496 return html_template % '<br>\n'.join(
1497 ['<a href=doc/%s>%s</a>' % (name, name)
1498 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001499
1500 @cherrypy.expose
1501 def doc(self, *args):
1502 """Shows the documentation for available methods / URLs.
1503
Amin Hassani08e42d22019-06-03 00:31:30 -07001504 Examples:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001505 http://myhost/doc/update
1506 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001507 if is_deprecated_server():
1508 raise DeprecatedRPCError('doc')
1509
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001510 name = '/'.join(args)
Congbin Guo6bc32182019-08-20 17:54:30 -07001511 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001512 if not method:
Amin Hassanid4e35392019-10-03 11:02:44 -07001513 raise DevServerError("No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001514 if not method.__doc__:
Amin Hassanid4e35392019-10-03 11:02:44 -07001515 raise DevServerError("No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001516 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001517
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07001518 @cherrypy.expose
Amin Hassani6eec8792020-01-09 14:06:48 -08001519 def update(self, *args, **kwargs):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001520 """Handles an update check from a Chrome OS client.
1521
1522 The HTTP request should contain the standard Omaha-style XML blob. The URL
1523 line may contain an additional intermediate path to the update payload.
1524
joychen121fc9b2013-08-02 14:30:30 -07001525 This request can be handled in one of 4 ways, depending on the devsever
1526 settings and intermediate path.
joychenb0dfe552013-07-30 10:02:06 -07001527
Amin Hassanie9ffb862019-09-25 17:10:40 -07001528 1. No intermediate path. DEPRECATED
joychen121fc9b2013-08-02 14:30:30 -07001529
1530 2. Path explicitly invokes XBuddy
1531 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1532 with 'xbuddy'. This path is then used to acquire an image binary for the
1533 devserver to generate an update payload from. Devserver then serves this
1534 payload.
1535
1536 3. Path is left for the devserver to interpret.
1537 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1538 to generate a payload from the test image in that directory and serve it.
1539
joychen121fc9b2013-08-02 14:30:30 -07001540 Examples:
joychen121fc9b2013-08-02 14:30:30 -07001541 2. Explicitly invoke xbuddy
1542 update_engine_client --omaha_url=
1543 http://myhost/update/xbuddy/remote/board/version/dev
1544 This would go to GS to download the dev image for the board, from which
1545 the devserver would generate a payload to serve.
1546
1547 3. Give a path for devserver to interpret
1548 update_engine_client --omaha_url=http://myhost/update/some/random/path
1549 This would attempt, in order to:
1550 a) Generate an update from a test image binary if found in
1551 static_dir/some/random/path.
1552 b) Serve an update payload found in static_dir/some/random/path.
1553 c) Hope that some/random/path takes the form "board/version" and
1554 and attempt to download an update payload for that board/version
1555 from GS.
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001556 """
joychen121fc9b2013-08-02 14:30:30 -07001557 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -08001558 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -07001559 data = cherrypy.request.rfile.read(body_length)
Amin Hassani6aa075c2020-02-21 18:36:44 +00001560
Amin Hassani6eec8792020-01-09 14:06:48 -08001561 return updater.HandleUpdatePing(data, label, **kwargs)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001562
Dan Shif5ce2de2013-04-25 16:06:32 -07001563
Chris Sosadbc20082012-12-10 13:39:11 -08001564def _CleanCache(cache_dir, wipe):
1565 """Wipes any excess cached items in the cache_dir.
1566
1567 Args:
1568 cache_dir: the directory we are wiping from.
1569 wipe: If True, wipe all the contents -- not just the excess.
1570 """
1571 if wipe:
1572 # Clear the cache and exit on error.
1573 cmd = 'rm -rf %s/*' % cache_dir
1574 if os.system(cmd) != 0:
1575 _Log('Failed to clear the cache with %s' % cmd)
1576 sys.exit(1)
1577 else:
1578 # Clear all but the last N cached updates
1579 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1580 (cache_dir, CACHED_ENTRIES))
1581 if os.system(cmd) != 0:
1582 _Log('Failed to clean up old delta cache files with %s' % cmd)
1583 sys.exit(1)
1584
1585
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001586def _AddTestingOptions(parser):
1587 group = optparse.OptionGroup(
1588 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1589 'developers writing integration tests utilizing the devserver. They are '
1590 'not intended to be really used outside the scope of someone '
1591 'knowledgable about the test.')
1592 group.add_option('--exit',
1593 action='store_true',
Amin Hassanie9ffb862019-09-25 17:10:40 -07001594 help='do not start the server (yet clear cache)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001595 group.add_option('--host_log',
1596 action='store_true', default=False,
1597 help='record history of host update events (/api/hostlog)')
1598 group.add_option('--max_updates',
Gabe Black3b567202015-09-23 14:07:59 -07001599 metavar='NUM', default=-1, type='int',
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001600 help='maximum number of update checks handled positively '
1601 '(default: unlimited)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001602 group.add_option('--proxy_port',
1603 metavar='PORT', default=None, type='int',
1604 help='port to have the client connect to -- basically the '
1605 'devserver lies to the update to tell it to get the payload '
1606 'from a different port that will proxy the request back to '
1607 'the devserver. The proxy must be managed outside the '
1608 'devserver.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001609 parser.add_option_group(group)
1610
1611
1612def _AddUpdateOptions(parser):
1613 group = optparse.OptionGroup(
1614 parser, 'Autoupdate Options', 'These options can be used to change '
Amin Hassanie9ffb862019-09-25 17:10:40 -07001615 'how the devserver serve update payloads. Please '
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001616 'note that all of these option affect how a payload is generated and so '
1617 'do not work in archive-only mode.')
Amin Hassani6eec8792020-01-09 14:06:48 -08001618 # TODO(crbug/1004487): Deprecate critical_update.
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001619 group.add_option('--critical_update',
1620 action='store_true', default=False,
1621 help='Present update payload as critical')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001622 group.add_option('--payload',
1623 metavar='PATH',
1624 help='use the update payload from specified directory '
1625 '(update.gz).')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001626 parser.add_option_group(group)
1627
1628
1629def _AddProductionOptions(parser):
1630 group = optparse.OptionGroup(
1631 parser, 'Advanced Server Options', 'These options can be used to changed '
1632 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001633 group.add_option('--clear_cache',
1634 action='store_true', default=False,
1635 help='At startup, removes all cached entries from the'
Amin Hassanid4e35392019-10-03 11:02:44 -07001636 "devserver's cache.")
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001637 group.add_option('--logfile',
1638 metavar='PATH',
1639 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001640 group.add_option('--pidfile',
1641 metavar='PATH',
1642 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001643 group.add_option('--portfile',
1644 metavar='PATH',
1645 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001646 group.add_option('--production',
1647 action='store_true', default=False,
1648 help='have the devserver use production values when '
1649 'starting up. This includes using more threads and '
1650 'performing less logging.')
1651 parser.add_option_group(group)
1652
1653
Paul Hobbsef4e0702016-06-27 17:01:42 -07001654def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001655 """Create a LogHandler instance used to log all messages."""
1656 hdlr_cls = handlers.TimedRotatingFileHandler
1657 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001658 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001659 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001660 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001661 return hdlr
1662
1663
Chris Sosacde6bf42012-05-31 18:36:39 -07001664def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001665 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001666 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001667
1668 # get directory that the devserver is run from
1669 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001670 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001671 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001672 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001673 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001674 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001675 parser.add_option('--port',
1676 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001677 help=('port for the dev server to use; if zero, binds to '
1678 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001679 parser.add_option('-t', '--test_image',
1680 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001681 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001682 parser.add_option('-x', '--xbuddy_manage_builds',
1683 action='store_true',
1684 default=False,
1685 help='If set, allow xbuddy to manage images in'
1686 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001687 parser.add_option('-a', '--android_build_credential',
1688 default=None,
1689 help='Path to a json file which contains the credential '
1690 'needed to access Android builds.')
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001691 parser.add_option('--infra_removal',
1692 action='store_true', default=False,
1693 help='If option is present, some RPCs will be disabled to '
1694 'help with infra removal efforts. See '
1695 'go/devserver-deprecation')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001696 _AddProductionOptions(parser)
1697 _AddUpdateOptions(parser)
1698 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001699 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001700
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001701 # Handle options that must be set globally in cherrypy. Do this
1702 # work up front, because calls to _Log() below depend on this
1703 # initialization.
1704 if options.production:
1705 cherrypy.config.update({'environment': 'production'})
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001706 cherrypy.config.update({'infra_removal': options.infra_removal})
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001707 if not options.logfile:
1708 cherrypy.config.update({'log.screen': True})
1709 else:
1710 cherrypy.config.update({'log.error_file': '',
1711 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001712 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001713 # Pylint can't seem to process these two calls properly
1714 # pylint: disable=E1101
1715 cherrypy.log.access_log.addHandler(hdlr)
1716 cherrypy.log.error_log.addHandler(hdlr)
1717 # pylint: enable=E1101
1718
joychened64b222013-06-21 16:39:34 -07001719 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001720 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001721
joychened64b222013-06-21 16:39:34 -07001722 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001723 # If our devserver is only supposed to serve payloads, we shouldn't be
1724 # mucking with the cache at all. If the devserver hadn't previously
1725 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001726 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001727 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001728 else:
1729 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001730
Chris Sosadbc20082012-12-10 13:39:11 -08001731 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001732 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001733
Amin Hassanie9ffb862019-09-25 17:10:40 -07001734 _xbuddy = xbuddy.XBuddy(manage_builds=options.xbuddy_manage_builds,
joychen121fc9b2013-08-02 14:30:30 -07001735 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001736 if options.clear_cache and options.xbuddy_manage_builds:
1737 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001738
Chris Sosa6a3697f2013-01-29 16:44:43 -08001739 # We allow global use here to share with cherrypy classes.
1740 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001741 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001742 updater = autoupdate.Autoupdate(
joychen121fc9b2013-08-02 14:30:30 -07001743 _xbuddy,
joychened64b222013-06-21 16:39:34 -07001744 static_dir=options.static_dir,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001745 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001746 proxy_port=options.proxy_port,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001747 critical_update=options.critical_update,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001748 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001749 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001750 )
Chris Sosa7c931362010-10-11 19:49:01 -07001751
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001752 if options.exit:
1753 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001754
joychen3cb228e2013-06-12 12:13:13 -07001755 dev_server = DevServerRoot(_xbuddy)
Congbin Guo3afae6c2019-08-13 16:29:42 -07001756 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 12:13:13 -07001757
Amin Hassanic5af4262019-11-13 13:37:20 -08001758 # Patch CherryPy to support binding to any available port (--port=0) only for
1759 # cherrypy versions smaller or equal to 3.2.2.
1760 #
1761 # TODO(crbug/1006305): Remove this once we have deprecated omaha_devserver.py
1762 # in the autotests as that is the only use case.
1763 #
1764 # pylint: disable=no-member
1765 if (distutils.version.StrictVersion(cherrypy.__version__) <=
1766 distutils.version.StrictVersion('3.2.2')):
1767 cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy)
1768 # pylint: enable=no-member
1769
Chris Sosa855b8932013-08-21 13:24:55 -07001770 if options.pidfile:
1771 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1772
Gilad Arnold11fbef42014-02-10 11:04:13 -08001773 if options.portfile:
1774 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1775
Dan Shiafd5c6c2016-01-07 10:27:03 -08001776 if (options.android_build_credential and
1777 os.path.exists(options.android_build_credential)):
1778 try:
1779 with open(options.android_build_credential) as f:
1780 android_build.BuildAccessor.credential_info = json.load(f)
1781 except ValueError as e:
1782 _Log('Failed to load the android build credential: %s. Error: %s.' %
1783 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 16:29:42 -07001784
1785 cherrypy.tree.mount(health_checker_app, '/check_health',
1786 config=health_checker.get_config())
joychen3cb228e2013-06-12 12:13:13 -07001787 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001788
1789
1790if __name__ == '__main__':
1791 main()