blob: 365d8a5559d6a5302350649551d046212d67cc6c [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
Gilad Arnoldc65330c2012-09-20 15:17:48 -070067
Gilad Arnoldc65330c2012-09-20 15:17:48 -070068# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080069def _Log(message, *args):
Achuith Bhandarkar662fb722019-10-31 16:12:49 -070070 return cherrypy_log_util.LogWithTag('DEVSERVER', message, *args)
Frank Farzan40160872011-12-12 18:39:18 -080071
Chris Sosa417e55d2011-01-25 16:40:48 -080072CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080073
Simran Basi4baad082013-02-14 13:39:18 -080074TELEMETRY_FOLDER = 'telemetry_src'
75TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
76 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070077 'dep-chrome_test.tar.bz2',
78 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -080079
Chris Sosa0356d3b2010-09-16 15:46:22 -070080# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +000081updater = None
rtc@google.comded22402009-10-26 22:36:21 +000082
xixuan3d48bff2017-01-30 19:00:09 -080083# Log rotation parameters. These settings correspond to twice a day once
84# devserver is started, with about two weeks (28 backup files) of old logs
85# kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -070086#
xixuan3d48bff2017-01-30 19:00:09 -080087# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 11:05:19 -070088# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-30 19:00:09 -080089_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 16:29:42 -070090_LOG_ROTATION_INTERVAL = 12 # hours
91_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-12 18:39:18 -080092
xixuan52c2fba2016-05-20 17:02:48 -070093# Auto-update parameters
94
95# Error msg for missing key in CrOS auto-update.
Xixuan Wu32af9f12017-11-13 14:11:44 -080096KEY_ERROR_MSG = 'Key Error in RPC: %s= is required'
xixuan52c2fba2016-05-20 17:02:48 -070097
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -080098# Error msg for deprecated RPC usage.
99DEPRECATED_RPC_ERROR_MSG = ('The %s RPC has been deprecated. Usage of this '
100 'RPC is discouraged. Please go to '
101 'go/devserver-deprecation for more information.')
102
xixuan52c2fba2016-05-20 17:02:48 -0700103
Amin Hassanid4e35392019-10-03 11:02:44 -0700104class DevServerError(Exception):
105 """Exception class used by DevServer."""
106
107
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800108class DeprecatedRPCError(DevServerError):
109 """Exception class used when an RPC is deprecated but is still being used."""
110
111 def __init__(self, rpc_name):
112 """Constructor for DeprecatedRPCError class.
113
114 :param rpc_name: (str) name of the RPC that has been deprecated.
115 """
116 super(DeprecatedRPCError, self).__init__(DEPRECATED_RPC_ERROR_MSG % rpc_name)
117 self.rpc_name = rpc_name
118
119
Amin Hassani722e0962019-11-15 15:45:31 -0800120class DevServerHTTPError(cherrypy.HTTPError):
121 """Exception class to log the HTTPResponse before routing it to cherrypy."""
122 def __init__(self, status, message):
123 """CherryPy error with logging.
124
125 Args:
126 status: HTTPResponse status.
127 message: Message associated with the response.
128 """
129 cherrypy.HTTPError.__init__(self, status, message)
130 _Log('HTTPError status: %s message: %s', status, message)
131
132
Gabe Black3b567202015-09-23 14:07:59 -0700133def _canonicalize_archive_url(archive_url):
134 """Canonicalizes archive_url strings.
135
136 Raises:
137 DevserverError: if archive_url is not set.
138 """
139 if archive_url:
140 if not archive_url.startswith('gs://'):
Amin Hassanid4e35392019-10-03 11:02:44 -0700141 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700142 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 14:07:59 -0700143
144 return archive_url.rstrip('/')
145 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700146 raise DevServerError('Must specify an archive_url in the request')
Gabe Black3b567202015-09-23 14:07:59 -0700147
148
149def _canonicalize_local_path(local_path):
150 """Canonicalizes |local_path| strings.
151
152 Raises:
153 DevserverError: if |local_path| is not set.
154 """
155 # Restrict staging of local content to only files within the static
156 # directory.
157 local_path = os.path.abspath(local_path)
158 if not local_path.startswith(updater.static_dir):
Amin Hassanid4e35392019-10-03 11:02:44 -0700159 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700160 'Local path %s must be a subdirectory of the static'
161 ' directory: %s' % (local_path, updater.static_dir))
Gabe Black3b567202015-09-23 14:07:59 -0700162
163 return local_path.rstrip('/')
164
165
166def _get_artifacts(kwargs):
167 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
168
169 Raises:
170 DevserverError if no artifacts would be returned.
171 """
172 artifacts = kwargs.get('artifacts')
173 files = kwargs.get('files')
174 if not artifacts and not files:
Amin Hassanid4e35392019-10-03 11:02:44 -0700175 raise DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700176
177 # Note we NEED to coerce files to a string as we get raw unicode from
178 # cherrypy and we treat files as strings elsewhere in the code.
179 return (str(artifacts).split(',') if artifacts else [],
180 str(files).split(',') if files else [])
181
182
Dan Shi61305df2015-10-26 16:52:35 -0700183def _is_android_build_request(kwargs):
184 """Check if a devserver call is for Android build, based on the arguments.
185
186 This method exams the request's arguments (os_type) to determine if the
187 request is for Android build. If os_type is set to `android`, returns True.
188 If os_type is not set or has other values, returns False.
189
190 Args:
191 kwargs: Keyword arguments for the request.
192
193 Returns:
194 True if the request is for Android build. False otherwise.
195 """
196 os_type = kwargs.get('os_type', None)
197 return os_type == 'android'
198
199
Gabe Black3b567202015-09-23 14:07:59 -0700200def _get_downloader(kwargs):
201 """Returns the downloader based on passed in arguments.
202
203 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700204 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700205 """
206 local_path = kwargs.get('local_path')
207 if local_path:
208 local_path = _canonicalize_local_path(local_path)
209
210 dl = None
211 if local_path:
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800212 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
213 dl = downloader.LocalDownloader(updater.static_dir, local_path,
214 delete_source=delete_source)
Gabe Black3b567202015-09-23 14:07:59 -0700215
Dan Shi61305df2015-10-26 16:52:35 -0700216 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700217 archive_url = kwargs.get('archive_url')
218 if not archive_url and not local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700219 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700220 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700221 if archive_url and local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700222 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700223 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700224 if not dl:
225 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700226 dl = downloader.GoogleStorageDownloader(
227 updater.static_dir, archive_url,
228 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
229 archive_url))
Gabe Black3b567202015-09-23 14:07:59 -0700230 elif not dl:
231 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 12:10:33 -0700232 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 16:52:35 -0700233 build_id = kwargs.get('build_id', None)
234 if not target or not branch or not build_id:
Amin Hassanid4e35392019-10-03 11:02:44 -0700235 raise DevServerError('target, branch, build ID must all be specified for '
236 'downloading Android build.')
Dan Shi72b16132015-10-08 12:10:33 -0700237 dl = downloader.AndroidBuildDownloader(updater.static_dir, branch, build_id,
238 target)
Gabe Black3b567202015-09-23 14:07:59 -0700239
240 return dl
241
242
243def _get_downloader_and_factory(kwargs):
244 """Returns the downloader and artifact factory based on passed in arguments.
245
246 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700247 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700248 """
249 artifacts, files = _get_artifacts(kwargs)
250 dl = _get_downloader(kwargs)
251
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700252 if (isinstance(dl, (downloader.GoogleStorageDownloader,
253 downloader.LocalDownloader))):
Gabe Black3b567202015-09-23 14:07:59 -0700254 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 12:10:33 -0700255 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 14:07:59 -0700256 factory_class = build_artifact.AndroidArtifactFactory
257 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700258 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700259 'Unrecognized value for downloader type: %s' % type(dl))
Gabe Black3b567202015-09-23 14:07:59 -0700260
261 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
262
263 return dl, factory
264
265
Scott Zawalski4647ce62012-01-03 17:17:28 -0500266def _LeadingWhiteSpaceCount(string):
267 """Count the amount of leading whitespace in a string.
268
269 Args:
270 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800271
Scott Zawalski4647ce62012-01-03 17:17:28 -0500272 Returns:
273 number of white space chars before characters start.
274 """
Gabe Black3b567202015-09-23 14:07:59 -0700275 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500276 if matched:
277 return len(matched.group())
278
279 return 0
280
281
282def _PrintDocStringAsHTML(func):
283 """Make a functions docstring somewhat HTML style.
284
285 Args:
286 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800287
Scott Zawalski4647ce62012-01-03 17:17:28 -0500288 Returns:
289 A string that is somewhat formated for a web browser.
290 """
291 # TODO(scottz): Make this parse Args/Returns in a prettier way.
292 # Arguments could be bolded and indented etc.
293 html_doc = []
294 for line in func.__doc__.splitlines():
295 leading_space = _LeadingWhiteSpaceCount(line)
296 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700297 line = ' ' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500298
299 html_doc.append('<BR>%s' % line)
300
301 return '\n'.join(html_doc)
302
303
Simran Basief83d6a2014-08-28 14:32:01 -0700304def _GetUpdateTimestampHandler(static_dir):
305 """Returns a handler to update directory staged.timestamp.
306
307 This handler resets the stage.timestamp whenever static content is accessed.
308
309 Args:
310 static_dir: Directory from which static content is being staged.
311
312 Returns:
Amin Hassani08e42d22019-06-03 00:31:30 -0700313 A cherrypy handler to update the timestamp of accessed content.
Simran Basief83d6a2014-08-28 14:32:01 -0700314 """
315 def UpdateTimestampHandler():
316 if not '404' in cherrypy.response.status:
317 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
318 cherrypy.request.path_info)
319 if build_match:
320 build_dir = os.path.join(static_dir, build_match.group('build'))
321 downloader.Downloader.TouchTimestampForStaged(build_dir)
322 return UpdateTimestampHandler
323
324
Chris Sosa7c931362010-10-11 19:49:01 -0700325def _GetConfig(options):
326 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800327
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800328 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800329 # Fall back to IPv4 when python is not configured with IPv6.
330 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800331 socket_host = '0.0.0.0'
332
Simran Basief83d6a2014-08-28 14:32:01 -0700333 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
334 # on the on_end_resource hook. This hook is called once processing is
335 # complete and the response is ready to be returned.
336 cherrypy.tools.update_timestamp = cherrypy.Tool(
337 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
338
David Riley2fcb0122017-11-02 11:25:39 -0700339 base_config = {
340 'global': {
341 'server.log_request_headers': True,
342 'server.protocol_version': 'HTTP/1.1',
343 'server.socket_host': socket_host,
344 'server.socket_port': int(options.port),
345 'response.timeout': 6000,
346 'request.show_tracebacks': True,
347 'server.socket_timeout': 60,
348 'server.thread_pool': 2,
349 'engine.autoreload.on': False,
350 },
351 '/api': {
352 # Gets rid of cherrypy parsing post file for args.
353 'request.process_request_body': False,
354 },
355 '/build': {
356 'response.timeout': 100000,
357 },
358 '/update': {
359 # Gets rid of cherrypy parsing post file for args.
360 'request.process_request_body': False,
361 'response.timeout': 10000,
362 },
363 # Sets up the static dir for file hosting.
364 '/static': {
365 'tools.staticdir.dir': options.static_dir,
366 'tools.staticdir.on': True,
367 'response.timeout': 10000,
368 'tools.update_timestamp.on': True,
369 },
370 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700371 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700372 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500373
Chris Sosa7c931362010-10-11 19:49:01 -0700374 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000375
Darin Petkove17164a2010-08-11 13:24:41 -0700376
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700377def _GetRecursiveMemberObject(root, member_list):
378 """Returns an object corresponding to a nested member list.
379
380 Args:
381 root: the root object to search
382 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800383
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700384 Returns:
385 An object corresponding to the member name list; None otherwise.
386 """
387 for member in member_list:
388 next_root = root.__class__.__dict__.get(member)
389 if not next_root:
390 return None
391 root = next_root
392 return root
393
394
395def _IsExposed(name):
396 """Returns True iff |name| has an `exposed' attribute and it is set."""
397 return hasattr(name, 'exposed') and name.exposed
398
399
Congbin Guo6bc32182019-08-20 17:54:30 -0700400def _GetExposedMethod(nested_member):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700401 """Returns a CherryPy-exposed method, if such exists.
402
403 Args:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700404 nested_member: a slash-joined path to the nested member
Don Garrettf84631a2014-01-07 18:21:26 -0800405
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700406 Returns:
Congbin Guo6bc32182019-08-20 17:54:30 -0700407 A function object corresponding to the path defined by |nested_member| from
408 the app root object registered, if the function is exposed; None otherwise.
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700409 """
Congbin Guo6bc32182019-08-20 17:54:30 -0700410 for app in cherrypy.tree.apps.values():
411 # Use the 'index' function doc as the doc of the app.
412 if nested_member == app.script_name.lstrip('/'):
413 nested_member = 'index'
414
415 method = _GetRecursiveMemberObject(app.root, nested_member.split('/'))
416 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
417 return method
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700418
419
Gilad Arnold748c8322012-10-12 09:51:35 -0700420def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700421 """Finds exposed CherryPy methods.
422
423 Args:
424 root: the root object for searching
425 prefix: slash-joined chain of members leading to current object
426 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800427
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700428 Returns:
429 List of exposed URLs that are not unlisted.
430 """
431 method_list = []
Congbin Guo6bc32182019-08-20 17:54:30 -0700432 for member in root.__class__.__dict__.keys():
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700433 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700434 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700435 continue
436 member_obj = root.__class__.__dict__[member]
437 if _IsExposed(member_obj):
Amin Hassani08e42d22019-06-03 00:31:30 -0700438 if isinstance(member_obj, types.FunctionType):
Congbin Guo6bc32182019-08-20 17:54:30 -0700439 # Regard the app name as exposed "method" name if it exposed 'index'
440 # function.
441 if prefix and member == 'index':
442 method_list.append(prefix)
443 else:
444 method_list.append(prefixed_member)
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700445 else:
446 method_list += _FindExposedMethods(
447 member_obj, prefixed_member, unlisted)
448 return method_list
449
450
xixuan52c2fba2016-05-20 17:02:48 -0700451def _check_base_args_for_auto_update(kwargs):
xixuanac89ce82016-11-30 16:48:20 -0800452 """Check basic args required for auto-update.
453
454 Args:
455 kwargs: the parameters to be checked.
456
457 Raises:
458 DevServerHTTPError if required parameters don't exist in kwargs.
459 """
xixuan52c2fba2016-05-20 17:02:48 -0700460 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800461 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
462 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700463
464 if 'build_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800465 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
466 KEY_ERROR_MSG % 'build_name')
xixuan52c2fba2016-05-20 17:02:48 -0700467
468
469def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-11-30 16:48:20 -0800470 """Parse boolean arg from kwargs.
471
472 Args:
473 kwargs: the parameters to be checked.
474 key: the key to be parsed.
475
476 Returns:
477 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
478
479 Raises:
480 DevServerHTTPError if kwargs[key] is not a boolean variable.
481 """
xixuan52c2fba2016-05-20 17:02:48 -0700482 if key in kwargs:
483 if kwargs[key] == 'True':
484 return True
485 elif kwargs[key] == 'False':
486 return False
487 else:
Amin Hassani722e0962019-11-15 15:45:31 -0800488 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
489 'The value for key %s is not boolean.' % key)
xixuan52c2fba2016-05-20 17:02:48 -0700490 else:
491 return False
492
xixuan447ad9d2017-02-28 14:46:20 -0800493
xixuanac89ce82016-11-30 16:48:20 -0800494def _parse_string_arg(kwargs, key):
495 """Parse string arg from kwargs.
496
497 Args:
498 kwargs: the parameters to be checked.
499 key: the key to be parsed.
500
501 Returns:
502 The string value of kwargs[key], or None if key doesn't exist in kwargs.
503 """
504 if key in kwargs:
505 return kwargs[key]
506 else:
507 return None
508
xixuan447ad9d2017-02-28 14:46:20 -0800509
xixuanac89ce82016-11-30 16:48:20 -0800510def _build_uri_from_build_name(build_name):
511 """Get build url from a given build name.
512
513 Args:
514 build_name: the build name to be parsed, whose format is
515 'board/release_version'.
516
517 Returns:
518 The release_archive_url on Google Storage for this build name.
519 """
Amin Hassani08e42d22019-06-03 00:31:30 -0700520 # TODO(ahassani): This function doesn't seem to be used anywhere since its
521 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
522 # causing any runtime issues. So deprecate this in the future.
523 tokens = build_name.split('/')
524 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-20 17:02:48 -0700525
xixuan447ad9d2017-02-28 14:46:20 -0800526
527def _clear_process(host_name, pid):
528 """Clear AU process for given hostname and pid.
529
530 This clear includes:
531 1. kill process if it's alive.
532 2. delete the track status file of this process.
533 3. delete the executing log file of this process.
534
535 Args:
536 host_name: the host to execute auto-update.
537 pid: the background auto-update process id.
538 """
539 if cros_update_progress.IsProcessAlive(pid):
540 os.killpg(int(pid), signal.SIGKILL)
541
542 cros_update_progress.DelTrackStatusFile(host_name, pid)
543 cros_update_progress.DelExecuteLogFile(host_name, pid)
544
545
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800546def is_deprecated_server():
547 """Gets whether the devserver has deprecated RPCs."""
548 return cherrypy.config.get('infra_removal', False)
549
550
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700551class ApiRoot(object):
552 """RESTful API for Dev Server information."""
553 exposed = True
554
555 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800556 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700557 """Returns a JSON object containing a log of host event.
558
559 Args:
560 ip: address of host whose event log is requested, or `all'
Don Garrettf84631a2014-01-07 18:21:26 -0800561
Gilad Arnold1b908392012-10-05 11:36:27 -0700562 Returns:
Amin Hassani7c447852019-09-26 15:01:48 -0700563 A JSON dictionary containing all or some of the following fields:
Amin Hassanie7ead902019-10-11 16:42:43 -0700564 version: The Chromium OS version the device is running.
565 track: The channel the device is running on.
566 board: The device's board.
567 event_result: The event result of Omaha request.
568 event_type: The event type of Omaha request.
569 previous_version: The Chromium OS version we updated and rebooted from.
570 timestamp: The timestamp the event was received.
Amin Hassani7c447852019-09-26 15:01:48 -0700571 See the OmahaEvent class in update_engine/omaha_request_action.h for
572 event type and status code definitions. If the ip does not exist an empty
573 string is returned.
Gilad Arnold1b908392012-10-05 11:36:27 -0700574
575 Example URL:
576 http://myhost/api/hostlog?ip=192.168.1.5
577 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800578 if is_deprecated_server():
579 raise DeprecatedRPCError('hostlog')
Gilad Arnold286a0062012-01-12 13:47:02 -0800580 return updater.HandleHostLogPing(ip)
581
582 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800583 def fileinfo(self, *args):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700584 """Returns information about a given staged file.
585
586 Args:
Don Garrettf84631a2014-01-07 18:21:26 -0800587 args: path to the file inside the server's static staging directory
588
Gilad Arnold55a2a372012-10-02 09:46:32 -0700589 Returns:
590 A JSON encoded dictionary with information about the said file, which may
591 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700592 size (int): the file size in bytes
Gilad Arnold1b908392012-10-05 11:36:27 -0700593 sha256 (string): a base64 encoded SHA256 hash
594
595 Example URL:
596 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700597 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800598 if is_deprecated_server():
599 raise DeprecatedRPCError('fileinfo')
600
Amin Hassani28df4212019-10-28 10:16:50 -0700601 # TODO(ahassani): A better way of doing this is to just return the the
602 # content of the payloads' property file instead. That has all this info
603 # except that the key for sha256 is 'sha256_hex', but still base64 encdoed.
604
Don Garrettf84631a2014-01-07 18:21:26 -0800605 file_path = os.path.join(updater.static_dir, *args)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700606 if not os.path.exists(file_path):
Amin Hassanid4e35392019-10-03 11:02:44 -0700607 raise DevServerError('file not found: %s' % file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700608 try:
609 file_size = os.path.getsize(file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700610 file_sha256 = common_util.GetFileSha256(file_path)
Amin Hassani469f5702019-10-21 15:35:06 -0700611 except os.error as e:
Amin Hassanid4e35392019-10-03 11:02:44 -0700612 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700613 'failed to get info for file %s: %s' % (file_path, e))
Gilad Arnolde74b3812013-04-22 11:27:38 -0700614
Gilad Arnolde74b3812013-04-22 11:27:38 -0700615 return json.dumps({
Amin Hassani28df4212019-10-28 10:16:50 -0700616 'size': file_size,
617 'sha256': file_sha256,
618 }, sort_keys=True)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700619
Chris Sosa76e44b92013-01-31 12:11:38 -0800620
David Rochberg7c79a812011-01-19 14:24:45 -0500621class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700622 """The Root Class for the Dev Server.
623
624 CherryPy works as follows:
625 For each method in this class, cherrpy interprets root/path
626 as a call to an instance of DevServerRoot->method_name. For example,
627 a call to http://myhost/build will call build. CherryPy automatically
628 parses http args and places them as keyword arguments in each method.
629 For paths http://myhost/update/dir1/dir2, you can use *args so that
630 cherrypy uses the update method and puts the extra paths in args.
631 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700632 # Method names that should not be listed on the index page.
633 _UNLISTED_METHODS = ['index', 'doc']
634
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700635 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700636
Dan Shi59ae7092013-06-04 14:37:27 -0700637 # Number of threads that devserver is staging images.
638 _staging_thread_count = 0
639 # Lock used to lock increasing/decreasing count.
640 _staging_thread_count_lock = threading.Lock()
641
joychen3cb228e2013-06-12 12:13:13 -0700642 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700643 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800644 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700645 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500646
Congbin Guo3afae6c2019-08-13 16:29:42 -0700647 @property
648 def staging_thread_count(self):
649 """Get the staging thread count."""
650 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700651
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700652 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500653 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700654 """Builds the package specified."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -0800655 if is_deprecated_server():
656 raise DeprecatedRPCError('build')
657
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700658 import builder
659 if self._builder is None:
660 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500661 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700662
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700663 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700664 def is_staged(self, **kwargs):
665 """Check if artifacts have been downloaded.
666
Congbin Guo3afae6c2019-08-13 16:29:42 -0700667 Examples:
668 To check if autotest and test_suites are staged:
669 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
670 artifacts=autotest,test_suites
671
Amin Hassani08e42d22019-06-03 00:31:30 -0700672 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700673 async: True to return without waiting for download to complete.
674 artifacts: Comma separated list of named artifacts to download.
675 These are defined in artifact_info and have their implementation
676 in build_artifact.py.
677 files: Comma separated list of file artifacts to stage. These
678 will be available as is in the corresponding static directory with no
679 custom post-processing.
680
Congbin Guo3afae6c2019-08-13 16:29:42 -0700681 Returns:
682 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700683 """
Gabe Black3b567202015-09-23 14:07:59 -0700684 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700685 response = str(dl.IsStaged(factory))
686 _Log('Responding to is_staged %s request with %r', kwargs, response)
687 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700688
Chris Sosa76e44b92013-01-31 12:11:38 -0800689 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800690 def list_image_dir(self, **kwargs):
691 """Take an archive url and list the contents in its staged directory.
692
Amin Hassani08e42d22019-06-03 00:31:30 -0700693 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800694 To list the contents of where this devserver should have staged
695 gs://image-archive/<board>-release/<build> call:
696 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
697
Congbin Guo3afae6c2019-08-13 16:29:42 -0700698 Args:
699 archive_url: Google Storage URL for the build.
700
Prashanth Ba06d2d22014-03-07 15:35:19 -0800701 Returns:
702 A string with information about the contents of the image directory.
703 """
Gabe Black3b567202015-09-23 14:07:59 -0700704 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800705 try:
Gabe Black3b567202015-09-23 14:07:59 -0700706 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800707 except build_artifact.ArtifactDownloadError as e:
708 return 'Cannot list the contents of staged artifacts. %s' % e
709 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700710 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800711 return image_dir_contents
712
713 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800714 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700715 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800716
Gabe Black3b567202015-09-23 14:07:59 -0700717 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700718 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700719 on the devserver. A call to this will attempt to cache non-specified
720 artifacts in the background for the given from the given URL following
721 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800722 artifacts is explicitly defined in the build_artifact module.
723
724 These artifacts will then be available from the static/ sub-directory of
725 the devserver.
726
Amin Hassani08e42d22019-06-03 00:31:30 -0700727 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800728 To download the autotest and test suites tarballs:
729 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
730 artifacts=autotest,test_suites
731 To download the full update payload:
732 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
733 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700734 To download just a file called blah.bin:
735 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
736 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800737
738 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700739 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800740
741 Note for this example, relative path is the archive_url stripped of its
742 basename i.e. path/ in the examples above. Specific example:
743
744 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
745
746 Will get staged to:
747
joychened64b222013-06-21 16:39:34 -0700748 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700749
750 Args:
751 archive_url: Google Storage URL for the build.
752 local_path: Local path for the build.
753 delete_source: Only meaningful with local_path. bool to indicate if the
754 source files should be deleted. This is especially useful when staging
755 a file locally in resource constrained environments as it allows us to
756 move the relevant files locally instead of copying them.
757 async: True to return without waiting for download to complete.
758 artifacts: Comma separated list of named artifacts to download.
759 These are defined in artifact_info and have their implementation
760 in build_artifact.py.
761 files: Comma separated list of files to stage. These
762 will be available as is in the corresponding static directory with no
763 custom post-processing.
764 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800765 """
Gabe Black3b567202015-09-23 14:07:59 -0700766 dl, factory = _get_downloader_and_factory(kwargs)
767
Dan Shi59ae7092013-06-04 14:37:27 -0700768 with DevServerRoot._staging_thread_count_lock:
769 DevServerRoot._staging_thread_count += 1
770 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800771 boolean_string = kwargs.get('clean')
772 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
773 if clean and os.path.exists(dl.GetBuildDir()):
774 _Log('Removing %s' % dl.GetBuildDir())
775 shutil.rmtree(dl.GetBuildDir())
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700776 is_async = kwargs.get('async', False)
777 dl.Download(factory, is_async=is_async)
Dan Shi59ae7092013-06-04 14:37:27 -0700778 finally:
779 with DevServerRoot._staging_thread_count_lock:
780 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800781 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700782
783 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700784 def cros_au(self, **kwargs):
785 """Auto-update a CrOS DUT.
786
787 Args:
788 kwargs:
789 host_name: the hostname of the DUT to auto-update.
790 build_name: the build name for update the DUT.
791 force_update: Force an update even if the version installed is the
792 same. Default: False.
793 full_update: If True, do not run stateful update, directly force a full
794 reimage. If False, try stateful update first if the dut is already
795 installed with the same version.
796 async: Whether the auto_update function is ran in the background.
David Rileyee75de22017-11-02 10:48:15 -0700797 quick_provision: Whether the quick provision path is attempted first.
xixuan52c2fba2016-05-20 17:02:48 -0700798
799 Returns:
800 A tuple includes two elements:
801 a boolean variable represents whether the auto-update process is
802 successfully started.
803 an integer represents the background auto-update process id.
804 """
805 _check_base_args_for_auto_update(kwargs)
806
807 host_name = kwargs['host_name']
808 build_name = kwargs['build_name']
809 force_update = _parse_boolean_arg(kwargs, 'force_update')
810 full_update = _parse_boolean_arg(kwargs, 'full_update')
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700811 is_async = _parse_boolean_arg(kwargs, 'async')
xixuanac89ce82016-11-30 16:48:20 -0800812 original_build = _parse_string_arg(kwargs, 'original_build')
David Haddock90e49442017-04-07 19:14:09 -0700813 payload_filename = _parse_string_arg(kwargs, 'payload_filename')
David Haddock20559612017-06-28 22:15:08 -0700814 clobber_stateful = _parse_boolean_arg(kwargs, 'clobber_stateful')
David Rileyee75de22017-11-02 10:48:15 -0700815 quick_provision = _parse_boolean_arg(kwargs, 'quick_provision')
816
817 devserver_url = updater.GetDevserverUrl()
818 static_url = updater.GetStaticUrl()
xixuan52c2fba2016-05-20 17:02:48 -0700819
Mike Frysinger88e277f2020-03-02 17:20:46 -0500820 # 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
Achuith Bhandarkar2a1fcd82019-10-17 17:45:58 -0700855 if is_async:
Amin Hassani78520ae2019-10-29 13:26:51 -0700856 p = subprocess.Popen(cmd, preexec_fn=os.setsid)
xixuan2a0970a2016-08-10 12:12:44 -0700857 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-20 17:02:48 -0700858
859 # Pre-write status in the track_status_file before the first call of
860 # 'get_au_status' to make sure that the track_status_file exists.
xixuan2a0970a2016-08-10 12:12:44 -0700861 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700862 progress_tracker.WriteStatus('CrOS update is just started.')
863
xixuan2a0970a2016-08-10 12:12:44 -0700864 return json.dumps((True, pid))
xixuan52c2fba2016-05-20 17:02:48 -0700865 else:
Mike Frysinger88e277f2020-03-02 17:20:46 -0500866 subprocess.check_call(cmd)
xixuan27d50442017-08-09 10:38:25 -0700867 return json.dumps((True, -1))
xixuan52c2fba2016-05-20 17:02:48 -0700868
869 @cherrypy.expose
870 def get_au_status(self, **kwargs):
871 """Check if the auto-update task is finished.
872
873 It handles 4 cases:
874 1. If an error exists in the track_status_file, delete the track file and
875 raise it.
876 2. If cros-update process is finished, delete the file and return the
877 success result.
878 3. If the process is not running, delete the track file and raise an error
879 about 'the process is terminated due to unknown reason'.
880 4. If the track_status_file does not exist, kill the process if it exists,
881 and raise the IOError.
882
883 Args:
884 kwargs:
885 host_name: the hostname of the DUT to auto-update.
886 pid: the background process id of cros-update.
887
888 Returns:
xixuan28d99072016-10-06 12:24:16 -0700889 A dict with three elements:
xixuan52c2fba2016-05-20 17:02:48 -0700890 a boolean variable represents whether the auto-update process is
891 finished.
892 a string represents the current auto-update process status.
893 For example, 'Transfer Devserver/Stateful Update Package'.
xixuan28d99072016-10-06 12:24:16 -0700894 a detailed error message paragraph if there exists an Auto-Update
895 error, in which the last line shows the main exception. Empty
896 string otherwise.
xixuan52c2fba2016-05-20 17:02:48 -0700897 """
898 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800899 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
900 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700901
902 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800903 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
904 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700905
906 host_name = kwargs['host_name']
907 pid = kwargs['pid']
908 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
909
xixuan28d99072016-10-06 12:24:16 -0700910 result_dict = {'finished': False, 'status': '', 'detailed_error_msg': ''}
xixuan52c2fba2016-05-20 17:02:48 -0700911 try:
912 result = progress_tracker.ReadStatus()
913 if result.startswith(cros_update_progress.ERROR_TAG):
xixuan28d99072016-10-06 12:24:16 -0700914 result_dict['detailed_error_msg'] = result[len(
915 cros_update_progress.ERROR_TAG):]
xixuan28681fd2016-11-23 11:13:56 -0800916 elif result == cros_update_progress.FINISHED:
xixuan28d99072016-10-06 12:24:16 -0700917 result_dict['finished'] = True
918 result_dict['status'] = result
xixuan28681fd2016-11-23 11:13:56 -0800919 elif not cros_update_progress.IsProcessAlive(pid):
xixuan28d99072016-10-06 12:24:16 -0700920 result_dict['detailed_error_msg'] = (
921 'Cros_update process terminated midway due to unknown reason. '
922 'Last update status was %s' % result)
xixuan28681fd2016-11-23 11:13:56 -0800923 else:
924 result_dict['status'] = result
925 except IOError as e:
926 if pid and cros_update_progress.IsProcessAlive(pid):
xixuan2a0970a2016-08-10 12:12:44 -0700927 os.killpg(int(pid), signal.SIGKILL)
xixuan52c2fba2016-05-20 17:02:48 -0700928
xixuan28681fd2016-11-23 11:13:56 -0800929 result_dict['detailed_error_msg'] = str(e)
930
931 return json.dumps(result_dict)
xixuan52c2fba2016-05-20 17:02:48 -0700932
933 @cherrypy.expose
David Riley6d5fca02017-10-31 10:35:47 -0700934 def post_au_status(self, status, **kwargs):
935 """Updates the status of an auto-update task.
936
937 Callers will need to POST to this URL with a body of MIME-type
938 "multipart/form-data".
939 The body should include a single argument, 'status', containing the
940 AU status to record.
941
942 Args:
943 status: The updated status.
944 kwargs:
945 host_name: the hostname of the DUT to auto-update.
946 pid: the background process id of cros-update.
947 """
948 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800949 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
950 KEY_ERROR_MSG % 'host_name')
David Riley6d5fca02017-10-31 10:35:47 -0700951
952 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800953 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
954 KEY_ERROR_MSG % 'pid')
David Riley6d5fca02017-10-31 10:35:47 -0700955
956 host_name = kwargs['host_name']
957 pid = kwargs['pid']
David Riley3cea2582017-11-24 22:03:01 -0800958 status = status.rstrip()
959 _Log('Recording status for %s (%s): %s' % (host_name, pid, status))
David Riley6d5fca02017-10-31 10:35:47 -0700960 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
961
David Riley3cea2582017-11-24 22:03:01 -0800962 progress_tracker.WriteStatus(status)
David Riley6d5fca02017-10-31 10:35:47 -0700963
964 return 'True'
965
966 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700967 def handler_cleanup(self, **kwargs):
xixuan3bc974e2016-10-18 17:21:43 -0700968 """Clean track status log and temp directory for CrOS auto-update process.
xixuan52c2fba2016-05-20 17:02:48 -0700969
970 Args:
971 kwargs:
972 host_name: the hostname of the DUT to auto-update.
973 pid: the background process id of cros-update.
974 """
975 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800976 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
977 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700978
979 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -0800980 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
981 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700982
983 host_name = kwargs['host_name']
984 pid = kwargs['pid']
985 cros_update_progress.DelTrackStatusFile(host_name, pid)
xixuan3bc974e2016-10-18 17:21:43 -0700986 cros_update_progress.DelAUTempDirectory(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700987
988 @cherrypy.expose
989 def kill_au_proc(self, **kwargs):
990 """Kill CrOS auto-update process using given process id.
991
992 Args:
993 kwargs:
994 host_name: Kill all the CrOS auto-update process of this host.
995
996 Returns:
997 True if all processes are killed properly.
998 """
999 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001000 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1001 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001002
xixuan447ad9d2017-02-28 14:46:20 -08001003 cur_pid = kwargs.get('pid')
1004
xixuan52c2fba2016-05-20 17:02:48 -07001005 host_name = kwargs['host_name']
xixuan3bc974e2016-10-18 17:21:43 -07001006 track_log_list = cros_update_progress.GetAllTrackStatusFileByHostName(
1007 host_name)
xixuan52c2fba2016-05-20 17:02:48 -07001008 for log in track_log_list:
1009 # The track log's full path is: path/host_name_pid.log
1010 # Use splitext to remove file extension, then parse pid from the
1011 # filename.
Congbin Guo3afae6c2019-08-13 16:29:42 -07001012 pid = os.path.splitext(os.path.basename(log))[0][len(host_name) + 1:]
xixuan447ad9d2017-02-28 14:46:20 -08001013 _clear_process(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -07001014
xixuan447ad9d2017-02-28 14:46:20 -08001015 if cur_pid:
1016 _clear_process(host_name, cur_pid)
xixuan52c2fba2016-05-20 17:02:48 -07001017
1018 return 'True'
1019
1020 @cherrypy.expose
1021 def collect_cros_au_log(self, **kwargs):
1022 """Collect CrOS auto-update log.
1023
1024 Args:
1025 kwargs:
1026 host_name: the hostname of the DUT to auto-update.
1027 pid: the background process id of cros-update.
1028
1029 Returns:
David Haddock9f459632017-05-11 14:45:46 -07001030 A dictionary containing the execute log file and any hostlog files.
xixuan52c2fba2016-05-20 17:02:48 -07001031 """
1032 if 'host_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001033 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1034 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001035
1036 if 'pid' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001037 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1038 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -07001039
1040 host_name = kwargs['host_name']
1041 pid = kwargs['pid']
xixuan3bc974e2016-10-18 17:21:43 -07001042
1043 # Fetch the execute log recorded by cros_update_progress.
xixuan1bbfaba2016-10-13 17:53:22 -07001044 au_log = cros_update_progress.ReadExecuteLogFile(host_name, pid)
1045 cros_update_progress.DelExecuteLogFile(host_name, pid)
David Haddock9f459632017-05-11 14:45:46 -07001046 # Fetch the cros_au host_logs if they exist
1047 au_hostlogs = cros_update_progress.ReadAUHostLogFiles(host_name, pid)
1048 return json.dumps({'cros_au_log': au_log, 'host_logs': au_hostlogs})
xixuan1bbfaba2016-10-13 17:53:22 -07001049
xixuan52c2fba2016-05-20 17:02:48 -07001050 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -08001051 def locate_file(self, **kwargs):
1052 """Get the path to the given file name.
1053
1054 This method looks up the given file name inside specified build artifacts.
1055 One use case is to help caller to locate an apk file inside a build
1056 artifact. The location of the apk file could be different based on the
1057 branch and target.
1058
1059 Args:
1060 file_name: Name of the file to look for.
1061 artifacts: A list of artifact names to search for the file.
1062
1063 Returns:
1064 Path to the file with the given name. It's relative to the folder for the
1065 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -08001066 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001067 if is_deprecated_server():
1068 raise DeprecatedRPCError('locate_file')
1069
Dan Shi2f136862016-02-11 15:38:38 -08001070 dl, _ = _get_downloader_and_factory(kwargs)
1071 try:
Joe Brennan1691f8e2017-03-15 15:53:36 -07001072 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 15:38:38 -08001073 artifacts = kwargs['artifacts']
1074 except KeyError:
Amin Hassanid4e35392019-10-03 11:02:44 -07001075 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001076 '`file_name` and `artifacts` are required to search '
1077 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 15:38:38 -08001078 build_path = dl.GetBuildDir()
1079 for artifact in artifacts:
1080 # Get the unzipped folder of the artifact. If it's not defined in
1081 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
1082 # directory directly.
1083 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
1084 artifact_path = os.path.join(build_path, folder)
1085 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 15:53:36 -07001086 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 15:38:38 -08001087 return os.path.relpath(os.path.join(root, file_name), build_path)
Amin Hassanid4e35392019-10-03 11:02:44 -07001088 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001089 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 15:38:38 -08001090
1091 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -08001092 def setup_telemetry(self, **kwargs):
1093 """Extracts and sets up telemetry
1094
1095 This method goes through the telemetry deps packages, and stages them on
1096 the devserver to be used by the drones and the telemetry tests.
1097
1098 Args:
1099 archive_url: Google Storage URL for the build.
1100
1101 Returns:
1102 Path to the source folder for the telemetry codebase once it is staged.
1103 """
Gabe Black3b567202015-09-23 14:07:59 -07001104 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 13:39:18 -08001105
Gabe Black3b567202015-09-23 14:07:59 -07001106 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -08001107 deps_path = os.path.join(build_path, 'autotest/packages')
1108 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
1109 src_folder = os.path.join(telemetry_path, 'src')
1110
1111 with self._telemetry_lock_dict.lock(telemetry_path):
1112 if os.path.exists(src_folder):
1113 # Telemetry is already fully stage return
1114 return src_folder
1115
1116 common_util.MkDirP(telemetry_path)
1117
1118 # Copy over the required deps tar balls to the telemetry directory.
1119 for dep in TELEMETRY_DEPS:
1120 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -07001121 if not os.path.exists(dep_path):
1122 # This dep does not exist (could be new), do not extract it.
1123 continue
Simran Basi4baad082013-02-14 13:39:18 -08001124 try:
1125 common_util.ExtractTarball(dep_path, telemetry_path)
1126 except common_util.CommonUtilError as e:
1127 shutil.rmtree(telemetry_path)
Amin Hassanid4e35392019-10-03 11:02:44 -07001128 raise DevServerError(str(e))
Simran Basi4baad082013-02-14 13:39:18 -08001129
1130 # By default all the tarballs extract to test_src but some parts of
1131 # the telemetry code specifically hardcoded to exist inside of 'src'.
1132 test_src = os.path.join(telemetry_path, 'test_src')
1133 try:
1134 shutil.move(test_src, src_folder)
1135 except shutil.Error:
1136 # This can occur if src_folder already exists. Remove and retry move.
1137 shutil.rmtree(src_folder)
Amin Hassanid4e35392019-10-03 11:02:44 -07001138 raise DevServerError(
Gabe Black3b567202015-09-23 14:07:59 -07001139 'Failure in telemetry setup for build %s. Appears that the '
1140 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -08001141
1142 return src_folder
1143
1144 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -08001145 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -07001146 """Symbolicates a minidump using pre-downloaded symbols, returns it.
1147
1148 Callers will need to POST to this URL with a body of MIME-type
1149 "multipart/form-data".
1150 The body should include a single argument, 'minidump', containing the
1151 binary-formatted minidump to symbolicate.
1152
Chris Masone816e38c2012-05-02 12:22:36 -07001153 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -08001154 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -07001155 minidump: The binary minidump file to symbolicate.
1156 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001157 if is_deprecated_server():
1158 raise DeprecatedRPCError('symbolicate_dump')
1159
Chris Sosa76e44b92013-01-31 12:11:38 -08001160 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -07001161 # Try debug.tar.xz first, then debug.tgz
1162 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
1163 kwargs['artifacts'] = artifact
1164 dl = _get_downloader(kwargs)
1165
1166 try:
1167 if self.stage(**kwargs) == 'Success':
1168 break
1169 except build_artifact.ArtifactDownloadError:
1170 continue
1171 else:
Amin Hassanid4e35392019-10-03 11:02:44 -07001172 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001173 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -08001174
Chris Masone816e38c2012-05-02 12:22:36 -07001175 to_return = ''
1176 with tempfile.NamedTemporaryFile() as local:
1177 while True:
1178 data = minidump.file.read(8192)
1179 if not data:
1180 break
1181 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -08001182
Chris Masone816e38c2012-05-02 12:22:36 -07001183 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -08001184
Gabe Black3b567202015-09-23 14:07:59 -07001185 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -08001186
xixuanab744382017-04-27 10:41:27 -07001187 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 12:11:38 -08001188 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 10:41:27 -07001189 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 12:11:38 -08001190 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1191
Chris Masone816e38c2012-05-02 12:22:36 -07001192 to_return, error_text = stackwalk.communicate()
1193 if stackwalk.returncode != 0:
Amin Hassanid4e35392019-10-03 11:02:44 -07001194 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001195 "Can't generate stack trace: %s (rc=%d)" % (error_text,
1196 stackwalk.returncode))
Chris Masone816e38c2012-05-02 12:22:36 -07001197
1198 return to_return
1199
1200 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001201 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -04001202 """Return a string representing the latest build for a given target.
1203
1204 Args:
1205 target: The build target, typically a combination of the board and the
1206 type of build e.g. x86-mario-release.
1207 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
1208 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -08001209
Scott Zawalski16954532012-03-20 15:31:36 -04001210 Returns:
1211 A string representation of the latest build if one exists, i.e.
1212 R19-1993.0.0-a1-b1480.
1213 An empty string if no latest could be found.
1214 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001215 if is_deprecated_server():
1216 raise DeprecatedRPCError('latestbuild')
1217
Don Garrettf84631a2014-01-07 18:21:26 -08001218 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -04001219 return _PrintDocStringAsHTML(self.latestbuild)
1220
Don Garrettf84631a2014-01-07 18:21:26 -08001221 if 'target' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001222 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1223 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -07001224
1225 if _is_android_build_request(kwargs):
1226 branch = kwargs.get('branch', None)
1227 target = kwargs.get('target', None)
1228 if not target or not branch:
Amin Hassanid4e35392019-10-03 11:02:44 -07001229 raise DevServerError('Both target and branch must be specified to query'
1230 ' for the latest Android build.')
Dan Shi61305df2015-10-26 16:52:35 -07001231 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
1232
Scott Zawalski16954532012-03-20 15:31:36 -04001233 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001234 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-07 18:21:26 -08001235 updater.static_dir, kwargs['target'],
1236 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -07001237 except common_util.CommonUtilError as errmsg:
Amin Hassani722e0962019-11-15 15:45:31 -08001238 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1239 str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -04001240
1241 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -07001242 def list_suite_controls(self, **kwargs):
1243 """Return a list of contents of all known control files.
1244
1245 Example URL:
1246 To List all control files' content:
1247 http://dev-server/list_suite_controls?suite_name=bvt&
1248 build=daisy_spring-release/R29-4279.0.0
1249
1250 Args:
1251 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
1252 suite_name: List the control files belonging to that suite.
1253
1254 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -07001255 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -07001256 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001257 if is_deprecated_server():
1258 raise DeprecatedRPCError('list_suite_controls')
1259
xixuan7efd0002016-04-14 15:34:01 -07001260 if not kwargs:
1261 return _PrintDocStringAsHTML(self.controlfiles)
1262
1263 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001264 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1265 'Error: build= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001266
1267 if 'suite_name' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001268 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1269 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001270
1271 control_file_list = [
1272 line.rstrip() for line in common_util.GetControlFileListForSuite(
1273 updater.static_dir, kwargs['build'],
1274 kwargs['suite_name']).splitlines()]
1275
Dan Shia1cd6522016-04-18 16:07:21 -07001276 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -07001277 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -07001278 control_file_content_dict[control_path] = (common_util.GetControlFile(
xixuan7efd0002016-04-14 15:34:01 -07001279 updater.static_dir, kwargs['build'], control_path))
1280
Dan Shia1cd6522016-04-18 16:07:21 -07001281 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -07001282
1283 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001284 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -05001285 """Return a control file or a list of all known control files.
1286
1287 Example URL:
1288 To List all control files:
beepsbd337242013-07-09 22:44:06 -07001289 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
1290 To List all control files for, say, the bvt suite:
1291 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -05001292 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001293 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 -05001294
1295 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001296 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -05001297 control_path: If you want the contents of a control file set this
1298 to the path. E.g. client/site_tests/sleeptest/control
1299 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -07001300 suite_name: If control_path is not specified but a suite_name is
1301 specified, list the control files belonging to that suite instead of
1302 all control files. The empty string for suite_name will list all control
1303 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -08001304
Scott Zawalski4647ce62012-01-03 17:17:28 -05001305 Returns:
1306 Contents of a control file if control_path is provided.
1307 A list of control files if no control_path is provided.
1308 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001309 if is_deprecated_server():
1310 raise DeprecatedRPCError('controlfiles')
1311
Don Garrettf84631a2014-01-07 18:21:26 -08001312 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -05001313 return _PrintDocStringAsHTML(self.controlfiles)
1314
Don Garrettf84631a2014-01-07 18:21:26 -08001315 if 'build' not in kwargs:
Amin Hassani722e0962019-11-15 15:45:31 -08001316 raise DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
1317 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -05001318
Don Garrettf84631a2014-01-07 18:21:26 -08001319 if 'control_path' not in kwargs:
1320 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -07001321 return common_util.GetControlFileListForSuite(
Don Garrettf84631a2014-01-07 18:21:26 -08001322 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -07001323 else:
1324 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-07 18:21:26 -08001325 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -05001326 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001327 return common_util.GetControlFile(
Don Garrettf84631a2014-01-07 18:21:26 -08001328 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -08001329
1330 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -07001331 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001332 """Translates an xBuddy path to a real path to artifact if it exists.
1333
1334 Args:
Simran Basi99e63c02014-05-20 10:39:52 -07001335 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
1336 Local searches the devserver's static directory. Remote searches a
1337 Google Storage image archive.
1338
1339 Kwargs:
1340 image_dir: Google Storage image archive to search in if requesting a
1341 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001342
1343 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -07001344 String in the format of build_id/artifact as stored on the local server
1345 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001346 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001347 if is_deprecated_server():
1348 raise DeprecatedRPCError('xbuddy_translate')
1349
Simran Basi99e63c02014-05-20 10:39:52 -07001350 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -07001351 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001352 response = os.path.join(build_id, filename)
1353 _Log('Path translation requested, returning: %s', response)
1354 return response
1355
1356 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -07001357 def xbuddy(self, *args, **kwargs):
1358 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -07001359
1360 Args:
joycheneaf4cfc2013-07-02 08:38:57 -07001361 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -07001362 components of the path. The path can be understood as
1363 "{local|remote}/build_id/artifact" where build_id is composed of
1364 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -07001365
joychen121fc9b2013-08-02 14:30:30 -07001366 The first path element is optional, and can be "remote" or "local"
1367 If local (the default), devserver will not attempt to access Google
1368 Storage, and will only search the static directory for the files.
1369 If remote, devserver will try to obtain the artifact off GS if it's
1370 not found locally.
1371 The board is the familiar board name, optionally suffixed.
1372 The version can be the google storage version number, and may also be
1373 any of a number of xBuddy defined version aliases that will be
1374 translated into the latest built image that fits the description.
1375 Defaults to latest.
1376 The artifact is one of a number of image or artifact aliases used by
1377 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -07001378
1379 Kwargs:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001380 for_update: {true|false}
Amin Hassanie9ffb862019-09-25 17:10:40 -07001381 if true, prepares the update payloads for the image,
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001382 and returns the update uri to pass to the
1383 update_engine_client.
joychen3cb228e2013-06-12 12:13:13 -07001384 return_dir: {true|false}
1385 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001386 relative_path: {true|false}
1387 if set to true, returns the relative path to the payload
1388 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -07001389 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -07001390 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -07001391 or
joycheneaf4cfc2013-07-02 08:38:57 -07001392 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -07001393
1394 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001395 If |for_update|, returns a redirect to the image or update file
1396 on the devserver. E.g.,
1397 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
1398 chromium-test-image.bin
1399 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1400 http://host:port/static/x86-generic-release/R26-4000.0.0/
1401 If |relative_path| is true, return a relative path the folder where the
1402 payloads are. E.g.,
1403 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -07001404 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001405 if is_deprecated_server():
1406 raise DeprecatedRPCError('xbuddy')
1407
Chris Sosa75490802013-09-30 17:21:45 -07001408 boolean_string = kwargs.get('for_update')
1409 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001410 boolean_string = kwargs.get('return_dir')
1411 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1412 boolean_string = kwargs.get('relative_path')
1413 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001414
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001415 if return_dir and relative_path:
Amin Hassani722e0962019-11-15 15:45:31 -08001416 raise DevServerHTTPError(
Amin Hassanid4e35392019-10-03 11:02:44 -07001417 http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001418 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001419
1420 # For updates, we optimize downloading of test images.
1421 file_name = None
1422 build_id = None
1423 if for_update:
1424 try:
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001425 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-09-30 17:21:45 -07001426 except build_artifact.ArtifactDownloadError:
1427 build_id = None
1428
1429 if not build_id:
1430 build_id, file_name = self._xbuddy.Get(args)
1431
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001432 if for_update:
Amin Hassanie9ffb862019-09-25 17:10:40 -07001433 _Log('Payloads requested.')
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001434 # Forces payload to be in cache and symlinked into build_id dir.
Amin Hassanie9ffb862019-09-25 17:10:40 -07001435 updater.GetUpdateForLabel(build_id)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001436
1437 response = None
1438 if return_dir:
1439 response = os.path.join(cherrypy.request.base, 'static', build_id)
1440 _Log('Directory requested, returning: %s', response)
1441 elif relative_path:
1442 response = build_id
1443 _Log('Relative path requested, returning: %s', response)
1444 elif for_update:
1445 response = os.path.join(cherrypy.request.base, 'update', build_id)
1446 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001447 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001448 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001449 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001450 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001451 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001452
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001453 return response
1454
joychen3cb228e2013-06-12 12:13:13 -07001455 @cherrypy.expose
1456 def xbuddy_list(self):
1457 """Lists the currently available images & time since last access.
1458
Gilad Arnold452fd272014-02-04 11:09:28 -08001459 Returns:
1460 A string representation of a list of tuples [(build_id, time since last
1461 access),...]
joychen3cb228e2013-06-12 12:13:13 -07001462 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001463 if is_deprecated_server():
1464 raise DeprecatedRPCError('xbuddy')
1465
joychen3cb228e2013-06-12 12:13:13 -07001466 return self._xbuddy.List()
1467
1468 @cherrypy.expose
1469 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001470 """Returns the number of images cached by xBuddy."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001471 if is_deprecated_server():
1472 raise DeprecatedRPCError('xbuddy_capacity')
1473
joychen3cb228e2013-06-12 12:13:13 -07001474 return self._xbuddy.Capacity()
1475
1476 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001477 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001478 """Presents a welcome message and documentation links."""
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001479 if is_deprecated_server():
1480 raise DeprecatedRPCError('index')
1481
Congbin Guo6bc32182019-08-20 17:54:30 -07001482 html_template = (
1483 'Welcome to the Dev Server!<br>\n'
1484 '<br>\n'
1485 'Here are the available methods, click for documentation:<br>\n'
1486 '<br>\n'
1487 '%s')
1488
1489 exposed_methods = []
1490 for app in cherrypy.tree.apps.values():
1491 exposed_methods += _FindExposedMethods(
1492 app.root, app.script_name.lstrip('/'),
1493 unlisted=self._UNLISTED_METHODS)
1494
1495 return html_template % '<br>\n'.join(
1496 ['<a href=doc/%s>%s</a>' % (name, name)
1497 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001498
1499 @cherrypy.expose
1500 def doc(self, *args):
1501 """Shows the documentation for available methods / URLs.
1502
Amin Hassani08e42d22019-06-03 00:31:30 -07001503 Examples:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001504 http://myhost/doc/update
1505 """
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001506 if is_deprecated_server():
1507 raise DeprecatedRPCError('doc')
1508
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001509 name = '/'.join(args)
Congbin Guo6bc32182019-08-20 17:54:30 -07001510 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001511 if not method:
Amin Hassanid4e35392019-10-03 11:02:44 -07001512 raise DevServerError("No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001513 if not method.__doc__:
Amin Hassanid4e35392019-10-03 11:02:44 -07001514 raise DevServerError("No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001515 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001516
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07001517 @cherrypy.expose
Amin Hassani6eec8792020-01-09 14:06:48 -08001518 def update(self, *args, **kwargs):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001519 """Handles an update check from a Chrome OS client.
1520
1521 The HTTP request should contain the standard Omaha-style XML blob. The URL
1522 line may contain an additional intermediate path to the update payload.
1523
joychen121fc9b2013-08-02 14:30:30 -07001524 This request can be handled in one of 4 ways, depending on the devsever
1525 settings and intermediate path.
joychenb0dfe552013-07-30 10:02:06 -07001526
Amin Hassanie9ffb862019-09-25 17:10:40 -07001527 1. No intermediate path. DEPRECATED
joychen121fc9b2013-08-02 14:30:30 -07001528
1529 2. Path explicitly invokes XBuddy
1530 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1531 with 'xbuddy'. This path is then used to acquire an image binary for the
1532 devserver to generate an update payload from. Devserver then serves this
1533 payload.
1534
1535 3. Path is left for the devserver to interpret.
1536 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1537 to generate a payload from the test image in that directory and serve it.
1538
joychen121fc9b2013-08-02 14:30:30 -07001539 Examples:
joychen121fc9b2013-08-02 14:30:30 -07001540 2. Explicitly invoke xbuddy
1541 update_engine_client --omaha_url=
1542 http://myhost/update/xbuddy/remote/board/version/dev
1543 This would go to GS to download the dev image for the board, from which
1544 the devserver would generate a payload to serve.
1545
1546 3. Give a path for devserver to interpret
1547 update_engine_client --omaha_url=http://myhost/update/some/random/path
1548 This would attempt, in order to:
1549 a) Generate an update from a test image binary if found in
1550 static_dir/some/random/path.
1551 b) Serve an update payload found in static_dir/some/random/path.
1552 c) Hope that some/random/path takes the form "board/version" and
1553 and attempt to download an update payload for that board/version
1554 from GS.
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001555 """
joychen121fc9b2013-08-02 14:30:30 -07001556 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -08001557 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -07001558 data = cherrypy.request.rfile.read(body_length)
Amin Hassani6aa075c2020-02-21 18:36:44 +00001559
Amin Hassani6eec8792020-01-09 14:06:48 -08001560 return updater.HandleUpdatePing(data, label, **kwargs)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001561
Dan Shif5ce2de2013-04-25 16:06:32 -07001562
Chris Sosadbc20082012-12-10 13:39:11 -08001563def _CleanCache(cache_dir, wipe):
1564 """Wipes any excess cached items in the cache_dir.
1565
1566 Args:
1567 cache_dir: the directory we are wiping from.
1568 wipe: If True, wipe all the contents -- not just the excess.
1569 """
1570 if wipe:
1571 # Clear the cache and exit on error.
1572 cmd = 'rm -rf %s/*' % cache_dir
1573 if os.system(cmd) != 0:
1574 _Log('Failed to clear the cache with %s' % cmd)
1575 sys.exit(1)
1576 else:
1577 # Clear all but the last N cached updates
1578 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1579 (cache_dir, CACHED_ENTRIES))
1580 if os.system(cmd) != 0:
1581 _Log('Failed to clean up old delta cache files with %s' % cmd)
1582 sys.exit(1)
1583
1584
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001585def _AddTestingOptions(parser):
1586 group = optparse.OptionGroup(
1587 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1588 'developers writing integration tests utilizing the devserver. They are '
1589 'not intended to be really used outside the scope of someone '
1590 'knowledgable about the test.')
1591 group.add_option('--exit',
1592 action='store_true',
Amin Hassanie9ffb862019-09-25 17:10:40 -07001593 help='do not start the server (yet clear cache)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001594 group.add_option('--host_log',
1595 action='store_true', default=False,
1596 help='record history of host update events (/api/hostlog)')
1597 group.add_option('--max_updates',
Gabe Black3b567202015-09-23 14:07:59 -07001598 metavar='NUM', default=-1, type='int',
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001599 help='maximum number of update checks handled positively '
1600 '(default: unlimited)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001601 group.add_option('--proxy_port',
1602 metavar='PORT', default=None, type='int',
1603 help='port to have the client connect to -- basically the '
1604 'devserver lies to the update to tell it to get the payload '
1605 'from a different port that will proxy the request back to '
1606 'the devserver. The proxy must be managed outside the '
1607 'devserver.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001608 parser.add_option_group(group)
1609
1610
1611def _AddUpdateOptions(parser):
1612 group = optparse.OptionGroup(
1613 parser, 'Autoupdate Options', 'These options can be used to change '
Amin Hassanie9ffb862019-09-25 17:10:40 -07001614 'how the devserver serve update payloads. Please '
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001615 'note that all of these option affect how a payload is generated and so '
1616 'do not work in archive-only mode.')
Amin Hassani6eec8792020-01-09 14:06:48 -08001617 # TODO(crbug/1004487): Deprecate critical_update.
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001618 group.add_option('--critical_update',
1619 action='store_true', default=False,
1620 help='Present update payload as critical')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001621 group.add_option('--payload',
1622 metavar='PATH',
1623 help='use the update payload from specified directory '
1624 '(update.gz).')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001625 parser.add_option_group(group)
1626
1627
1628def _AddProductionOptions(parser):
1629 group = optparse.OptionGroup(
1630 parser, 'Advanced Server Options', 'These options can be used to changed '
1631 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001632 group.add_option('--clear_cache',
1633 action='store_true', default=False,
1634 help='At startup, removes all cached entries from the'
Amin Hassanid4e35392019-10-03 11:02:44 -07001635 "devserver's cache.")
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001636 group.add_option('--logfile',
1637 metavar='PATH',
1638 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001639 group.add_option('--pidfile',
1640 metavar='PATH',
1641 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001642 group.add_option('--portfile',
1643 metavar='PATH',
1644 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001645 group.add_option('--production',
1646 action='store_true', default=False,
1647 help='have the devserver use production values when '
1648 'starting up. This includes using more threads and '
1649 'performing less logging.')
1650 parser.add_option_group(group)
1651
1652
Paul Hobbsef4e0702016-06-27 17:01:42 -07001653def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001654 """Create a LogHandler instance used to log all messages."""
1655 hdlr_cls = handlers.TimedRotatingFileHandler
1656 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001657 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001658 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001659 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001660 return hdlr
1661
1662
Chris Sosacde6bf42012-05-31 18:36:39 -07001663def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001664 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001665 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001666
1667 # get directory that the devserver is run from
1668 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001669 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001670 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001671 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001672 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001673 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001674 parser.add_option('--port',
1675 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001676 help=('port for the dev server to use; if zero, binds to '
1677 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001678 parser.add_option('-t', '--test_image',
1679 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001680 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001681 parser.add_option('-x', '--xbuddy_manage_builds',
1682 action='store_true',
1683 default=False,
1684 help='If set, allow xbuddy to manage images in'
1685 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001686 parser.add_option('-a', '--android_build_credential',
1687 default=None,
1688 help='Path to a json file which contains the credential '
1689 'needed to access Android builds.')
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001690 parser.add_option('--infra_removal',
1691 action='store_true', default=False,
1692 help='If option is present, some RPCs will be disabled to '
1693 'help with infra removal efforts. See '
1694 'go/devserver-deprecation')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001695 _AddProductionOptions(parser)
1696 _AddUpdateOptions(parser)
1697 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001698 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001699
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001700 # Handle options that must be set globally in cherrypy. Do this
1701 # work up front, because calls to _Log() below depend on this
1702 # initialization.
1703 if options.production:
1704 cherrypy.config.update({'environment': 'production'})
Sanika Kulkarnid4496fd2020-02-04 17:26:25 -08001705 cherrypy.config.update({'infra_removal': options.infra_removal})
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001706 if not options.logfile:
1707 cherrypy.config.update({'log.screen': True})
1708 else:
1709 cherrypy.config.update({'log.error_file': '',
1710 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001711 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001712 # Pylint can't seem to process these two calls properly
1713 # pylint: disable=E1101
1714 cherrypy.log.access_log.addHandler(hdlr)
1715 cherrypy.log.error_log.addHandler(hdlr)
1716 # pylint: enable=E1101
1717
joychened64b222013-06-21 16:39:34 -07001718 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001719 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001720
joychened64b222013-06-21 16:39:34 -07001721 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001722 # If our devserver is only supposed to serve payloads, we shouldn't be
1723 # mucking with the cache at all. If the devserver hadn't previously
1724 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001725 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001726 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001727 else:
1728 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001729
Chris Sosadbc20082012-12-10 13:39:11 -08001730 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001731 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001732
Amin Hassanie9ffb862019-09-25 17:10:40 -07001733 _xbuddy = xbuddy.XBuddy(manage_builds=options.xbuddy_manage_builds,
joychen121fc9b2013-08-02 14:30:30 -07001734 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001735 if options.clear_cache and options.xbuddy_manage_builds:
1736 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001737
Chris Sosa6a3697f2013-01-29 16:44:43 -08001738 # We allow global use here to share with cherrypy classes.
1739 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001740 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001741 updater = autoupdate.Autoupdate(
joychen121fc9b2013-08-02 14:30:30 -07001742 _xbuddy,
joychened64b222013-06-21 16:39:34 -07001743 static_dir=options.static_dir,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001744 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001745 proxy_port=options.proxy_port,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001746 critical_update=options.critical_update,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001747 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001748 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001749 )
Chris Sosa7c931362010-10-11 19:49:01 -07001750
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001751 if options.exit:
1752 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001753
joychen3cb228e2013-06-12 12:13:13 -07001754 dev_server = DevServerRoot(_xbuddy)
Congbin Guo3afae6c2019-08-13 16:29:42 -07001755 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 12:13:13 -07001756
Amin Hassanic5af4262019-11-13 13:37:20 -08001757 # Patch CherryPy to support binding to any available port (--port=0) only for
1758 # cherrypy versions smaller or equal to 3.2.2.
1759 #
1760 # TODO(crbug/1006305): Remove this once we have deprecated omaha_devserver.py
1761 # in the autotests as that is the only use case.
1762 #
1763 # pylint: disable=no-member
1764 if (distutils.version.StrictVersion(cherrypy.__version__) <=
1765 distutils.version.StrictVersion('3.2.2')):
1766 cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy)
1767 # pylint: enable=no-member
1768
Chris Sosa855b8932013-08-21 13:24:55 -07001769 if options.pidfile:
1770 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1771
Gilad Arnold11fbef42014-02-10 11:04:13 -08001772 if options.portfile:
1773 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1774
Dan Shiafd5c6c2016-01-07 10:27:03 -08001775 if (options.android_build_credential and
1776 os.path.exists(options.android_build_credential)):
1777 try:
1778 with open(options.android_build_credential) as f:
1779 android_build.BuildAccessor.credential_info = json.load(f)
1780 except ValueError as e:
1781 _Log('Failed to load the android build credential: %s. Error: %s.' %
1782 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 16:29:42 -07001783
1784 cherrypy.tree.mount(health_checker_app, '/check_health',
1785 config=health_checker.get_config())
joychen3cb228e2013-06-12 12:13:13 -07001786 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001787
1788
1789if __name__ == '__main__':
1790 main()