blob: 9d77be04d4ea609c6552ff339b8ed77b0d4af07a [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
11systems. It mainly operates in two modes:
12
131) archive mode: In this mode, the devserver is configured to stage and
14serve artifacts from Google Storage using the credentials provided to it before
15it is run. The easiest way to understand this is that the devserver is
16functioning as a local cache for artifacts produced and uploaded by build
17servers. Users of this form of devserver can either download the artifacts
18from the devservers static directory OR use the update RPC to perform a
19system-wide autoupdate. Archive mode is always active.
20
212) artifact-generation mode: in this mode, the devserver will attempt to
22generate update payloads and build artifacts when requested. This mode only
23works in the Chromium OS chroot as it uses build tools only present in the
24chroot (emerge, cros_generate_update_payload, etc.). By default, when a device
25requests an update from this form of devserver, the devserver will attempt to
26discover if a more recent build of the board has been built by the developer
Mike Frysingeraa0cb102019-02-25 01:09:19 -050027and generate a payload that the requested system can autoupdate to.
Chris Sosa3ae4dc12013-03-29 11:47:00 -070028
29For autoupdates, there are many more advanced options that can help specify
30how to update and which payload to give to a requester.
31"""
32
Gabe Black3b567202015-09-23 14:07:59 -070033from __future__ import print_function
Chris Sosa7c931362010-10-11 19:49:01 -070034
Amin Hassani08e42d22019-06-03 00:31:30 -070035import httplib
Gilad Arnold55a2a372012-10-02 09:46:32 -070036import json
David Riley2fcb0122017-11-02 11:25:39 -070037import optparse # pylint: disable=deprecated-module
rtc@google.comded22402009-10-26 22:36:21 +000038import os
Scott Zawalski4647ce62012-01-03 17:17:28 -050039import re
Simran Basi4baad082013-02-14 13:39:18 -080040import shutil
xixuan52c2fba2016-05-20 17:02:48 -070041import signal
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -080042import socket
Chris Masone816e38c2012-05-02 12:22:36 -070043import subprocess
J. Richard Barnette3d977b82013-04-23 11:05:19 -070044import sys
Chris Masone816e38c2012-05-02 12:22:36 -070045import tempfile
Dan Shi59ae7092013-06-04 14:37:27 -070046import threading
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070047import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070048from logging import handlers
49
50import cherrypy
David Riley2fcb0122017-11-02 11:25:39 -070051# pylint: disable=no-name-in-module
Chris Sosa855b8932013-08-21 13:24:55 -070052from cherrypy import _cplogging as cplogging
Congbin Guo3afae6c2019-08-13 16:29:42 -070053from cherrypy.process import plugins # pylint: disable=import-error
David Riley2fcb0122017-11-02 11:25:39 -070054# pylint: enable=no-name-in-module
rtc@google.comded22402009-10-26 22:36:21 +000055
Richard Barnettedf35c322017-08-18 17:02:13 -070056# This must happen before any local modules get a chance to import
57# anything from chromite. Otherwise, really bad things will happen, and
58# you will _not_ understand why.
Congbin Guo3afae6c2019-08-13 16:29:42 -070059import setup_chromite # pylint: disable=unused-import
Richard Barnettedf35c322017-08-18 17:02:13 -070060
Dan Shi2f136862016-02-11 15:38:38 -080061import artifact_info
Congbin Guo3afae6c2019-08-13 16:29:42 -070062import autoupdate
Chris Sosa75490802013-09-30 17:21:45 -070063import build_artifact
Gilad Arnold11fbef42014-02-10 11:04:13 -080064import cherrypy_ext
Gilad Arnoldc65330c2012-09-20 15:17:48 -070065import common_util
Simran Basief83d6a2014-08-28 14:32:01 -070066import devserver_constants
Chris Sosa47a7d4e2012-03-28 11:26:55 -070067import downloader
Congbin Guo3afae6c2019-08-13 16:29:42 -070068import health_checker
Gilad Arnoldc65330c2012-09-20 15:17:48 -070069import log_util
joychen3cb228e2013-06-12 12:13:13 -070070import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070071
Gilad Arnoldc65330c2012-09-20 15:17:48 -070072# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080073def _Log(message, *args):
74 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 15:46:22 -070075
Dan Shi94dcbe82015-06-08 20:51:13 -070076
xixuanac89ce82016-11-30 16:48:20 -080077# Use try-except to skip unneccesary import for simple use case, eg. running
78# devserver on host.
79try:
80 import cros_update
xixuanac89ce82016-11-30 16:48:20 -080081except ImportError as e:
82 _Log('cros_update cannot be imported: %r', e)
83 cros_update = None
xixuana4f4e712017-05-08 15:17:54 -070084
85try:
86 import cros_update_progress
87except ImportError as e:
88 _Log('cros_update_progress cannot be imported: %r', e)
xixuanac89ce82016-11-30 16:48:20 -080089 cros_update_progress = None
90
xixuanac89ce82016-11-30 16:48:20 -080091try:
Dan Shi72b16132015-10-08 12:10:33 -070092 import android_build
93except ImportError as e:
94 # Ignore android_build import failure. This is to support devserver running
95 # inside a ChromeOS device triggered by cros flash. Most ChromeOS test images
96 # do not have google-api-python-client module and they don't need to support
97 # Android updating, therefore, ignore the import failure here.
Dan Shi72b16132015-10-08 12:10:33 -070098 android_build = None
Frank Farzan40160872011-12-12 18:39:18 -080099
Chris Sosa417e55d2011-01-25 16:40:48 -0800100CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -0800101
Simran Basi4baad082013-02-14 13:39:18 -0800102TELEMETRY_FOLDER = 'telemetry_src'
103TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
104 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -0700105 'dep-chrome_test.tar.bz2',
106 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -0800107
Chris Sosa0356d3b2010-09-16 15:46:22 -0700108# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +0000109updater = None
rtc@google.comded22402009-10-26 22:36:21 +0000110
xixuan3d48bff2017-01-30 19:00:09 -0800111# Log rotation parameters. These settings correspond to twice a day once
112# devserver is started, with about two weeks (28 backup files) of old logs
113# kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700114#
xixuan3d48bff2017-01-30 19:00:09 -0800115# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700116# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-30 19:00:09 -0800117_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 16:29:42 -0700118_LOG_ROTATION_INTERVAL = 12 # hours
119_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-12 18:39:18 -0800120
xixuan52c2fba2016-05-20 17:02:48 -0700121# Auto-update parameters
122
123# Error msg for missing key in CrOS auto-update.
Xixuan Wu32af9f12017-11-13 14:11:44 -0800124KEY_ERROR_MSG = 'Key Error in RPC: %s= is required'
xixuan52c2fba2016-05-20 17:02:48 -0700125
126# Command of running auto-update.
127AUTO_UPDATE_CMD = '/usr/bin/python -u %s -d %s -b %s --static_dir %s'
128
129
Chris Sosa9164ca32012-03-28 11:04:50 -0700130class DevServerError(Exception):
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700131 """Exception class used by this module."""
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700132
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://'):
142 raise DevServerError("Archive URL isn't from Google Storage (%s) ." %
143 archive_url)
144
145 return archive_url.rstrip('/')
146 else:
147 raise DevServerError("Must specify an archive_url in the request")
148
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):
160 raise DevServerError('Local path %s must be a subdirectory of the static'
161 ' directory: %s' % (local_path, updater.static_dir))
162
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:
175 raise DevServerError('No artifacts specified.')
176
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:
219 raise DevServerError('Requires archive_url or local_path to be '
220 'specified.')
221 if archive_url and local_path:
222 raise DevServerError('archive_url and local_path can not both be '
223 'specified.')
224 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:
Dan Shi72b16132015-10-08 12:10:33 -0700235 raise DevServerError(
Dan Shi61305df2015-10-26 16:52:35 -0700236 'target, branch, build ID must all be specified for downloading '
237 '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
253 if (isinstance(dl, downloader.GoogleStorageDownloader) or
254 isinstance(dl, downloader.LocalDownloader)):
255 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:
259 raise DevServerError('Unrecognized value for downloader type: %s' %
260 type(dl))
261
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
Gilad Arnold748c8322012-10-12 09:51:35 -0700401def _GetExposedMethod(root, nested_member, ignored=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700402 """Returns a CherryPy-exposed method, if such exists.
403
404 Args:
405 root: the root object for searching
406 nested_member: a slash-joined path to the nested member
407 ignored: method paths to be ignored
Don Garrettf84631a2014-01-07 18:21:26 -0800408
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700409 Returns:
410 A function object corresponding to the path defined by |member_list| from
411 the |root| object, if the function is exposed and not ignored; None
412 otherwise.
413 """
Gilad Arnold748c8322012-10-12 09:51:35 -0700414 method = (not (ignored and nested_member in ignored) and
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700415 _GetRecursiveMemberObject(root, nested_member.split('/')))
Amin Hassani08e42d22019-06-03 00:31:30 -0700416 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700417 return method
418
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 = []
432 for member in sorted(root.__class__.__dict__.keys()):
433 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):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700439 method_list.append(prefixed_member)
440 else:
441 method_list += _FindExposedMethods(
442 member_obj, prefixed_member, unlisted)
443 return method_list
444
445
xixuan52c2fba2016-05-20 17:02:48 -0700446def _check_base_args_for_auto_update(kwargs):
xixuanac89ce82016-11-30 16:48:20 -0800447 """Check basic args required for auto-update.
448
449 Args:
450 kwargs: the parameters to be checked.
451
452 Raises:
453 DevServerHTTPError if required parameters don't exist in kwargs.
454 """
xixuan52c2fba2016-05-20 17:02:48 -0700455 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700456 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
457 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700458
459 if 'build_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700460 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
461 KEY_ERROR_MSG % 'build_name')
xixuan52c2fba2016-05-20 17:02:48 -0700462
463
464def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-11-30 16:48:20 -0800465 """Parse boolean arg from kwargs.
466
467 Args:
468 kwargs: the parameters to be checked.
469 key: the key to be parsed.
470
471 Returns:
472 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
473
474 Raises:
475 DevServerHTTPError if kwargs[key] is not a boolean variable.
476 """
xixuan52c2fba2016-05-20 17:02:48 -0700477 if key in kwargs:
478 if kwargs[key] == 'True':
479 return True
480 elif kwargs[key] == 'False':
481 return False
482 else:
483 raise common_util.DevServerHTTPError(
Amin Hassani08e42d22019-06-03 00:31:30 -0700484 httplib.INTERNAL_SERVER_ERROR,
xixuan52c2fba2016-05-20 17:02:48 -0700485 'The value for key %s is not boolean.' % key)
486 else:
487 return False
488
xixuan447ad9d2017-02-28 14:46:20 -0800489
xixuanac89ce82016-11-30 16:48:20 -0800490def _parse_string_arg(kwargs, key):
491 """Parse string arg from kwargs.
492
493 Args:
494 kwargs: the parameters to be checked.
495 key: the key to be parsed.
496
497 Returns:
498 The string value of kwargs[key], or None if key doesn't exist in kwargs.
499 """
500 if key in kwargs:
501 return kwargs[key]
502 else:
503 return None
504
xixuan447ad9d2017-02-28 14:46:20 -0800505
xixuanac89ce82016-11-30 16:48:20 -0800506def _build_uri_from_build_name(build_name):
507 """Get build url from a given build name.
508
509 Args:
510 build_name: the build name to be parsed, whose format is
511 'board/release_version'.
512
513 Returns:
514 The release_archive_url on Google Storage for this build name.
515 """
Amin Hassani08e42d22019-06-03 00:31:30 -0700516 # TODO(ahassani): This function doesn't seem to be used anywhere since its
517 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
518 # causing any runtime issues. So deprecate this in the future.
519 tokens = build_name.split('/')
520 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-20 17:02:48 -0700521
xixuan447ad9d2017-02-28 14:46:20 -0800522
523def _clear_process(host_name, pid):
524 """Clear AU process for given hostname and pid.
525
526 This clear includes:
527 1. kill process if it's alive.
528 2. delete the track status file of this process.
529 3. delete the executing log file of this process.
530
531 Args:
532 host_name: the host to execute auto-update.
533 pid: the background auto-update process id.
534 """
535 if cros_update_progress.IsProcessAlive(pid):
536 os.killpg(int(pid), signal.SIGKILL)
537
538 cros_update_progress.DelTrackStatusFile(host_name, pid)
539 cros_update_progress.DelExecuteLogFile(host_name, pid)
540
541
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700542class ApiRoot(object):
543 """RESTful API for Dev Server information."""
544 exposed = True
545
546 @cherrypy.expose
547 def hostinfo(self, ip):
548 """Returns a JSON dictionary containing information about the given ip.
549
Gilad Arnold1b908392012-10-05 11:36:27 -0700550 Args:
551 ip: address of host whose info is requested
Don Garrettf84631a2014-01-07 18:21:26 -0800552
Gilad Arnold1b908392012-10-05 11:36:27 -0700553 Returns:
554 A JSON dictionary containing all or some of the following fields:
555 last_event_type (int): last update event type received
556 last_event_status (int): last update event status received
557 last_known_version (string): last known version reported in update ping
558 forced_update_label (string): update label to force next update ping to
559 use, set by setnextupdate
560 See the OmahaEvent class in update_engine/omaha_request_action.h for
561 event type and status code definitions. If the ip does not exist an empty
562 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700563
Gilad Arnold1b908392012-10-05 11:36:27 -0700564 Example URL:
565 http://myhost/api/hostinfo?ip=192.168.1.5
566 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700567 return updater.HandleHostInfoPing(ip)
568
569 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800570 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700571 """Returns a JSON object containing a log of host event.
572
573 Args:
574 ip: address of host whose event log is requested, or `all'
Don Garrettf84631a2014-01-07 18:21:26 -0800575
Gilad Arnold1b908392012-10-05 11:36:27 -0700576 Returns:
577 A JSON encoded list (log) of dictionaries (events), each of which
578 containing a `timestamp' and other event fields, as described under
579 /api/hostinfo.
580
581 Example URL:
582 http://myhost/api/hostlog?ip=192.168.1.5
583 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800584 return updater.HandleHostLogPing(ip)
585
586 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700587 def setnextupdate(self, ip):
588 """Allows the response to the next update ping from a host to be set.
589
590 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 11:36:27 -0700591 /update command.
592 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700593 body_length = int(cherrypy.request.headers['Content-Length'])
594 label = cherrypy.request.rfile.read(body_length)
595
596 if label:
597 label = label.strip()
598 if label:
599 return updater.HandleSetUpdatePing(ip, label)
Amin Hassani08e42d22019-06-03 00:31:30 -0700600 raise common_util.DevServerHTTPError(httplib.BAD_REQUEST,
601 'No label provided.')
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700602
Gilad Arnold55a2a372012-10-02 09:46:32 -0700603 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800604 def fileinfo(self, *args):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700605 """Returns information about a given staged file.
606
607 Args:
Don Garrettf84631a2014-01-07 18:21:26 -0800608 args: path to the file inside the server's static staging directory
609
Gilad Arnold55a2a372012-10-02 09:46:32 -0700610 Returns:
611 A JSON encoded dictionary with information about the said file, which may
612 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700613 size (int): the file size in bytes
Gilad Arnold1b908392012-10-05 11:36:27 -0700614 sha256 (string): a base64 encoded SHA256 hash
615
616 Example URL:
617 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700618 """
Don Garrettf84631a2014-01-07 18:21:26 -0800619 file_path = os.path.join(updater.static_dir, *args)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700620 if not os.path.exists(file_path):
621 raise DevServerError('file not found: %s' % file_path)
622 try:
623 file_size = os.path.getsize(file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700624 file_sha256 = common_util.GetFileSha256(file_path)
625 except os.error, e:
626 raise DevServerError('failed to get info for file %s: %s' %
Gilad Arnolde74b3812013-04-22 11:27:38 -0700627 (file_path, e))
628
Gilad Arnolde74b3812013-04-22 11:27:38 -0700629 return json.dumps({
630 autoupdate.Autoupdate.SIZE_ATTR: file_size,
Gilad Arnolde74b3812013-04-22 11:27:38 -0700631 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
Gilad Arnolde74b3812013-04-22 11:27:38 -0700632 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700633
Chris Sosa76e44b92013-01-31 12:11:38 -0800634
David Rochberg7c79a812011-01-19 14:24:45 -0500635class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700636 """The Root Class for the Dev Server.
637
638 CherryPy works as follows:
639 For each method in this class, cherrpy interprets root/path
640 as a call to an instance of DevServerRoot->method_name. For example,
641 a call to http://myhost/build will call build. CherryPy automatically
642 parses http args and places them as keyword arguments in each method.
643 For paths http://myhost/update/dir1/dir2, you can use *args so that
644 cherrypy uses the update method and puts the extra paths in args.
645 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700646 # Method names that should not be listed on the index page.
647 _UNLISTED_METHODS = ['index', 'doc']
648
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700649 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700650
Dan Shi59ae7092013-06-04 14:37:27 -0700651 # Number of threads that devserver is staging images.
652 _staging_thread_count = 0
653 # Lock used to lock increasing/decreasing count.
654 _staging_thread_count_lock = threading.Lock()
655
joychen3cb228e2013-06-12 12:13:13 -0700656 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700657 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800658 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700659 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500660
Congbin Guo3afae6c2019-08-13 16:29:42 -0700661 @property
662 def staging_thread_count(self):
663 """Get the staging thread count."""
664 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700665
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700666 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500667 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700668 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700669 import builder
670 if self._builder is None:
671 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500672 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700673
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700674 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700675 def is_staged(self, **kwargs):
676 """Check if artifacts have been downloaded.
677
Congbin Guo3afae6c2019-08-13 16:29:42 -0700678 Examples:
679 To check if autotest and test_suites are staged:
680 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
681 artifacts=autotest,test_suites
682
Amin Hassani08e42d22019-06-03 00:31:30 -0700683 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700684 async: True to return without waiting for download to complete.
685 artifacts: Comma separated list of named artifacts to download.
686 These are defined in artifact_info and have their implementation
687 in build_artifact.py.
688 files: Comma separated list of file artifacts to stage. These
689 will be available as is in the corresponding static directory with no
690 custom post-processing.
691
Congbin Guo3afae6c2019-08-13 16:29:42 -0700692 Returns:
693 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700694 """
Gabe Black3b567202015-09-23 14:07:59 -0700695 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700696 response = str(dl.IsStaged(factory))
697 _Log('Responding to is_staged %s request with %r', kwargs, response)
698 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700699
Chris Sosa76e44b92013-01-31 12:11:38 -0800700 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800701 def list_image_dir(self, **kwargs):
702 """Take an archive url and list the contents in its staged directory.
703
Amin Hassani08e42d22019-06-03 00:31:30 -0700704 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800705 To list the contents of where this devserver should have staged
706 gs://image-archive/<board>-release/<build> call:
707 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
708
Congbin Guo3afae6c2019-08-13 16:29:42 -0700709 Args:
710 archive_url: Google Storage URL for the build.
711
Prashanth Ba06d2d22014-03-07 15:35:19 -0800712 Returns:
713 A string with information about the contents of the image directory.
714 """
Gabe Black3b567202015-09-23 14:07:59 -0700715 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800716 try:
Gabe Black3b567202015-09-23 14:07:59 -0700717 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800718 except build_artifact.ArtifactDownloadError as e:
719 return 'Cannot list the contents of staged artifacts. %s' % e
720 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700721 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800722 return image_dir_contents
723
724 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800725 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700726 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800727
Gabe Black3b567202015-09-23 14:07:59 -0700728 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700729 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700730 on the devserver. A call to this will attempt to cache non-specified
731 artifacts in the background for the given from the given URL following
732 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800733 artifacts is explicitly defined in the build_artifact module.
734
735 These artifacts will then be available from the static/ sub-directory of
736 the devserver.
737
Amin Hassani08e42d22019-06-03 00:31:30 -0700738 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800739 To download the autotest and test suites tarballs:
740 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
741 artifacts=autotest,test_suites
742 To download the full update payload:
743 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
744 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700745 To download just a file called blah.bin:
746 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
747 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800748
749 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700750 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800751
752 Note for this example, relative path is the archive_url stripped of its
753 basename i.e. path/ in the examples above. Specific example:
754
755 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
756
757 Will get staged to:
758
joychened64b222013-06-21 16:39:34 -0700759 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700760
761 Args:
762 archive_url: Google Storage URL for the build.
763 local_path: Local path for the build.
764 delete_source: Only meaningful with local_path. bool to indicate if the
765 source files should be deleted. This is especially useful when staging
766 a file locally in resource constrained environments as it allows us to
767 move the relevant files locally instead of copying them.
768 async: True to return without waiting for download to complete.
769 artifacts: Comma separated list of named artifacts to download.
770 These are defined in artifact_info and have their implementation
771 in build_artifact.py.
772 files: Comma separated list of files to stage. These
773 will be available as is in the corresponding static directory with no
774 custom post-processing.
775 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800776 """
Gabe Black3b567202015-09-23 14:07:59 -0700777 dl, factory = _get_downloader_and_factory(kwargs)
778
Dan Shi59ae7092013-06-04 14:37:27 -0700779 with DevServerRoot._staging_thread_count_lock:
780 DevServerRoot._staging_thread_count += 1
781 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800782 boolean_string = kwargs.get('clean')
783 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
784 if clean and os.path.exists(dl.GetBuildDir()):
785 _Log('Removing %s' % dl.GetBuildDir())
786 shutil.rmtree(dl.GetBuildDir())
Gabe Black3b567202015-09-23 14:07:59 -0700787 async = kwargs.get('async', False)
788 dl.Download(factory, async=async)
Dan Shi59ae7092013-06-04 14:37:27 -0700789 finally:
790 with DevServerRoot._staging_thread_count_lock:
791 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800792 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700793
794 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700795 def cros_au(self, **kwargs):
796 """Auto-update a CrOS DUT.
797
798 Args:
799 kwargs:
800 host_name: the hostname of the DUT to auto-update.
801 build_name: the build name for update the DUT.
802 force_update: Force an update even if the version installed is the
803 same. Default: False.
804 full_update: If True, do not run stateful update, directly force a full
805 reimage. If False, try stateful update first if the dut is already
806 installed with the same version.
807 async: Whether the auto_update function is ran in the background.
David Rileyee75de22017-11-02 10:48:15 -0700808 quick_provision: Whether the quick provision path is attempted first.
xixuan52c2fba2016-05-20 17:02:48 -0700809
810 Returns:
811 A tuple includes two elements:
812 a boolean variable represents whether the auto-update process is
813 successfully started.
814 an integer represents the background auto-update process id.
815 """
816 _check_base_args_for_auto_update(kwargs)
817
818 host_name = kwargs['host_name']
819 build_name = kwargs['build_name']
820 force_update = _parse_boolean_arg(kwargs, 'force_update')
821 full_update = _parse_boolean_arg(kwargs, 'full_update')
822 async = _parse_boolean_arg(kwargs, 'async')
xixuanac89ce82016-11-30 16:48:20 -0800823 original_build = _parse_string_arg(kwargs, 'original_build')
David Haddock90e49442017-04-07 19:14:09 -0700824 payload_filename = _parse_string_arg(kwargs, 'payload_filename')
David Haddock20559612017-06-28 22:15:08 -0700825 clobber_stateful = _parse_boolean_arg(kwargs, 'clobber_stateful')
David Rileyee75de22017-11-02 10:48:15 -0700826 quick_provision = _parse_boolean_arg(kwargs, 'quick_provision')
827
828 devserver_url = updater.GetDevserverUrl()
829 static_url = updater.GetStaticUrl()
xixuan52c2fba2016-05-20 17:02:48 -0700830
831 if async:
832 path = os.path.dirname(os.path.abspath(__file__))
833 execute_file = os.path.join(path, 'cros_update.py')
834 args = (AUTO_UPDATE_CMD % (execute_file, host_name, build_name,
835 updater.static_dir))
xixuanac89ce82016-11-30 16:48:20 -0800836
837 # The original_build's format is like: link/3428.210.0
838 # The corresponding release_archive_url's format is like:
839 # gs://chromeos-releases/stable-channel/link/3428.210.0
840 if original_build:
841 release_archive_url = _build_uri_from_build_name(original_build)
842 # First staging the stateful.tgz synchronousely.
843 self.stage(files='stateful.tgz', async=False,
844 archive_url=release_archive_url)
845 args = ('%s --original_build %s' % (args, original_build))
846
xixuan52c2fba2016-05-20 17:02:48 -0700847 if force_update:
848 args = ('%s --force_update' % args)
849
850 if full_update:
851 args = ('%s --full_update' % args)
852
David Haddock90e49442017-04-07 19:14:09 -0700853 if payload_filename:
854 args = ('%s --payload_filename %s' % (args, payload_filename))
855
David Haddock20559612017-06-28 22:15:08 -0700856 if clobber_stateful:
857 args = ('%s --clobber_stateful' % args)
858
David Rileyee75de22017-11-02 10:48:15 -0700859 if quick_provision:
860 args = ('%s --quick_provision' % args)
861
862 if devserver_url:
863 args = ('%s --devserver_url %s' % (args, devserver_url))
864
865 if static_url:
866 args = ('%s --static_url %s' % (args, static_url))
867
xixuan2a0970a2016-08-10 12:12:44 -0700868 p = subprocess.Popen([args], shell=True, preexec_fn=os.setsid)
869 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-20 17:02:48 -0700870
871 # Pre-write status in the track_status_file before the first call of
872 # 'get_au_status' to make sure that the track_status_file exists.
xixuan2a0970a2016-08-10 12:12:44 -0700873 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700874 progress_tracker.WriteStatus('CrOS update is just started.')
875
xixuan2a0970a2016-08-10 12:12:44 -0700876 return json.dumps((True, pid))
xixuan52c2fba2016-05-20 17:02:48 -0700877 else:
878 cros_update_trigger = cros_update.CrOSUpdateTrigger(
xixuanac89ce82016-11-30 16:48:20 -0800879 host_name, build_name, updater.static_dir, force_update=force_update,
David Rileyee75de22017-11-02 10:48:15 -0700880 full_update=full_update, original_build=original_build,
881 quick_provision=quick_provision, devserver_url=devserver_url,
882 static_url=static_url)
xixuan52c2fba2016-05-20 17:02:48 -0700883 cros_update_trigger.TriggerAU()
xixuan27d50442017-08-09 10:38:25 -0700884 return json.dumps((True, -1))
xixuan52c2fba2016-05-20 17:02:48 -0700885
886 @cherrypy.expose
887 def get_au_status(self, **kwargs):
888 """Check if the auto-update task is finished.
889
890 It handles 4 cases:
891 1. If an error exists in the track_status_file, delete the track file and
892 raise it.
893 2. If cros-update process is finished, delete the file and return the
894 success result.
895 3. If the process is not running, delete the track file and raise an error
896 about 'the process is terminated due to unknown reason'.
897 4. If the track_status_file does not exist, kill the process if it exists,
898 and raise the IOError.
899
900 Args:
901 kwargs:
902 host_name: the hostname of the DUT to auto-update.
903 pid: the background process id of cros-update.
904
905 Returns:
xixuan28d99072016-10-06 12:24:16 -0700906 A dict with three elements:
xixuan52c2fba2016-05-20 17:02:48 -0700907 a boolean variable represents whether the auto-update process is
908 finished.
909 a string represents the current auto-update process status.
910 For example, 'Transfer Devserver/Stateful Update Package'.
xixuan28d99072016-10-06 12:24:16 -0700911 a detailed error message paragraph if there exists an Auto-Update
912 error, in which the last line shows the main exception. Empty
913 string otherwise.
xixuan52c2fba2016-05-20 17:02:48 -0700914 """
915 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700916 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
917 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700918
919 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700920 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
921 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700922
923 host_name = kwargs['host_name']
924 pid = kwargs['pid']
925 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
926
xixuan28d99072016-10-06 12:24:16 -0700927 result_dict = {'finished': False, 'status': '', 'detailed_error_msg': ''}
xixuan52c2fba2016-05-20 17:02:48 -0700928 try:
929 result = progress_tracker.ReadStatus()
930 if result.startswith(cros_update_progress.ERROR_TAG):
xixuan28d99072016-10-06 12:24:16 -0700931 result_dict['detailed_error_msg'] = result[len(
932 cros_update_progress.ERROR_TAG):]
xixuan28681fd2016-11-23 11:13:56 -0800933 elif result == cros_update_progress.FINISHED:
xixuan28d99072016-10-06 12:24:16 -0700934 result_dict['finished'] = True
935 result_dict['status'] = result
xixuan28681fd2016-11-23 11:13:56 -0800936 elif not cros_update_progress.IsProcessAlive(pid):
xixuan28d99072016-10-06 12:24:16 -0700937 result_dict['detailed_error_msg'] = (
938 'Cros_update process terminated midway due to unknown reason. '
939 'Last update status was %s' % result)
xixuan28681fd2016-11-23 11:13:56 -0800940 else:
941 result_dict['status'] = result
942 except IOError as e:
943 if pid and cros_update_progress.IsProcessAlive(pid):
xixuan2a0970a2016-08-10 12:12:44 -0700944 os.killpg(int(pid), signal.SIGKILL)
xixuan52c2fba2016-05-20 17:02:48 -0700945
xixuan28681fd2016-11-23 11:13:56 -0800946 result_dict['detailed_error_msg'] = str(e)
947
948 return json.dumps(result_dict)
xixuan52c2fba2016-05-20 17:02:48 -0700949
950 @cherrypy.expose
David Riley6d5fca02017-10-31 10:35:47 -0700951 def post_au_status(self, status, **kwargs):
952 """Updates the status of an auto-update task.
953
954 Callers will need to POST to this URL with a body of MIME-type
955 "multipart/form-data".
956 The body should include a single argument, 'status', containing the
957 AU status to record.
958
959 Args:
960 status: The updated status.
961 kwargs:
962 host_name: the hostname of the DUT to auto-update.
963 pid: the background process id of cros-update.
964 """
965 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700966 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
967 KEY_ERROR_MSG % 'host_name')
David Riley6d5fca02017-10-31 10:35:47 -0700968
969 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700970 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
971 KEY_ERROR_MSG % 'pid')
David Riley6d5fca02017-10-31 10:35:47 -0700972
973 host_name = kwargs['host_name']
974 pid = kwargs['pid']
David Riley3cea2582017-11-24 22:03:01 -0800975 status = status.rstrip()
976 _Log('Recording status for %s (%s): %s' % (host_name, pid, status))
David Riley6d5fca02017-10-31 10:35:47 -0700977 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
978
David Riley3cea2582017-11-24 22:03:01 -0800979 progress_tracker.WriteStatus(status)
David Riley6d5fca02017-10-31 10:35:47 -0700980
981 return 'True'
982
983 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700984 def handler_cleanup(self, **kwargs):
xixuan3bc974e2016-10-18 17:21:43 -0700985 """Clean track status log and temp directory for CrOS auto-update process.
xixuan52c2fba2016-05-20 17:02:48 -0700986
987 Args:
988 kwargs:
989 host_name: the hostname of the DUT to auto-update.
990 pid: the background process id of cros-update.
991 """
992 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700993 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
994 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700995
996 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700997 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
998 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700999
1000 host_name = kwargs['host_name']
1001 pid = kwargs['pid']
1002 cros_update_progress.DelTrackStatusFile(host_name, pid)
xixuan3bc974e2016-10-18 17:21:43 -07001003 cros_update_progress.DelAUTempDirectory(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -07001004
1005 @cherrypy.expose
1006 def kill_au_proc(self, **kwargs):
1007 """Kill CrOS auto-update process using given process id.
1008
1009 Args:
1010 kwargs:
1011 host_name: Kill all the CrOS auto-update process of this host.
1012
1013 Returns:
1014 True if all processes are killed properly.
1015 """
1016 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001017 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1018 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001019
xixuan447ad9d2017-02-28 14:46:20 -08001020 cur_pid = kwargs.get('pid')
1021
xixuan52c2fba2016-05-20 17:02:48 -07001022 host_name = kwargs['host_name']
xixuan3bc974e2016-10-18 17:21:43 -07001023 track_log_list = cros_update_progress.GetAllTrackStatusFileByHostName(
1024 host_name)
xixuan52c2fba2016-05-20 17:02:48 -07001025 for log in track_log_list:
1026 # The track log's full path is: path/host_name_pid.log
1027 # Use splitext to remove file extension, then parse pid from the
1028 # filename.
Congbin Guo3afae6c2019-08-13 16:29:42 -07001029 pid = os.path.splitext(os.path.basename(log))[0][len(host_name) + 1:]
xixuan447ad9d2017-02-28 14:46:20 -08001030 _clear_process(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -07001031
xixuan447ad9d2017-02-28 14:46:20 -08001032 if cur_pid:
1033 _clear_process(host_name, cur_pid)
xixuan52c2fba2016-05-20 17:02:48 -07001034
1035 return 'True'
1036
1037 @cherrypy.expose
1038 def collect_cros_au_log(self, **kwargs):
1039 """Collect CrOS auto-update log.
1040
1041 Args:
1042 kwargs:
1043 host_name: the hostname of the DUT to auto-update.
1044 pid: the background process id of cros-update.
1045
1046 Returns:
David Haddock9f459632017-05-11 14:45:46 -07001047 A dictionary containing the execute log file and any hostlog files.
xixuan52c2fba2016-05-20 17:02:48 -07001048 """
1049 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001050 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1051 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001052
1053 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001054 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1055 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -07001056
1057 host_name = kwargs['host_name']
1058 pid = kwargs['pid']
xixuan3bc974e2016-10-18 17:21:43 -07001059
1060 # Fetch the execute log recorded by cros_update_progress.
xixuan1bbfaba2016-10-13 17:53:22 -07001061 au_log = cros_update_progress.ReadExecuteLogFile(host_name, pid)
1062 cros_update_progress.DelExecuteLogFile(host_name, pid)
David Haddock9f459632017-05-11 14:45:46 -07001063 # Fetch the cros_au host_logs if they exist
1064 au_hostlogs = cros_update_progress.ReadAUHostLogFiles(host_name, pid)
1065 return json.dumps({'cros_au_log': au_log, 'host_logs': au_hostlogs})
xixuan1bbfaba2016-10-13 17:53:22 -07001066
xixuan52c2fba2016-05-20 17:02:48 -07001067 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -08001068 def locate_file(self, **kwargs):
1069 """Get the path to the given file name.
1070
1071 This method looks up the given file name inside specified build artifacts.
1072 One use case is to help caller to locate an apk file inside a build
1073 artifact. The location of the apk file could be different based on the
1074 branch and target.
1075
1076 Args:
1077 file_name: Name of the file to look for.
1078 artifacts: A list of artifact names to search for the file.
1079
1080 Returns:
1081 Path to the file with the given name. It's relative to the folder for the
1082 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -08001083 """
1084 dl, _ = _get_downloader_and_factory(kwargs)
1085 try:
Joe Brennan1691f8e2017-03-15 15:53:36 -07001086 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 15:38:38 -08001087 artifacts = kwargs['artifacts']
1088 except KeyError:
1089 raise DevServerError('`file_name` and `artifacts` are required to search '
1090 'for a file in build artifacts.')
1091 build_path = dl.GetBuildDir()
1092 for artifact in artifacts:
1093 # Get the unzipped folder of the artifact. If it's not defined in
1094 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
1095 # directory directly.
1096 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
1097 artifact_path = os.path.join(build_path, folder)
1098 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 15:53:36 -07001099 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 15:38:38 -08001100 return os.path.relpath(os.path.join(root, file_name), build_path)
1101 raise DevServerError('File `%s` can not be found in artifacts: %s' %
1102 (file_name, artifacts))
1103
1104 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -08001105 def setup_telemetry(self, **kwargs):
1106 """Extracts and sets up telemetry
1107
1108 This method goes through the telemetry deps packages, and stages them on
1109 the devserver to be used by the drones and the telemetry tests.
1110
1111 Args:
1112 archive_url: Google Storage URL for the build.
1113
1114 Returns:
1115 Path to the source folder for the telemetry codebase once it is staged.
1116 """
Gabe Black3b567202015-09-23 14:07:59 -07001117 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 13:39:18 -08001118
Gabe Black3b567202015-09-23 14:07:59 -07001119 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -08001120 deps_path = os.path.join(build_path, 'autotest/packages')
1121 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
1122 src_folder = os.path.join(telemetry_path, 'src')
1123
1124 with self._telemetry_lock_dict.lock(telemetry_path):
1125 if os.path.exists(src_folder):
1126 # Telemetry is already fully stage return
1127 return src_folder
1128
1129 common_util.MkDirP(telemetry_path)
1130
1131 # Copy over the required deps tar balls to the telemetry directory.
1132 for dep in TELEMETRY_DEPS:
1133 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -07001134 if not os.path.exists(dep_path):
1135 # This dep does not exist (could be new), do not extract it.
1136 continue
Simran Basi4baad082013-02-14 13:39:18 -08001137 try:
1138 common_util.ExtractTarball(dep_path, telemetry_path)
1139 except common_util.CommonUtilError as e:
1140 shutil.rmtree(telemetry_path)
1141 raise DevServerError(str(e))
1142
1143 # By default all the tarballs extract to test_src but some parts of
1144 # the telemetry code specifically hardcoded to exist inside of 'src'.
1145 test_src = os.path.join(telemetry_path, 'test_src')
1146 try:
1147 shutil.move(test_src, src_folder)
1148 except shutil.Error:
1149 # This can occur if src_folder already exists. Remove and retry move.
1150 shutil.rmtree(src_folder)
Gabe Black3b567202015-09-23 14:07:59 -07001151 raise DevServerError(
1152 'Failure in telemetry setup for build %s. Appears that the '
1153 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -08001154
1155 return src_folder
1156
1157 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -08001158 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -07001159 """Symbolicates a minidump using pre-downloaded symbols, returns it.
1160
1161 Callers will need to POST to this URL with a body of MIME-type
1162 "multipart/form-data".
1163 The body should include a single argument, 'minidump', containing the
1164 binary-formatted minidump to symbolicate.
1165
Chris Masone816e38c2012-05-02 12:22:36 -07001166 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -08001167 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -07001168 minidump: The binary minidump file to symbolicate.
1169 """
Chris Sosa76e44b92013-01-31 12:11:38 -08001170 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -07001171 # Try debug.tar.xz first, then debug.tgz
1172 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
1173 kwargs['artifacts'] = artifact
1174 dl = _get_downloader(kwargs)
1175
1176 try:
1177 if self.stage(**kwargs) == 'Success':
1178 break
1179 except build_artifact.ArtifactDownloadError:
1180 continue
1181 else:
Gabe Black3b567202015-09-23 14:07:59 -07001182 raise DevServerError('Failed to stage symbols for %s' %
1183 dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -08001184
Chris Masone816e38c2012-05-02 12:22:36 -07001185 to_return = ''
1186 with tempfile.NamedTemporaryFile() as local:
1187 while True:
1188 data = minidump.file.read(8192)
1189 if not data:
1190 break
1191 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -08001192
Chris Masone816e38c2012-05-02 12:22:36 -07001193 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -08001194
Gabe Black3b567202015-09-23 14:07:59 -07001195 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -08001196
xixuanab744382017-04-27 10:41:27 -07001197 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 12:11:38 -08001198 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 10:41:27 -07001199 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 12:11:38 -08001200 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1201
Chris Masone816e38c2012-05-02 12:22:36 -07001202 to_return, error_text = stackwalk.communicate()
1203 if stackwalk.returncode != 0:
1204 raise DevServerError("Can't generate stack trace: %s (rc=%d)" % (
1205 error_text, stackwalk.returncode))
1206
1207 return to_return
1208
1209 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001210 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -04001211 """Return a string representing the latest build for a given target.
1212
1213 Args:
1214 target: The build target, typically a combination of the board and the
1215 type of build e.g. x86-mario-release.
1216 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
1217 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -08001218
Scott Zawalski16954532012-03-20 15:31:36 -04001219 Returns:
1220 A string representation of the latest build if one exists, i.e.
1221 R19-1993.0.0-a1-b1480.
1222 An empty string if no latest could be found.
1223 """
Don Garrettf84631a2014-01-07 18:21:26 -08001224 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -04001225 return _PrintDocStringAsHTML(self.latestbuild)
1226
Don Garrettf84631a2014-01-07 18:21:26 -08001227 if 'target' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001228 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1229 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -07001230
1231 if _is_android_build_request(kwargs):
1232 branch = kwargs.get('branch', None)
1233 target = kwargs.get('target', None)
1234 if not target or not branch:
1235 raise DevServerError(
xixuan52c2fba2016-05-20 17:02:48 -07001236 'Both target and branch must be specified to query for the latest '
1237 'Android build.')
Dan Shi61305df2015-10-26 16:52:35 -07001238 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
1239
Scott Zawalski16954532012-03-20 15:31:36 -04001240 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001241 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-07 18:21:26 -08001242 updater.static_dir, kwargs['target'],
1243 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -07001244 except common_util.CommonUtilError as errmsg:
Amin Hassani08e42d22019-06-03 00:31:30 -07001245 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1246 str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -04001247
1248 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -07001249 def list_suite_controls(self, **kwargs):
1250 """Return a list of contents of all known control files.
1251
1252 Example URL:
1253 To List all control files' content:
1254 http://dev-server/list_suite_controls?suite_name=bvt&
1255 build=daisy_spring-release/R29-4279.0.0
1256
1257 Args:
1258 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
1259 suite_name: List the control files belonging to that suite.
1260
1261 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -07001262 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -07001263 """
1264 if not kwargs:
1265 return _PrintDocStringAsHTML(self.controlfiles)
1266
1267 if 'build' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001268 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1269 'Error: build= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001270
1271 if 'suite_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001272 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
Dan Shia1cd6522016-04-18 16:07:21 -07001273 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001274
1275 control_file_list = [
1276 line.rstrip() for line in common_util.GetControlFileListForSuite(
1277 updater.static_dir, kwargs['build'],
1278 kwargs['suite_name']).splitlines()]
1279
Dan Shia1cd6522016-04-18 16:07:21 -07001280 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -07001281 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -07001282 control_file_content_dict[control_path] = (common_util.GetControlFile(
xixuan7efd0002016-04-14 15:34:01 -07001283 updater.static_dir, kwargs['build'], control_path))
1284
Dan Shia1cd6522016-04-18 16:07:21 -07001285 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -07001286
1287 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001288 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -05001289 """Return a control file or a list of all known control files.
1290
1291 Example URL:
1292 To List all control files:
beepsbd337242013-07-09 22:44:06 -07001293 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
1294 To List all control files for, say, the bvt suite:
1295 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -05001296 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001297 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 -05001298
1299 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001300 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -05001301 control_path: If you want the contents of a control file set this
1302 to the path. E.g. client/site_tests/sleeptest/control
1303 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -07001304 suite_name: If control_path is not specified but a suite_name is
1305 specified, list the control files belonging to that suite instead of
1306 all control files. The empty string for suite_name will list all control
1307 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -08001308
Scott Zawalski4647ce62012-01-03 17:17:28 -05001309 Returns:
1310 Contents of a control file if control_path is provided.
1311 A list of control files if no control_path is provided.
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 Hassani08e42d22019-06-03 00:31:30 -07001317 raise common_util.DevServerHTTPError(httplib.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 """
Simran Basi99e63c02014-05-20 10:39:52 -07001348 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -07001349 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001350 response = os.path.join(build_id, filename)
1351 _Log('Path translation requested, returning: %s', response)
1352 return response
1353
1354 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -07001355 def xbuddy(self, *args, **kwargs):
1356 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -07001357
1358 Args:
joycheneaf4cfc2013-07-02 08:38:57 -07001359 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -07001360 components of the path. The path can be understood as
1361 "{local|remote}/build_id/artifact" where build_id is composed of
1362 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -07001363
joychen121fc9b2013-08-02 14:30:30 -07001364 The first path element is optional, and can be "remote" or "local"
1365 If local (the default), devserver will not attempt to access Google
1366 Storage, and will only search the static directory for the files.
1367 If remote, devserver will try to obtain the artifact off GS if it's
1368 not found locally.
1369 The board is the familiar board name, optionally suffixed.
1370 The version can be the google storage version number, and may also be
1371 any of a number of xBuddy defined version aliases that will be
1372 translated into the latest built image that fits the description.
1373 Defaults to latest.
1374 The artifact is one of a number of image or artifact aliases used by
1375 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -07001376
1377 Kwargs:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001378 for_update: {true|false}
1379 if true, pregenerates the update payloads for the image,
1380 and returns the update uri to pass to the
1381 update_engine_client.
joychen3cb228e2013-06-12 12:13:13 -07001382 return_dir: {true|false}
1383 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001384 relative_path: {true|false}
1385 if set to true, returns the relative path to the payload
1386 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -07001387 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -07001388 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -07001389 or
joycheneaf4cfc2013-07-02 08:38:57 -07001390 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -07001391
1392 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001393 If |for_update|, returns a redirect to the image or update file
1394 on the devserver. E.g.,
1395 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
1396 chromium-test-image.bin
1397 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1398 http://host:port/static/x86-generic-release/R26-4000.0.0/
1399 If |relative_path| is true, return a relative path the folder where the
1400 payloads are. E.g.,
1401 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -07001402 """
Chris Sosa75490802013-09-30 17:21:45 -07001403 boolean_string = kwargs.get('for_update')
1404 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001405 boolean_string = kwargs.get('return_dir')
1406 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1407 boolean_string = kwargs.get('relative_path')
1408 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001409
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001410 if return_dir and relative_path:
Chris Sosa4b951602014-04-09 20:26:07 -07001411 raise common_util.DevServerHTTPError(
Amin Hassani08e42d22019-06-03 00:31:30 -07001412 httplib.INTERNAL_SERVER_ERROR,
1413 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001414
1415 # For updates, we optimize downloading of test images.
1416 file_name = None
1417 build_id = None
1418 if for_update:
1419 try:
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001420 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-09-30 17:21:45 -07001421 except build_artifact.ArtifactDownloadError:
1422 build_id = None
1423
1424 if not build_id:
1425 build_id, file_name = self._xbuddy.Get(args)
1426
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001427 if for_update:
1428 _Log('Payload generation triggered by request')
1429 # Forces payload to be in cache and symlinked into build_id dir.
Chris Sosa75490802013-09-30 17:21:45 -07001430 updater.GetUpdateForLabel(autoupdate.FORCED_UPDATE, build_id,
1431 image_name=file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001432
1433 response = None
1434 if return_dir:
1435 response = os.path.join(cherrypy.request.base, 'static', build_id)
1436 _Log('Directory requested, returning: %s', response)
1437 elif relative_path:
1438 response = build_id
1439 _Log('Relative path requested, returning: %s', response)
1440 elif for_update:
1441 response = os.path.join(cherrypy.request.base, 'update', build_id)
1442 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001443 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001444 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001445 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001446 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001447 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001448
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001449 return response
1450
joychen3cb228e2013-06-12 12:13:13 -07001451 @cherrypy.expose
1452 def xbuddy_list(self):
1453 """Lists the currently available images & time since last access.
1454
Gilad Arnold452fd272014-02-04 11:09:28 -08001455 Returns:
1456 A string representation of a list of tuples [(build_id, time since last
1457 access),...]
joychen3cb228e2013-06-12 12:13:13 -07001458 """
1459 return self._xbuddy.List()
1460
1461 @cherrypy.expose
1462 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001463 """Returns the number of images cached by xBuddy."""
joychen3cb228e2013-06-12 12:13:13 -07001464 return self._xbuddy.Capacity()
1465
1466 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001467 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001468 """Presents a welcome message and documentation links."""
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001469 return ('Welcome to the Dev Server!<br>\n'
1470 '<br>\n'
1471 'Here are the available methods, click for documentation:<br>\n'
1472 '<br>\n'
1473 '%s' %
1474 '<br>\n'.join(
1475 [('<a href=doc/%s>%s</a>' % (name, name))
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001476 for name in _FindExposedMethods(
1477 self, '', unlisted=self._UNLISTED_METHODS)]))
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001478
1479 @cherrypy.expose
1480 def doc(self, *args):
1481 """Shows the documentation for available methods / URLs.
1482
Amin Hassani08e42d22019-06-03 00:31:30 -07001483 Examples:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001484 http://myhost/doc/update
1485 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001486 name = '/'.join(args)
1487 method = _GetExposedMethod(self, name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001488 if not method:
1489 raise DevServerError("No exposed method named `%s'" % name)
1490 if not method.__doc__:
1491 raise DevServerError("No documentation for exposed method `%s'" % name)
1492 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001493
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07001494 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001495 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001496 """Handles an update check from a Chrome OS client.
1497
1498 The HTTP request should contain the standard Omaha-style XML blob. The URL
1499 line may contain an additional intermediate path to the update payload.
1500
joychen121fc9b2013-08-02 14:30:30 -07001501 This request can be handled in one of 4 ways, depending on the devsever
1502 settings and intermediate path.
joychenb0dfe552013-07-30 10:02:06 -07001503
joychen121fc9b2013-08-02 14:30:30 -07001504 1. No intermediate path
1505 If no intermediate path is given, the default behavior is to generate an
1506 update payload from the latest test image locally built for the board
1507 specified in the xml. Devserver serves the generated payload.
1508
1509 2. Path explicitly invokes XBuddy
1510 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1511 with 'xbuddy'. This path is then used to acquire an image binary for the
1512 devserver to generate an update payload from. Devserver then serves this
1513 payload.
1514
1515 3. Path is left for the devserver to interpret.
1516 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1517 to generate a payload from the test image in that directory and serve it.
1518
1519 4. The devserver is in a 'forced' mode. TO BE DEPRECATED
1520 This comes from the usage of --forced_payload or --image when starting the
1521 devserver. No matter what path (or no path) gets passed in, devserver will
1522 serve the update payload (--forced_payload) or generate an update payload
1523 from the image (--image).
1524
1525 Examples:
1526 1. No intermediate path
1527 update_engine_client --omaha_url=http://myhost/update
1528 This generates an update payload from the latest test image locally built
1529 for the board specified in the xml.
1530
1531 2. Explicitly invoke xbuddy
1532 update_engine_client --omaha_url=
1533 http://myhost/update/xbuddy/remote/board/version/dev
1534 This would go to GS to download the dev image for the board, from which
1535 the devserver would generate a payload to serve.
1536
1537 3. Give a path for devserver to interpret
1538 update_engine_client --omaha_url=http://myhost/update/some/random/path
1539 This would attempt, in order to:
1540 a) Generate an update from a test image binary if found in
1541 static_dir/some/random/path.
1542 b) Serve an update payload found in static_dir/some/random/path.
1543 c) Hope that some/random/path takes the form "board/version" and
1544 and attempt to download an update payload for that board/version
1545 from GS.
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001546 """
joychen121fc9b2013-08-02 14:30:30 -07001547 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -08001548 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -07001549 data = cherrypy.request.rfile.read(body_length)
Chris Sosa7c931362010-10-11 19:49:01 -07001550
joychen121fc9b2013-08-02 14:30:30 -07001551 return updater.HandleUpdatePing(data, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001552
Dan Shif5ce2de2013-04-25 16:06:32 -07001553
Chris Sosadbc20082012-12-10 13:39:11 -08001554def _CleanCache(cache_dir, wipe):
1555 """Wipes any excess cached items in the cache_dir.
1556
1557 Args:
1558 cache_dir: the directory we are wiping from.
1559 wipe: If True, wipe all the contents -- not just the excess.
1560 """
1561 if wipe:
1562 # Clear the cache and exit on error.
1563 cmd = 'rm -rf %s/*' % cache_dir
1564 if os.system(cmd) != 0:
1565 _Log('Failed to clear the cache with %s' % cmd)
1566 sys.exit(1)
1567 else:
1568 # Clear all but the last N cached updates
1569 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1570 (cache_dir, CACHED_ENTRIES))
1571 if os.system(cmd) != 0:
1572 _Log('Failed to clean up old delta cache files with %s' % cmd)
1573 sys.exit(1)
1574
1575
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001576def _AddTestingOptions(parser):
1577 group = optparse.OptionGroup(
1578 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1579 'developers writing integration tests utilizing the devserver. They are '
1580 'not intended to be really used outside the scope of someone '
1581 'knowledgable about the test.')
1582 group.add_option('--exit',
1583 action='store_true',
1584 help='do not start the server (yet pregenerate/clear cache)')
1585 group.add_option('--host_log',
1586 action='store_true', default=False,
1587 help='record history of host update events (/api/hostlog)')
1588 group.add_option('--max_updates',
Gabe Black3b567202015-09-23 14:07:59 -07001589 metavar='NUM', default=-1, type='int',
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001590 help='maximum number of update checks handled positively '
1591 '(default: unlimited)')
David Zeuthen52ccd012013-10-31 12:58:26 -07001592 group.add_option('--public_key',
1593 metavar='PATH', default=None,
1594 help='path to the public key in pem format. If this is set '
1595 'the devserver will transmit a base64 encoded version of '
1596 'the content in the Omaha-style XML response.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001597 group.add_option('--proxy_port',
1598 metavar='PORT', default=None, type='int',
1599 help='port to have the client connect to -- basically the '
1600 'devserver lies to the update to tell it to get the payload '
1601 'from a different port that will proxy the request back to '
1602 'the devserver. The proxy must be managed outside the '
1603 'devserver.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001604 parser.add_option_group(group)
1605
1606
1607def _AddUpdateOptions(parser):
1608 group = optparse.OptionGroup(
1609 parser, 'Autoupdate Options', 'These options can be used to change '
1610 'how the devserver either generates or serve update payloads. Please '
1611 'note that all of these option affect how a payload is generated and so '
1612 'do not work in archive-only mode.')
1613 group.add_option('--board',
1614 help='By default the devserver will create an update '
1615 'payload from the latest image built for the board '
1616 'a device that is requesting an update has. When we '
1617 'pre-generate an update (see below) and we do not specify '
1618 'another update_type option like image or payload, the '
1619 'devserver needs to know the board to generate the latest '
1620 'image for. This is that board.')
1621 group.add_option('--critical_update',
1622 action='store_true', default=False,
1623 help='Present update payload as critical')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001624 group.add_option('--image',
1625 metavar='FILE',
1626 help='Generate and serve an update using this image to any '
1627 'device that requests an update.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001628 group.add_option('--payload',
1629 metavar='PATH',
1630 help='use the update payload from specified directory '
1631 '(update.gz).')
1632 group.add_option('-p', '--pregenerate_update',
1633 action='store_true', default=False,
1634 help='pre-generate the update payload before accepting '
1635 'update requests. Useful to help debug payload generation '
1636 'issues quickly. Also if an update payload will take a '
1637 'long time to generate, a client may timeout if you do not'
1638 'pregenerate the update.')
1639 group.add_option('--src_image',
1640 metavar='PATH', default='',
1641 help='If specified, delta updates will be generated using '
1642 'this image as the source image. Delta updates are when '
1643 'you are updating from a "source image" to a another '
1644 'image.')
1645 parser.add_option_group(group)
1646
1647
1648def _AddProductionOptions(parser):
1649 group = optparse.OptionGroup(
1650 parser, 'Advanced Server Options', 'These options can be used to changed '
1651 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001652 group.add_option('--clear_cache',
1653 action='store_true', default=False,
1654 help='At startup, removes all cached entries from the'
1655 'devserver\'s cache.')
1656 group.add_option('--logfile',
1657 metavar='PATH',
1658 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001659 group.add_option('--pidfile',
1660 metavar='PATH',
1661 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001662 group.add_option('--portfile',
1663 metavar='PATH',
1664 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001665 group.add_option('--production',
1666 action='store_true', default=False,
1667 help='have the devserver use production values when '
1668 'starting up. This includes using more threads and '
1669 'performing less logging.')
1670 parser.add_option_group(group)
1671
1672
Paul Hobbsef4e0702016-06-27 17:01:42 -07001673def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001674 """Create a LogHandler instance used to log all messages."""
1675 hdlr_cls = handlers.TimedRotatingFileHandler
1676 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001677 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001678 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001679 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001680 return hdlr
1681
1682
Chris Sosacde6bf42012-05-31 18:36:39 -07001683def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001684 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001685 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001686
1687 # get directory that the devserver is run from
1688 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001689 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001690 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001691 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001692 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001693 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001694 parser.add_option('--port',
1695 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001696 help=('port for the dev server to use; if zero, binds to '
1697 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001698 parser.add_option('-t', '--test_image',
1699 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001700 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001701 parser.add_option('-x', '--xbuddy_manage_builds',
1702 action='store_true',
1703 default=False,
1704 help='If set, allow xbuddy to manage images in'
1705 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001706 parser.add_option('-a', '--android_build_credential',
1707 default=None,
1708 help='Path to a json file which contains the credential '
1709 'needed to access Android builds.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001710 _AddProductionOptions(parser)
1711 _AddUpdateOptions(parser)
1712 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001713 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001714
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001715 # Handle options that must be set globally in cherrypy. Do this
1716 # work up front, because calls to _Log() below depend on this
1717 # initialization.
1718 if options.production:
1719 cherrypy.config.update({'environment': 'production'})
1720 if not options.logfile:
1721 cherrypy.config.update({'log.screen': True})
1722 else:
1723 cherrypy.config.update({'log.error_file': '',
1724 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001725 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001726 # Pylint can't seem to process these two calls properly
1727 # pylint: disable=E1101
1728 cherrypy.log.access_log.addHandler(hdlr)
1729 cherrypy.log.error_log.addHandler(hdlr)
1730 # pylint: enable=E1101
1731
joychened64b222013-06-21 16:39:34 -07001732 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001733 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001734
joychened64b222013-06-21 16:39:34 -07001735 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001736 # If our devserver is only supposed to serve payloads, we shouldn't be
1737 # mucking with the cache at all. If the devserver hadn't previously
1738 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001739 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001740 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001741 else:
1742 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001743
Chris Sosadbc20082012-12-10 13:39:11 -08001744 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001745 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001746
joychen121fc9b2013-08-02 14:30:30 -07001747 _xbuddy = xbuddy.XBuddy(options.xbuddy_manage_builds,
1748 options.board,
joychen121fc9b2013-08-02 14:30:30 -07001749 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001750 if options.clear_cache and options.xbuddy_manage_builds:
1751 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001752
Chris Sosa6a3697f2013-01-29 16:44:43 -08001753 # We allow global use here to share with cherrypy classes.
1754 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001755 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001756 updater = autoupdate.Autoupdate(
joychen121fc9b2013-08-02 14:30:30 -07001757 _xbuddy,
joychened64b222013-06-21 16:39:34 -07001758 static_dir=options.static_dir,
Chris Sosa5d342a22010-09-28 16:54:41 -07001759 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001760 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001761 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -07001762 src_image=options.src_image,
Chris Sosa08d55a22011-01-19 16:08:02 -08001763 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001764 copy_to_static_root=not options.exit,
David Zeuthen52ccd012013-10-31 12:58:26 -07001765 public_key=options.public_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001766 critical_update=options.critical_update,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001767 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001768 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001769 )
Chris Sosa7c931362010-10-11 19:49:01 -07001770
Chris Sosa6a3697f2013-01-29 16:44:43 -08001771 if options.pregenerate_update:
1772 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -07001773
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001774 if options.exit:
1775 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001776
joychen3cb228e2013-06-12 12:13:13 -07001777 dev_server = DevServerRoot(_xbuddy)
Congbin Guo3afae6c2019-08-13 16:29:42 -07001778 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 12:13:13 -07001779
Gilad Arnold11fbef42014-02-10 11:04:13 -08001780 # Patch CherryPy to support binding to any available port (--port=0).
1781 cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy)
1782
Chris Sosa855b8932013-08-21 13:24:55 -07001783 if options.pidfile:
1784 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1785
Gilad Arnold11fbef42014-02-10 11:04:13 -08001786 if options.portfile:
1787 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1788
Dan Shiafd5c6c2016-01-07 10:27:03 -08001789 if (options.android_build_credential and
1790 os.path.exists(options.android_build_credential)):
1791 try:
1792 with open(options.android_build_credential) as f:
1793 android_build.BuildAccessor.credential_info = json.load(f)
1794 except ValueError as e:
1795 _Log('Failed to load the android build credential: %s. Error: %s.' %
1796 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 16:29:42 -07001797
1798 cherrypy.tree.mount(health_checker_app, '/check_health',
1799 config=health_checker.get_config())
joychen3cb228e2013-06-12 12:13:13 -07001800 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001801
1802
1803if __name__ == '__main__':
1804 main()