blob: 3640ac03b89fdab17b3d62ca0d9e687767691b22 [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
Congbin Guo4132a272019-08-20 12:32:14 -070067import devserver_exceptions
Chris Sosa47a7d4e2012-03-28 11:26:55 -070068import downloader
Congbin Guo3afae6c2019-08-13 16:29:42 -070069import health_checker
Gilad Arnoldc65330c2012-09-20 15:17:48 -070070import log_util
joychen3cb228e2013-06-12 12:13:13 -070071import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070072
Gilad Arnoldc65330c2012-09-20 15:17:48 -070073# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080074def _Log(message, *args):
75 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 15:46:22 -070076
Dan Shi94dcbe82015-06-08 20:51:13 -070077
xixuanac89ce82016-11-30 16:48:20 -080078# Use try-except to skip unneccesary import for simple use case, eg. running
79# devserver on host.
80try:
81 import cros_update
xixuanac89ce82016-11-30 16:48:20 -080082except ImportError as e:
83 _Log('cros_update cannot be imported: %r', e)
84 cros_update = None
xixuana4f4e712017-05-08 15:17:54 -070085
86try:
87 import cros_update_progress
88except ImportError as e:
89 _Log('cros_update_progress cannot be imported: %r', e)
xixuanac89ce82016-11-30 16:48:20 -080090 cros_update_progress = None
91
xixuanac89ce82016-11-30 16:48:20 -080092try:
Dan Shi72b16132015-10-08 12:10:33 -070093 import android_build
94except ImportError as e:
95 # Ignore android_build import failure. This is to support devserver running
96 # inside a ChromeOS device triggered by cros flash. Most ChromeOS test images
97 # do not have google-api-python-client module and they don't need to support
98 # Android updating, therefore, ignore the import failure here.
Dan Shi72b16132015-10-08 12:10:33 -070099 android_build = None
Frank Farzan40160872011-12-12 18:39:18 -0800100
Chris Sosa417e55d2011-01-25 16:40:48 -0800101CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -0800102
Simran Basi4baad082013-02-14 13:39:18 -0800103TELEMETRY_FOLDER = 'telemetry_src'
104TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
105 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -0700106 'dep-chrome_test.tar.bz2',
107 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -0800108
Chris Sosa0356d3b2010-09-16 15:46:22 -0700109# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +0000110updater = None
rtc@google.comded22402009-10-26 22:36:21 +0000111
xixuan3d48bff2017-01-30 19:00:09 -0800112# Log rotation parameters. These settings correspond to twice a day once
113# devserver is started, with about two weeks (28 backup files) of old logs
114# kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700115#
xixuan3d48bff2017-01-30 19:00:09 -0800116# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700117# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-30 19:00:09 -0800118_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 16:29:42 -0700119_LOG_ROTATION_INTERVAL = 12 # hours
120_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-12 18:39:18 -0800121
xixuan52c2fba2016-05-20 17:02:48 -0700122# Auto-update parameters
123
124# Error msg for missing key in CrOS auto-update.
Xixuan Wu32af9f12017-11-13 14:11:44 -0800125KEY_ERROR_MSG = 'Key Error in RPC: %s= is required'
xixuan52c2fba2016-05-20 17:02:48 -0700126
127# Command of running auto-update.
128AUTO_UPDATE_CMD = '/usr/bin/python -u %s -d %s -b %s --static_dir %s'
129
130
Gabe Black3b567202015-09-23 14:07:59 -0700131def _canonicalize_archive_url(archive_url):
132 """Canonicalizes archive_url strings.
133
134 Raises:
135 DevserverError: if archive_url is not set.
136 """
137 if archive_url:
138 if not archive_url.startswith('gs://'):
Congbin Guo4132a272019-08-20 12:32:14 -0700139 raise devserver_exceptions.DevServerError(
140 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 14:07:59 -0700141
142 return archive_url.rstrip('/')
143 else:
Congbin Guo4132a272019-08-20 12:32:14 -0700144 raise devserver_exceptions.DevServerError(
145 "Must specify an archive_url in the request")
Gabe Black3b567202015-09-23 14:07:59 -0700146
147
148def _canonicalize_local_path(local_path):
149 """Canonicalizes |local_path| strings.
150
151 Raises:
152 DevserverError: if |local_path| is not set.
153 """
154 # Restrict staging of local content to only files within the static
155 # directory.
156 local_path = os.path.abspath(local_path)
157 if not local_path.startswith(updater.static_dir):
Congbin Guo4132a272019-08-20 12:32:14 -0700158 raise devserver_exceptions.DevServerError(
159 'Local path %s must be a subdirectory of the static'
160 ' directory: %s' % (local_path, updater.static_dir))
Gabe Black3b567202015-09-23 14:07:59 -0700161
162 return local_path.rstrip('/')
163
164
165def _get_artifacts(kwargs):
166 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
167
168 Raises:
169 DevserverError if no artifacts would be returned.
170 """
171 artifacts = kwargs.get('artifacts')
172 files = kwargs.get('files')
173 if not artifacts and not files:
Congbin Guo4132a272019-08-20 12:32:14 -0700174 raise devserver_exceptions.DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700175
176 # Note we NEED to coerce files to a string as we get raw unicode from
177 # cherrypy and we treat files as strings elsewhere in the code.
178 return (str(artifacts).split(',') if artifacts else [],
179 str(files).split(',') if files else [])
180
181
Dan Shi61305df2015-10-26 16:52:35 -0700182def _is_android_build_request(kwargs):
183 """Check if a devserver call is for Android build, based on the arguments.
184
185 This method exams the request's arguments (os_type) to determine if the
186 request is for Android build. If os_type is set to `android`, returns True.
187 If os_type is not set or has other values, returns False.
188
189 Args:
190 kwargs: Keyword arguments for the request.
191
192 Returns:
193 True if the request is for Android build. False otherwise.
194 """
195 os_type = kwargs.get('os_type', None)
196 return os_type == 'android'
197
198
Gabe Black3b567202015-09-23 14:07:59 -0700199def _get_downloader(kwargs):
200 """Returns the downloader based on passed in arguments.
201
202 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700203 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700204 """
205 local_path = kwargs.get('local_path')
206 if local_path:
207 local_path = _canonicalize_local_path(local_path)
208
209 dl = None
210 if local_path:
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800211 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
212 dl = downloader.LocalDownloader(updater.static_dir, local_path,
213 delete_source=delete_source)
Gabe Black3b567202015-09-23 14:07:59 -0700214
Dan Shi61305df2015-10-26 16:52:35 -0700215 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700216 archive_url = kwargs.get('archive_url')
217 if not archive_url and not local_path:
Congbin Guo4132a272019-08-20 12:32:14 -0700218 raise devserver_exceptions.DevServerError(
219 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700220 if archive_url and local_path:
Congbin Guo4132a272019-08-20 12:32:14 -0700221 raise devserver_exceptions.DevServerError(
222 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700223 if not dl:
224 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700225 dl = downloader.GoogleStorageDownloader(
226 updater.static_dir, archive_url,
227 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
228 archive_url))
Gabe Black3b567202015-09-23 14:07:59 -0700229 elif not dl:
230 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 12:10:33 -0700231 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 16:52:35 -0700232 build_id = kwargs.get('build_id', None)
233 if not target or not branch or not build_id:
Congbin Guo4132a272019-08-20 12:32:14 -0700234 raise devserver_exceptions.DevServerError(
Dan Shi61305df2015-10-26 16:52:35 -0700235 'target, branch, build ID must all be specified for downloading '
236 '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
252 if (isinstance(dl, downloader.GoogleStorageDownloader) or
253 isinstance(dl, downloader.LocalDownloader)):
254 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:
Congbin Guo4132a272019-08-20 12:32:14 -0700258 raise devserver_exceptions.DevServerError(
259 '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 Hassani08e42d22019-06-03 00:31:30 -0700461 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
462 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700463
464 if 'build_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700465 raise common_util.DevServerHTTPError(httplib.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:
488 raise common_util.DevServerHTTPError(
Amin Hassani08e42d22019-06-03 00:31:30 -0700489 httplib.INTERNAL_SERVER_ERROR,
xixuan52c2fba2016-05-20 17:02:48 -0700490 'The value for key %s is not boolean.' % key)
491 else:
492 return False
493
xixuan447ad9d2017-02-28 14:46:20 -0800494
xixuanac89ce82016-11-30 16:48:20 -0800495def _parse_string_arg(kwargs, key):
496 """Parse string arg from kwargs.
497
498 Args:
499 kwargs: the parameters to be checked.
500 key: the key to be parsed.
501
502 Returns:
503 The string value of kwargs[key], or None if key doesn't exist in kwargs.
504 """
505 if key in kwargs:
506 return kwargs[key]
507 else:
508 return None
509
xixuan447ad9d2017-02-28 14:46:20 -0800510
xixuanac89ce82016-11-30 16:48:20 -0800511def _build_uri_from_build_name(build_name):
512 """Get build url from a given build name.
513
514 Args:
515 build_name: the build name to be parsed, whose format is
516 'board/release_version'.
517
518 Returns:
519 The release_archive_url on Google Storage for this build name.
520 """
Amin Hassani08e42d22019-06-03 00:31:30 -0700521 # TODO(ahassani): This function doesn't seem to be used anywhere since its
522 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
523 # causing any runtime issues. So deprecate this in the future.
524 tokens = build_name.split('/')
525 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-20 17:02:48 -0700526
xixuan447ad9d2017-02-28 14:46:20 -0800527
528def _clear_process(host_name, pid):
529 """Clear AU process for given hostname and pid.
530
531 This clear includes:
532 1. kill process if it's alive.
533 2. delete the track status file of this process.
534 3. delete the executing log file of this process.
535
536 Args:
537 host_name: the host to execute auto-update.
538 pid: the background auto-update process id.
539 """
540 if cros_update_progress.IsProcessAlive(pid):
541 os.killpg(int(pid), signal.SIGKILL)
542
543 cros_update_progress.DelTrackStatusFile(host_name, pid)
544 cros_update_progress.DelExecuteLogFile(host_name, pid)
545
546
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700547class ApiRoot(object):
548 """RESTful API for Dev Server information."""
549 exposed = True
550
551 @cherrypy.expose
552 def hostinfo(self, ip):
553 """Returns a JSON dictionary containing information about the given ip.
554
Gilad Arnold1b908392012-10-05 11:36:27 -0700555 Args:
556 ip: address of host whose info is requested
Don Garrettf84631a2014-01-07 18:21:26 -0800557
Gilad Arnold1b908392012-10-05 11:36:27 -0700558 Returns:
559 A JSON dictionary containing all or some of the following fields:
560 last_event_type (int): last update event type received
561 last_event_status (int): last update event status received
562 last_known_version (string): last known version reported in update ping
563 forced_update_label (string): update label to force next update ping to
564 use, set by setnextupdate
565 See the OmahaEvent class in update_engine/omaha_request_action.h for
566 event type and status code definitions. If the ip does not exist an empty
567 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700568
Gilad Arnold1b908392012-10-05 11:36:27 -0700569 Example URL:
570 http://myhost/api/hostinfo?ip=192.168.1.5
571 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700572 return updater.HandleHostInfoPing(ip)
573
574 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800575 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700576 """Returns a JSON object containing a log of host event.
577
578 Args:
579 ip: address of host whose event log is requested, or `all'
Don Garrettf84631a2014-01-07 18:21:26 -0800580
Gilad Arnold1b908392012-10-05 11:36:27 -0700581 Returns:
582 A JSON encoded list (log) of dictionaries (events), each of which
583 containing a `timestamp' and other event fields, as described under
584 /api/hostinfo.
585
586 Example URL:
587 http://myhost/api/hostlog?ip=192.168.1.5
588 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800589 return updater.HandleHostLogPing(ip)
590
591 @cherrypy.expose
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700592 def setnextupdate(self, ip):
593 """Allows the response to the next update ping from a host to be set.
594
595 Takes the IP of the host and an update label as normally provided to the
Gilad Arnold1b908392012-10-05 11:36:27 -0700596 /update command.
597 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700598 body_length = int(cherrypy.request.headers['Content-Length'])
599 label = cherrypy.request.rfile.read(body_length)
600
601 if label:
602 label = label.strip()
603 if label:
604 return updater.HandleSetUpdatePing(ip, label)
Amin Hassani08e42d22019-06-03 00:31:30 -0700605 raise common_util.DevServerHTTPError(httplib.BAD_REQUEST,
606 'No label provided.')
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700607
Gilad Arnold55a2a372012-10-02 09:46:32 -0700608 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800609 def fileinfo(self, *args):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700610 """Returns information about a given staged file.
611
612 Args:
Don Garrettf84631a2014-01-07 18:21:26 -0800613 args: path to the file inside the server's static staging directory
614
Gilad Arnold55a2a372012-10-02 09:46:32 -0700615 Returns:
616 A JSON encoded dictionary with information about the said file, which may
617 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700618 size (int): the file size in bytes
Gilad Arnold1b908392012-10-05 11:36:27 -0700619 sha256 (string): a base64 encoded SHA256 hash
620
621 Example URL:
622 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700623 """
Don Garrettf84631a2014-01-07 18:21:26 -0800624 file_path = os.path.join(updater.static_dir, *args)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700625 if not os.path.exists(file_path):
Congbin Guo4132a272019-08-20 12:32:14 -0700626 raise devserver_exceptions.DevServerError(
627 'file not found: %s' % file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700628 try:
629 file_size = os.path.getsize(file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700630 file_sha256 = common_util.GetFileSha256(file_path)
631 except os.error, e:
Congbin Guo4132a272019-08-20 12:32:14 -0700632 raise devserver_exceptions.DevServerError(
633 'failed to get info for file %s: %s' % (file_path, e))
Gilad Arnolde74b3812013-04-22 11:27:38 -0700634
Gilad Arnolde74b3812013-04-22 11:27:38 -0700635 return json.dumps({
636 autoupdate.Autoupdate.SIZE_ATTR: file_size,
Gilad Arnolde74b3812013-04-22 11:27:38 -0700637 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
Gilad Arnolde74b3812013-04-22 11:27:38 -0700638 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700639
Chris Sosa76e44b92013-01-31 12:11:38 -0800640
David Rochberg7c79a812011-01-19 14:24:45 -0500641class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700642 """The Root Class for the Dev Server.
643
644 CherryPy works as follows:
645 For each method in this class, cherrpy interprets root/path
646 as a call to an instance of DevServerRoot->method_name. For example,
647 a call to http://myhost/build will call build. CherryPy automatically
648 parses http args and places them as keyword arguments in each method.
649 For paths http://myhost/update/dir1/dir2, you can use *args so that
650 cherrypy uses the update method and puts the extra paths in args.
651 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700652 # Method names that should not be listed on the index page.
653 _UNLISTED_METHODS = ['index', 'doc']
654
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700655 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700656
Dan Shi59ae7092013-06-04 14:37:27 -0700657 # Number of threads that devserver is staging images.
658 _staging_thread_count = 0
659 # Lock used to lock increasing/decreasing count.
660 _staging_thread_count_lock = threading.Lock()
661
joychen3cb228e2013-06-12 12:13:13 -0700662 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700663 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800664 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700665 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500666
Congbin Guo3afae6c2019-08-13 16:29:42 -0700667 @property
668 def staging_thread_count(self):
669 """Get the staging thread count."""
670 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700671
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700672 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500673 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700674 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700675 import builder
676 if self._builder is None:
677 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500678 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700679
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700680 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700681 def is_staged(self, **kwargs):
682 """Check if artifacts have been downloaded.
683
Congbin Guo3afae6c2019-08-13 16:29:42 -0700684 Examples:
685 To check if autotest and test_suites are staged:
686 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
687 artifacts=autotest,test_suites
688
Amin Hassani08e42d22019-06-03 00:31:30 -0700689 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700690 async: True to return without waiting for download to complete.
691 artifacts: Comma separated list of named artifacts to download.
692 These are defined in artifact_info and have their implementation
693 in build_artifact.py.
694 files: Comma separated list of file artifacts to stage. These
695 will be available as is in the corresponding static directory with no
696 custom post-processing.
697
Congbin Guo3afae6c2019-08-13 16:29:42 -0700698 Returns:
699 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700700 """
Gabe Black3b567202015-09-23 14:07:59 -0700701 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700702 response = str(dl.IsStaged(factory))
703 _Log('Responding to is_staged %s request with %r', kwargs, response)
704 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700705
Chris Sosa76e44b92013-01-31 12:11:38 -0800706 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800707 def list_image_dir(self, **kwargs):
708 """Take an archive url and list the contents in its staged directory.
709
Amin Hassani08e42d22019-06-03 00:31:30 -0700710 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800711 To list the contents of where this devserver should have staged
712 gs://image-archive/<board>-release/<build> call:
713 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
714
Congbin Guo3afae6c2019-08-13 16:29:42 -0700715 Args:
716 archive_url: Google Storage URL for the build.
717
Prashanth Ba06d2d22014-03-07 15:35:19 -0800718 Returns:
719 A string with information about the contents of the image directory.
720 """
Gabe Black3b567202015-09-23 14:07:59 -0700721 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800722 try:
Gabe Black3b567202015-09-23 14:07:59 -0700723 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800724 except build_artifact.ArtifactDownloadError as e:
725 return 'Cannot list the contents of staged artifacts. %s' % e
726 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700727 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800728 return image_dir_contents
729
730 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800731 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700732 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800733
Gabe Black3b567202015-09-23 14:07:59 -0700734 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700735 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700736 on the devserver. A call to this will attempt to cache non-specified
737 artifacts in the background for the given from the given URL following
738 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800739 artifacts is explicitly defined in the build_artifact module.
740
741 These artifacts will then be available from the static/ sub-directory of
742 the devserver.
743
Amin Hassani08e42d22019-06-03 00:31:30 -0700744 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800745 To download the autotest and test suites tarballs:
746 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
747 artifacts=autotest,test_suites
748 To download the full update payload:
749 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
750 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700751 To download just a file called blah.bin:
752 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
753 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800754
755 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700756 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800757
758 Note for this example, relative path is the archive_url stripped of its
759 basename i.e. path/ in the examples above. Specific example:
760
761 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
762
763 Will get staged to:
764
joychened64b222013-06-21 16:39:34 -0700765 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700766
767 Args:
768 archive_url: Google Storage URL for the build.
769 local_path: Local path for the build.
770 delete_source: Only meaningful with local_path. bool to indicate if the
771 source files should be deleted. This is especially useful when staging
772 a file locally in resource constrained environments as it allows us to
773 move the relevant files locally instead of copying them.
774 async: True to return without waiting for download to complete.
775 artifacts: Comma separated list of named artifacts to download.
776 These are defined in artifact_info and have their implementation
777 in build_artifact.py.
778 files: Comma separated list of files to stage. These
779 will be available as is in the corresponding static directory with no
780 custom post-processing.
781 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800782 """
Gabe Black3b567202015-09-23 14:07:59 -0700783 dl, factory = _get_downloader_and_factory(kwargs)
784
Dan Shi59ae7092013-06-04 14:37:27 -0700785 with DevServerRoot._staging_thread_count_lock:
786 DevServerRoot._staging_thread_count += 1
787 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800788 boolean_string = kwargs.get('clean')
789 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
790 if clean and os.path.exists(dl.GetBuildDir()):
791 _Log('Removing %s' % dl.GetBuildDir())
792 shutil.rmtree(dl.GetBuildDir())
Gabe Black3b567202015-09-23 14:07:59 -0700793 async = kwargs.get('async', False)
794 dl.Download(factory, async=async)
Dan Shi59ae7092013-06-04 14:37:27 -0700795 finally:
796 with DevServerRoot._staging_thread_count_lock:
797 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800798 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700799
800 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700801 def cros_au(self, **kwargs):
802 """Auto-update a CrOS DUT.
803
804 Args:
805 kwargs:
806 host_name: the hostname of the DUT to auto-update.
807 build_name: the build name for update the DUT.
808 force_update: Force an update even if the version installed is the
809 same. Default: False.
810 full_update: If True, do not run stateful update, directly force a full
811 reimage. If False, try stateful update first if the dut is already
812 installed with the same version.
813 async: Whether the auto_update function is ran in the background.
David Rileyee75de22017-11-02 10:48:15 -0700814 quick_provision: Whether the quick provision path is attempted first.
xixuan52c2fba2016-05-20 17:02:48 -0700815
816 Returns:
817 A tuple includes two elements:
818 a boolean variable represents whether the auto-update process is
819 successfully started.
820 an integer represents the background auto-update process id.
821 """
822 _check_base_args_for_auto_update(kwargs)
823
824 host_name = kwargs['host_name']
825 build_name = kwargs['build_name']
826 force_update = _parse_boolean_arg(kwargs, 'force_update')
827 full_update = _parse_boolean_arg(kwargs, 'full_update')
828 async = _parse_boolean_arg(kwargs, 'async')
xixuanac89ce82016-11-30 16:48:20 -0800829 original_build = _parse_string_arg(kwargs, 'original_build')
David Haddock90e49442017-04-07 19:14:09 -0700830 payload_filename = _parse_string_arg(kwargs, 'payload_filename')
David Haddock20559612017-06-28 22:15:08 -0700831 clobber_stateful = _parse_boolean_arg(kwargs, 'clobber_stateful')
David Rileyee75de22017-11-02 10:48:15 -0700832 quick_provision = _parse_boolean_arg(kwargs, 'quick_provision')
833
834 devserver_url = updater.GetDevserverUrl()
835 static_url = updater.GetStaticUrl()
xixuan52c2fba2016-05-20 17:02:48 -0700836
837 if async:
838 path = os.path.dirname(os.path.abspath(__file__))
839 execute_file = os.path.join(path, 'cros_update.py')
840 args = (AUTO_UPDATE_CMD % (execute_file, host_name, build_name,
841 updater.static_dir))
xixuanac89ce82016-11-30 16:48:20 -0800842
843 # The original_build's format is like: link/3428.210.0
844 # The corresponding release_archive_url's format is like:
845 # gs://chromeos-releases/stable-channel/link/3428.210.0
846 if original_build:
847 release_archive_url = _build_uri_from_build_name(original_build)
848 # First staging the stateful.tgz synchronousely.
849 self.stage(files='stateful.tgz', async=False,
850 archive_url=release_archive_url)
851 args = ('%s --original_build %s' % (args, original_build))
852
xixuan52c2fba2016-05-20 17:02:48 -0700853 if force_update:
854 args = ('%s --force_update' % args)
855
856 if full_update:
857 args = ('%s --full_update' % args)
858
David Haddock90e49442017-04-07 19:14:09 -0700859 if payload_filename:
860 args = ('%s --payload_filename %s' % (args, payload_filename))
861
David Haddock20559612017-06-28 22:15:08 -0700862 if clobber_stateful:
863 args = ('%s --clobber_stateful' % args)
864
David Rileyee75de22017-11-02 10:48:15 -0700865 if quick_provision:
866 args = ('%s --quick_provision' % args)
867
868 if devserver_url:
869 args = ('%s --devserver_url %s' % (args, devserver_url))
870
871 if static_url:
872 args = ('%s --static_url %s' % (args, static_url))
873
xixuan2a0970a2016-08-10 12:12:44 -0700874 p = subprocess.Popen([args], shell=True, preexec_fn=os.setsid)
875 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-20 17:02:48 -0700876
877 # Pre-write status in the track_status_file before the first call of
878 # 'get_au_status' to make sure that the track_status_file exists.
xixuan2a0970a2016-08-10 12:12:44 -0700879 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700880 progress_tracker.WriteStatus('CrOS update is just started.')
881
xixuan2a0970a2016-08-10 12:12:44 -0700882 return json.dumps((True, pid))
xixuan52c2fba2016-05-20 17:02:48 -0700883 else:
884 cros_update_trigger = cros_update.CrOSUpdateTrigger(
xixuanac89ce82016-11-30 16:48:20 -0800885 host_name, build_name, updater.static_dir, force_update=force_update,
David Rileyee75de22017-11-02 10:48:15 -0700886 full_update=full_update, original_build=original_build,
887 quick_provision=quick_provision, devserver_url=devserver_url,
888 static_url=static_url)
xixuan52c2fba2016-05-20 17:02:48 -0700889 cros_update_trigger.TriggerAU()
xixuan27d50442017-08-09 10:38:25 -0700890 return json.dumps((True, -1))
xixuan52c2fba2016-05-20 17:02:48 -0700891
892 @cherrypy.expose
893 def get_au_status(self, **kwargs):
894 """Check if the auto-update task is finished.
895
896 It handles 4 cases:
897 1. If an error exists in the track_status_file, delete the track file and
898 raise it.
899 2. If cros-update process is finished, delete the file and return the
900 success result.
901 3. If the process is not running, delete the track file and raise an error
902 about 'the process is terminated due to unknown reason'.
903 4. If the track_status_file does not exist, kill the process if it exists,
904 and raise the IOError.
905
906 Args:
907 kwargs:
908 host_name: the hostname of the DUT to auto-update.
909 pid: the background process id of cros-update.
910
911 Returns:
xixuan28d99072016-10-06 12:24:16 -0700912 A dict with three elements:
xixuan52c2fba2016-05-20 17:02:48 -0700913 a boolean variable represents whether the auto-update process is
914 finished.
915 a string represents the current auto-update process status.
916 For example, 'Transfer Devserver/Stateful Update Package'.
xixuan28d99072016-10-06 12:24:16 -0700917 a detailed error message paragraph if there exists an Auto-Update
918 error, in which the last line shows the main exception. Empty
919 string otherwise.
xixuan52c2fba2016-05-20 17:02:48 -0700920 """
921 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700922 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
923 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700924
925 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700926 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
927 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700928
929 host_name = kwargs['host_name']
930 pid = kwargs['pid']
931 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
932
xixuan28d99072016-10-06 12:24:16 -0700933 result_dict = {'finished': False, 'status': '', 'detailed_error_msg': ''}
xixuan52c2fba2016-05-20 17:02:48 -0700934 try:
935 result = progress_tracker.ReadStatus()
936 if result.startswith(cros_update_progress.ERROR_TAG):
xixuan28d99072016-10-06 12:24:16 -0700937 result_dict['detailed_error_msg'] = result[len(
938 cros_update_progress.ERROR_TAG):]
xixuan28681fd2016-11-23 11:13:56 -0800939 elif result == cros_update_progress.FINISHED:
xixuan28d99072016-10-06 12:24:16 -0700940 result_dict['finished'] = True
941 result_dict['status'] = result
xixuan28681fd2016-11-23 11:13:56 -0800942 elif not cros_update_progress.IsProcessAlive(pid):
xixuan28d99072016-10-06 12:24:16 -0700943 result_dict['detailed_error_msg'] = (
944 'Cros_update process terminated midway due to unknown reason. '
945 'Last update status was %s' % result)
xixuan28681fd2016-11-23 11:13:56 -0800946 else:
947 result_dict['status'] = result
948 except IOError as e:
949 if pid and cros_update_progress.IsProcessAlive(pid):
xixuan2a0970a2016-08-10 12:12:44 -0700950 os.killpg(int(pid), signal.SIGKILL)
xixuan52c2fba2016-05-20 17:02:48 -0700951
xixuan28681fd2016-11-23 11:13:56 -0800952 result_dict['detailed_error_msg'] = str(e)
953
954 return json.dumps(result_dict)
xixuan52c2fba2016-05-20 17:02:48 -0700955
956 @cherrypy.expose
David Riley6d5fca02017-10-31 10:35:47 -0700957 def post_au_status(self, status, **kwargs):
958 """Updates the status of an auto-update task.
959
960 Callers will need to POST to this URL with a body of MIME-type
961 "multipart/form-data".
962 The body should include a single argument, 'status', containing the
963 AU status to record.
964
965 Args:
966 status: The updated status.
967 kwargs:
968 host_name: the hostname of the DUT to auto-update.
969 pid: the background process id of cros-update.
970 """
971 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700972 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
973 KEY_ERROR_MSG % 'host_name')
David Riley6d5fca02017-10-31 10:35:47 -0700974
975 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700976 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
977 KEY_ERROR_MSG % 'pid')
David Riley6d5fca02017-10-31 10:35:47 -0700978
979 host_name = kwargs['host_name']
980 pid = kwargs['pid']
David Riley3cea2582017-11-24 22:03:01 -0800981 status = status.rstrip()
982 _Log('Recording status for %s (%s): %s' % (host_name, pid, status))
David Riley6d5fca02017-10-31 10:35:47 -0700983 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
984
David Riley3cea2582017-11-24 22:03:01 -0800985 progress_tracker.WriteStatus(status)
David Riley6d5fca02017-10-31 10:35:47 -0700986
987 return 'True'
988
989 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700990 def handler_cleanup(self, **kwargs):
xixuan3bc974e2016-10-18 17:21:43 -0700991 """Clean track status log and temp directory for CrOS auto-update process.
xixuan52c2fba2016-05-20 17:02:48 -0700992
993 Args:
994 kwargs:
995 host_name: the hostname of the DUT to auto-update.
996 pid: the background process id of cros-update.
997 """
998 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700999 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1000 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001001
1002 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001003 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1004 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -07001005
1006 host_name = kwargs['host_name']
1007 pid = kwargs['pid']
1008 cros_update_progress.DelTrackStatusFile(host_name, pid)
xixuan3bc974e2016-10-18 17:21:43 -07001009 cros_update_progress.DelAUTempDirectory(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -07001010
1011 @cherrypy.expose
1012 def kill_au_proc(self, **kwargs):
1013 """Kill CrOS auto-update process using given process id.
1014
1015 Args:
1016 kwargs:
1017 host_name: Kill all the CrOS auto-update process of this host.
1018
1019 Returns:
1020 True if all processes are killed properly.
1021 """
1022 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001023 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1024 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001025
xixuan447ad9d2017-02-28 14:46:20 -08001026 cur_pid = kwargs.get('pid')
1027
xixuan52c2fba2016-05-20 17:02:48 -07001028 host_name = kwargs['host_name']
xixuan3bc974e2016-10-18 17:21:43 -07001029 track_log_list = cros_update_progress.GetAllTrackStatusFileByHostName(
1030 host_name)
xixuan52c2fba2016-05-20 17:02:48 -07001031 for log in track_log_list:
1032 # The track log's full path is: path/host_name_pid.log
1033 # Use splitext to remove file extension, then parse pid from the
1034 # filename.
Congbin Guo3afae6c2019-08-13 16:29:42 -07001035 pid = os.path.splitext(os.path.basename(log))[0][len(host_name) + 1:]
xixuan447ad9d2017-02-28 14:46:20 -08001036 _clear_process(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -07001037
xixuan447ad9d2017-02-28 14:46:20 -08001038 if cur_pid:
1039 _clear_process(host_name, cur_pid)
xixuan52c2fba2016-05-20 17:02:48 -07001040
1041 return 'True'
1042
1043 @cherrypy.expose
1044 def collect_cros_au_log(self, **kwargs):
1045 """Collect CrOS auto-update log.
1046
1047 Args:
1048 kwargs:
1049 host_name: the hostname of the DUT to auto-update.
1050 pid: the background process id of cros-update.
1051
1052 Returns:
David Haddock9f459632017-05-11 14:45:46 -07001053 A dictionary containing the execute log file and any hostlog files.
xixuan52c2fba2016-05-20 17:02:48 -07001054 """
1055 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001056 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1057 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001058
1059 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001060 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1061 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -07001062
1063 host_name = kwargs['host_name']
1064 pid = kwargs['pid']
xixuan3bc974e2016-10-18 17:21:43 -07001065
1066 # Fetch the execute log recorded by cros_update_progress.
xixuan1bbfaba2016-10-13 17:53:22 -07001067 au_log = cros_update_progress.ReadExecuteLogFile(host_name, pid)
1068 cros_update_progress.DelExecuteLogFile(host_name, pid)
David Haddock9f459632017-05-11 14:45:46 -07001069 # Fetch the cros_au host_logs if they exist
1070 au_hostlogs = cros_update_progress.ReadAUHostLogFiles(host_name, pid)
1071 return json.dumps({'cros_au_log': au_log, 'host_logs': au_hostlogs})
xixuan1bbfaba2016-10-13 17:53:22 -07001072
xixuan52c2fba2016-05-20 17:02:48 -07001073 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -08001074 def locate_file(self, **kwargs):
1075 """Get the path to the given file name.
1076
1077 This method looks up the given file name inside specified build artifacts.
1078 One use case is to help caller to locate an apk file inside a build
1079 artifact. The location of the apk file could be different based on the
1080 branch and target.
1081
1082 Args:
1083 file_name: Name of the file to look for.
1084 artifacts: A list of artifact names to search for the file.
1085
1086 Returns:
1087 Path to the file with the given name. It's relative to the folder for the
1088 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -08001089 """
1090 dl, _ = _get_downloader_and_factory(kwargs)
1091 try:
Joe Brennan1691f8e2017-03-15 15:53:36 -07001092 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 15:38:38 -08001093 artifacts = kwargs['artifacts']
1094 except KeyError:
Congbin Guo4132a272019-08-20 12:32:14 -07001095 raise devserver_exceptions.DevServerError(
1096 '`file_name` and `artifacts` are required to search '
1097 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 15:38:38 -08001098 build_path = dl.GetBuildDir()
1099 for artifact in artifacts:
1100 # Get the unzipped folder of the artifact. If it's not defined in
1101 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
1102 # directory directly.
1103 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
1104 artifact_path = os.path.join(build_path, folder)
1105 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 15:53:36 -07001106 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 15:38:38 -08001107 return os.path.relpath(os.path.join(root, file_name), build_path)
Congbin Guo4132a272019-08-20 12:32:14 -07001108 raise devserver_exceptions.DevServerError(
1109 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 15:38:38 -08001110
1111 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -08001112 def setup_telemetry(self, **kwargs):
1113 """Extracts and sets up telemetry
1114
1115 This method goes through the telemetry deps packages, and stages them on
1116 the devserver to be used by the drones and the telemetry tests.
1117
1118 Args:
1119 archive_url: Google Storage URL for the build.
1120
1121 Returns:
1122 Path to the source folder for the telemetry codebase once it is staged.
1123 """
Gabe Black3b567202015-09-23 14:07:59 -07001124 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 13:39:18 -08001125
Gabe Black3b567202015-09-23 14:07:59 -07001126 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -08001127 deps_path = os.path.join(build_path, 'autotest/packages')
1128 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
1129 src_folder = os.path.join(telemetry_path, 'src')
1130
1131 with self._telemetry_lock_dict.lock(telemetry_path):
1132 if os.path.exists(src_folder):
1133 # Telemetry is already fully stage return
1134 return src_folder
1135
1136 common_util.MkDirP(telemetry_path)
1137
1138 # Copy over the required deps tar balls to the telemetry directory.
1139 for dep in TELEMETRY_DEPS:
1140 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -07001141 if not os.path.exists(dep_path):
1142 # This dep does not exist (could be new), do not extract it.
1143 continue
Simran Basi4baad082013-02-14 13:39:18 -08001144 try:
1145 common_util.ExtractTarball(dep_path, telemetry_path)
1146 except common_util.CommonUtilError as e:
1147 shutil.rmtree(telemetry_path)
Congbin Guo4132a272019-08-20 12:32:14 -07001148 raise devserver_exceptions.DevServerError(str(e))
Simran Basi4baad082013-02-14 13:39:18 -08001149
1150 # By default all the tarballs extract to test_src but some parts of
1151 # the telemetry code specifically hardcoded to exist inside of 'src'.
1152 test_src = os.path.join(telemetry_path, 'test_src')
1153 try:
1154 shutil.move(test_src, src_folder)
1155 except shutil.Error:
1156 # This can occur if src_folder already exists. Remove and retry move.
1157 shutil.rmtree(src_folder)
Congbin Guo4132a272019-08-20 12:32:14 -07001158 raise devserver_exceptions.DevServerError(
Gabe Black3b567202015-09-23 14:07:59 -07001159 'Failure in telemetry setup for build %s. Appears that the '
1160 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -08001161
1162 return src_folder
1163
1164 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -08001165 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -07001166 """Symbolicates a minidump using pre-downloaded symbols, returns it.
1167
1168 Callers will need to POST to this URL with a body of MIME-type
1169 "multipart/form-data".
1170 The body should include a single argument, 'minidump', containing the
1171 binary-formatted minidump to symbolicate.
1172
Chris Masone816e38c2012-05-02 12:22:36 -07001173 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -08001174 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -07001175 minidump: The binary minidump file to symbolicate.
1176 """
Chris Sosa76e44b92013-01-31 12:11:38 -08001177 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -07001178 # Try debug.tar.xz first, then debug.tgz
1179 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
1180 kwargs['artifacts'] = artifact
1181 dl = _get_downloader(kwargs)
1182
1183 try:
1184 if self.stage(**kwargs) == 'Success':
1185 break
1186 except build_artifact.ArtifactDownloadError:
1187 continue
1188 else:
Congbin Guo4132a272019-08-20 12:32:14 -07001189 raise devserver_exceptions.DevServerError(
1190 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -08001191
Chris Masone816e38c2012-05-02 12:22:36 -07001192 to_return = ''
1193 with tempfile.NamedTemporaryFile() as local:
1194 while True:
1195 data = minidump.file.read(8192)
1196 if not data:
1197 break
1198 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -08001199
Chris Masone816e38c2012-05-02 12:22:36 -07001200 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -08001201
Gabe Black3b567202015-09-23 14:07:59 -07001202 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -08001203
xixuanab744382017-04-27 10:41:27 -07001204 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 12:11:38 -08001205 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 10:41:27 -07001206 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 12:11:38 -08001207 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1208
Chris Masone816e38c2012-05-02 12:22:36 -07001209 to_return, error_text = stackwalk.communicate()
1210 if stackwalk.returncode != 0:
Congbin Guo4132a272019-08-20 12:32:14 -07001211 raise devserver_exceptions.DevServerError(
1212 "Can't generate stack trace: %s (rc=%d)" % (error_text,
1213 stackwalk.returncode))
Chris Masone816e38c2012-05-02 12:22:36 -07001214
1215 return to_return
1216
1217 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001218 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -04001219 """Return a string representing the latest build for a given target.
1220
1221 Args:
1222 target: The build target, typically a combination of the board and the
1223 type of build e.g. x86-mario-release.
1224 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
1225 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -08001226
Scott Zawalski16954532012-03-20 15:31:36 -04001227 Returns:
1228 A string representation of the latest build if one exists, i.e.
1229 R19-1993.0.0-a1-b1480.
1230 An empty string if no latest could be found.
1231 """
Don Garrettf84631a2014-01-07 18:21:26 -08001232 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -04001233 return _PrintDocStringAsHTML(self.latestbuild)
1234
Don Garrettf84631a2014-01-07 18:21:26 -08001235 if 'target' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001236 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1237 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -07001238
1239 if _is_android_build_request(kwargs):
1240 branch = kwargs.get('branch', None)
1241 target = kwargs.get('target', None)
1242 if not target or not branch:
Congbin Guo4132a272019-08-20 12:32:14 -07001243 raise devserver_exceptions.DevServerError(
xixuan52c2fba2016-05-20 17:02:48 -07001244 'Both target and branch must be specified to query for the latest '
1245 'Android build.')
Dan Shi61305df2015-10-26 16:52:35 -07001246 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
1247
Scott Zawalski16954532012-03-20 15:31:36 -04001248 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001249 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-07 18:21:26 -08001250 updater.static_dir, kwargs['target'],
1251 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -07001252 except common_util.CommonUtilError as errmsg:
Amin Hassani08e42d22019-06-03 00:31:30 -07001253 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1254 str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -04001255
1256 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -07001257 def list_suite_controls(self, **kwargs):
1258 """Return a list of contents of all known control files.
1259
1260 Example URL:
1261 To List all control files' content:
1262 http://dev-server/list_suite_controls?suite_name=bvt&
1263 build=daisy_spring-release/R29-4279.0.0
1264
1265 Args:
1266 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
1267 suite_name: List the control files belonging to that suite.
1268
1269 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -07001270 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -07001271 """
1272 if not kwargs:
1273 return _PrintDocStringAsHTML(self.controlfiles)
1274
1275 if 'build' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001276 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1277 'Error: build= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001278
1279 if 'suite_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001280 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
Dan Shia1cd6522016-04-18 16:07:21 -07001281 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001282
1283 control_file_list = [
1284 line.rstrip() for line in common_util.GetControlFileListForSuite(
1285 updater.static_dir, kwargs['build'],
1286 kwargs['suite_name']).splitlines()]
1287
Dan Shia1cd6522016-04-18 16:07:21 -07001288 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -07001289 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -07001290 control_file_content_dict[control_path] = (common_util.GetControlFile(
xixuan7efd0002016-04-14 15:34:01 -07001291 updater.static_dir, kwargs['build'], control_path))
1292
Dan Shia1cd6522016-04-18 16:07:21 -07001293 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -07001294
1295 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001296 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -05001297 """Return a control file or a list of all known control files.
1298
1299 Example URL:
1300 To List all control files:
beepsbd337242013-07-09 22:44:06 -07001301 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
1302 To List all control files for, say, the bvt suite:
1303 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -05001304 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001305 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 -05001306
1307 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001308 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -05001309 control_path: If you want the contents of a control file set this
1310 to the path. E.g. client/site_tests/sleeptest/control
1311 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -07001312 suite_name: If control_path is not specified but a suite_name is
1313 specified, list the control files belonging to that suite instead of
1314 all control files. The empty string for suite_name will list all control
1315 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -08001316
Scott Zawalski4647ce62012-01-03 17:17:28 -05001317 Returns:
1318 Contents of a control file if control_path is provided.
1319 A list of control files if no control_path is provided.
1320 """
Don Garrettf84631a2014-01-07 18:21:26 -08001321 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -05001322 return _PrintDocStringAsHTML(self.controlfiles)
1323
Don Garrettf84631a2014-01-07 18:21:26 -08001324 if 'build' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001325 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1326 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -05001327
Don Garrettf84631a2014-01-07 18:21:26 -08001328 if 'control_path' not in kwargs:
1329 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -07001330 return common_util.GetControlFileListForSuite(
Don Garrettf84631a2014-01-07 18:21:26 -08001331 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -07001332 else:
1333 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-07 18:21:26 -08001334 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -05001335 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001336 return common_util.GetControlFile(
Don Garrettf84631a2014-01-07 18:21:26 -08001337 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -08001338
1339 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -07001340 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001341 """Translates an xBuddy path to a real path to artifact if it exists.
1342
1343 Args:
Simran Basi99e63c02014-05-20 10:39:52 -07001344 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
1345 Local searches the devserver's static directory. Remote searches a
1346 Google Storage image archive.
1347
1348 Kwargs:
1349 image_dir: Google Storage image archive to search in if requesting a
1350 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001351
1352 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -07001353 String in the format of build_id/artifact as stored on the local server
1354 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001355 """
Simran Basi99e63c02014-05-20 10:39:52 -07001356 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -07001357 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001358 response = os.path.join(build_id, filename)
1359 _Log('Path translation requested, returning: %s', response)
1360 return response
1361
1362 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -07001363 def xbuddy(self, *args, **kwargs):
1364 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -07001365
1366 Args:
joycheneaf4cfc2013-07-02 08:38:57 -07001367 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -07001368 components of the path. The path can be understood as
1369 "{local|remote}/build_id/artifact" where build_id is composed of
1370 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -07001371
joychen121fc9b2013-08-02 14:30:30 -07001372 The first path element is optional, and can be "remote" or "local"
1373 If local (the default), devserver will not attempt to access Google
1374 Storage, and will only search the static directory for the files.
1375 If remote, devserver will try to obtain the artifact off GS if it's
1376 not found locally.
1377 The board is the familiar board name, optionally suffixed.
1378 The version can be the google storage version number, and may also be
1379 any of a number of xBuddy defined version aliases that will be
1380 translated into the latest built image that fits the description.
1381 Defaults to latest.
1382 The artifact is one of a number of image or artifact aliases used by
1383 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -07001384
1385 Kwargs:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001386 for_update: {true|false}
1387 if true, pregenerates the update payloads for the image,
1388 and returns the update uri to pass to the
1389 update_engine_client.
joychen3cb228e2013-06-12 12:13:13 -07001390 return_dir: {true|false}
1391 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001392 relative_path: {true|false}
1393 if set to true, returns the relative path to the payload
1394 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -07001395 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -07001396 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -07001397 or
joycheneaf4cfc2013-07-02 08:38:57 -07001398 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -07001399
1400 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001401 If |for_update|, returns a redirect to the image or update file
1402 on the devserver. E.g.,
1403 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
1404 chromium-test-image.bin
1405 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1406 http://host:port/static/x86-generic-release/R26-4000.0.0/
1407 If |relative_path| is true, return a relative path the folder where the
1408 payloads are. E.g.,
1409 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -07001410 """
Chris Sosa75490802013-09-30 17:21:45 -07001411 boolean_string = kwargs.get('for_update')
1412 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001413 boolean_string = kwargs.get('return_dir')
1414 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1415 boolean_string = kwargs.get('relative_path')
1416 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001417
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001418 if return_dir and relative_path:
Chris Sosa4b951602014-04-09 20:26:07 -07001419 raise common_util.DevServerHTTPError(
Amin Hassani08e42d22019-06-03 00:31:30 -07001420 httplib.INTERNAL_SERVER_ERROR,
1421 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001422
1423 # For updates, we optimize downloading of test images.
1424 file_name = None
1425 build_id = None
1426 if for_update:
1427 try:
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001428 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-09-30 17:21:45 -07001429 except build_artifact.ArtifactDownloadError:
1430 build_id = None
1431
1432 if not build_id:
1433 build_id, file_name = self._xbuddy.Get(args)
1434
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001435 if for_update:
1436 _Log('Payload generation triggered by request')
1437 # Forces payload to be in cache and symlinked into build_id dir.
Chris Sosa75490802013-09-30 17:21:45 -07001438 updater.GetUpdateForLabel(autoupdate.FORCED_UPDATE, build_id,
1439 image_name=file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001440
1441 response = None
1442 if return_dir:
1443 response = os.path.join(cherrypy.request.base, 'static', build_id)
1444 _Log('Directory requested, returning: %s', response)
1445 elif relative_path:
1446 response = build_id
1447 _Log('Relative path requested, returning: %s', response)
1448 elif for_update:
1449 response = os.path.join(cherrypy.request.base, 'update', build_id)
1450 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001451 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001452 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001453 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001454 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001455 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001456
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001457 return response
1458
joychen3cb228e2013-06-12 12:13:13 -07001459 @cherrypy.expose
1460 def xbuddy_list(self):
1461 """Lists the currently available images & time since last access.
1462
Gilad Arnold452fd272014-02-04 11:09:28 -08001463 Returns:
1464 A string representation of a list of tuples [(build_id, time since last
1465 access),...]
joychen3cb228e2013-06-12 12:13:13 -07001466 """
1467 return self._xbuddy.List()
1468
1469 @cherrypy.expose
1470 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001471 """Returns the number of images cached by xBuddy."""
joychen3cb228e2013-06-12 12:13:13 -07001472 return self._xbuddy.Capacity()
1473
1474 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001475 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001476 """Presents a welcome message and documentation links."""
Congbin Guo6bc32182019-08-20 17:54:30 -07001477 html_template = (
1478 'Welcome to the Dev Server!<br>\n'
1479 '<br>\n'
1480 'Here are the available methods, click for documentation:<br>\n'
1481 '<br>\n'
1482 '%s')
1483
1484 exposed_methods = []
1485 for app in cherrypy.tree.apps.values():
1486 exposed_methods += _FindExposedMethods(
1487 app.root, app.script_name.lstrip('/'),
1488 unlisted=self._UNLISTED_METHODS)
1489
1490 return html_template % '<br>\n'.join(
1491 ['<a href=doc/%s>%s</a>' % (name, name)
1492 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001493
1494 @cherrypy.expose
1495 def doc(self, *args):
1496 """Shows the documentation for available methods / URLs.
1497
Amin Hassani08e42d22019-06-03 00:31:30 -07001498 Examples:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001499 http://myhost/doc/update
1500 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001501 name = '/'.join(args)
Congbin Guo6bc32182019-08-20 17:54:30 -07001502 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001503 if not method:
Congbin Guo4132a272019-08-20 12:32:14 -07001504 raise devserver_exceptions.DevServerError(
1505 "No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001506 if not method.__doc__:
Congbin Guo4132a272019-08-20 12:32:14 -07001507 raise devserver_exceptions.DevServerError(
1508 "No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001509 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001510
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07001511 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001512 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001513 """Handles an update check from a Chrome OS client.
1514
1515 The HTTP request should contain the standard Omaha-style XML blob. The URL
1516 line may contain an additional intermediate path to the update payload.
1517
joychen121fc9b2013-08-02 14:30:30 -07001518 This request can be handled in one of 4 ways, depending on the devsever
1519 settings and intermediate path.
joychenb0dfe552013-07-30 10:02:06 -07001520
joychen121fc9b2013-08-02 14:30:30 -07001521 1. No intermediate path
1522 If no intermediate path is given, the default behavior is to generate an
1523 update payload from the latest test image locally built for the board
1524 specified in the xml. Devserver serves the generated payload.
1525
1526 2. Path explicitly invokes XBuddy
1527 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1528 with 'xbuddy'. This path is then used to acquire an image binary for the
1529 devserver to generate an update payload from. Devserver then serves this
1530 payload.
1531
1532 3. Path is left for the devserver to interpret.
1533 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1534 to generate a payload from the test image in that directory and serve it.
1535
1536 4. The devserver is in a 'forced' mode. TO BE DEPRECATED
1537 This comes from the usage of --forced_payload or --image when starting the
1538 devserver. No matter what path (or no path) gets passed in, devserver will
1539 serve the update payload (--forced_payload) or generate an update payload
1540 from the image (--image).
1541
1542 Examples:
1543 1. No intermediate path
1544 update_engine_client --omaha_url=http://myhost/update
1545 This generates an update payload from the latest test image locally built
1546 for the board specified in the xml.
1547
1548 2. Explicitly invoke xbuddy
1549 update_engine_client --omaha_url=
1550 http://myhost/update/xbuddy/remote/board/version/dev
1551 This would go to GS to download the dev image for the board, from which
1552 the devserver would generate a payload to serve.
1553
1554 3. Give a path for devserver to interpret
1555 update_engine_client --omaha_url=http://myhost/update/some/random/path
1556 This would attempt, in order to:
1557 a) Generate an update from a test image binary if found in
1558 static_dir/some/random/path.
1559 b) Serve an update payload found in static_dir/some/random/path.
1560 c) Hope that some/random/path takes the form "board/version" and
1561 and attempt to download an update payload for that board/version
1562 from GS.
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001563 """
joychen121fc9b2013-08-02 14:30:30 -07001564 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -08001565 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -07001566 data = cherrypy.request.rfile.read(body_length)
Chris Sosa7c931362010-10-11 19:49:01 -07001567
joychen121fc9b2013-08-02 14:30:30 -07001568 return updater.HandleUpdatePing(data, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001569
Dan Shif5ce2de2013-04-25 16:06:32 -07001570
Chris Sosadbc20082012-12-10 13:39:11 -08001571def _CleanCache(cache_dir, wipe):
1572 """Wipes any excess cached items in the cache_dir.
1573
1574 Args:
1575 cache_dir: the directory we are wiping from.
1576 wipe: If True, wipe all the contents -- not just the excess.
1577 """
1578 if wipe:
1579 # Clear the cache and exit on error.
1580 cmd = 'rm -rf %s/*' % cache_dir
1581 if os.system(cmd) != 0:
1582 _Log('Failed to clear the cache with %s' % cmd)
1583 sys.exit(1)
1584 else:
1585 # Clear all but the last N cached updates
1586 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1587 (cache_dir, CACHED_ENTRIES))
1588 if os.system(cmd) != 0:
1589 _Log('Failed to clean up old delta cache files with %s' % cmd)
1590 sys.exit(1)
1591
1592
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001593def _AddTestingOptions(parser):
1594 group = optparse.OptionGroup(
1595 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1596 'developers writing integration tests utilizing the devserver. They are '
1597 'not intended to be really used outside the scope of someone '
1598 'knowledgable about the test.')
1599 group.add_option('--exit',
1600 action='store_true',
1601 help='do not start the server (yet pregenerate/clear cache)')
1602 group.add_option('--host_log',
1603 action='store_true', default=False,
1604 help='record history of host update events (/api/hostlog)')
1605 group.add_option('--max_updates',
Gabe Black3b567202015-09-23 14:07:59 -07001606 metavar='NUM', default=-1, type='int',
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001607 help='maximum number of update checks handled positively '
1608 '(default: unlimited)')
David Zeuthen52ccd012013-10-31 12:58:26 -07001609 group.add_option('--public_key',
1610 metavar='PATH', default=None,
1611 help='path to the public key in pem format. If this is set '
1612 'the devserver will transmit a base64 encoded version of '
1613 'the content in the Omaha-style XML response.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001614 group.add_option('--proxy_port',
1615 metavar='PORT', default=None, type='int',
1616 help='port to have the client connect to -- basically the '
1617 'devserver lies to the update to tell it to get the payload '
1618 'from a different port that will proxy the request back to '
1619 'the devserver. The proxy must be managed outside the '
1620 'devserver.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001621 parser.add_option_group(group)
1622
1623
1624def _AddUpdateOptions(parser):
1625 group = optparse.OptionGroup(
1626 parser, 'Autoupdate Options', 'These options can be used to change '
1627 'how the devserver either generates or serve update payloads. Please '
1628 'note that all of these option affect how a payload is generated and so '
1629 'do not work in archive-only mode.')
1630 group.add_option('--board',
1631 help='By default the devserver will create an update '
1632 'payload from the latest image built for the board '
1633 'a device that is requesting an update has. When we '
1634 'pre-generate an update (see below) and we do not specify '
1635 'another update_type option like image or payload, the '
1636 'devserver needs to know the board to generate the latest '
1637 'image for. This is that board.')
1638 group.add_option('--critical_update',
1639 action='store_true', default=False,
1640 help='Present update payload as critical')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001641 group.add_option('--image',
1642 metavar='FILE',
1643 help='Generate and serve an update using this image to any '
1644 'device that requests an update.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001645 group.add_option('--payload',
1646 metavar='PATH',
1647 help='use the update payload from specified directory '
1648 '(update.gz).')
1649 group.add_option('-p', '--pregenerate_update',
1650 action='store_true', default=False,
1651 help='pre-generate the update payload before accepting '
1652 'update requests. Useful to help debug payload generation '
1653 'issues quickly. Also if an update payload will take a '
1654 'long time to generate, a client may timeout if you do not'
1655 'pregenerate the update.')
1656 group.add_option('--src_image',
1657 metavar='PATH', default='',
1658 help='If specified, delta updates will be generated using '
1659 'this image as the source image. Delta updates are when '
1660 'you are updating from a "source image" to a another '
1661 'image.')
1662 parser.add_option_group(group)
1663
1664
1665def _AddProductionOptions(parser):
1666 group = optparse.OptionGroup(
1667 parser, 'Advanced Server Options', 'These options can be used to changed '
1668 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001669 group.add_option('--clear_cache',
1670 action='store_true', default=False,
1671 help='At startup, removes all cached entries from the'
1672 'devserver\'s cache.')
1673 group.add_option('--logfile',
1674 metavar='PATH',
1675 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001676 group.add_option('--pidfile',
1677 metavar='PATH',
1678 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001679 group.add_option('--portfile',
1680 metavar='PATH',
1681 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001682 group.add_option('--production',
1683 action='store_true', default=False,
1684 help='have the devserver use production values when '
1685 'starting up. This includes using more threads and '
1686 'performing less logging.')
1687 parser.add_option_group(group)
1688
1689
Paul Hobbsef4e0702016-06-27 17:01:42 -07001690def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001691 """Create a LogHandler instance used to log all messages."""
1692 hdlr_cls = handlers.TimedRotatingFileHandler
1693 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001694 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001695 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001696 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001697 return hdlr
1698
1699
Chris Sosacde6bf42012-05-31 18:36:39 -07001700def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001701 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001702 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001703
1704 # get directory that the devserver is run from
1705 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001706 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001707 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001708 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001709 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001710 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001711 parser.add_option('--port',
1712 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001713 help=('port for the dev server to use; if zero, binds to '
1714 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001715 parser.add_option('-t', '--test_image',
1716 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001717 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001718 parser.add_option('-x', '--xbuddy_manage_builds',
1719 action='store_true',
1720 default=False,
1721 help='If set, allow xbuddy to manage images in'
1722 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001723 parser.add_option('-a', '--android_build_credential',
1724 default=None,
1725 help='Path to a json file which contains the credential '
1726 'needed to access Android builds.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001727 _AddProductionOptions(parser)
1728 _AddUpdateOptions(parser)
1729 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001730 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001731
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001732 # Handle options that must be set globally in cherrypy. Do this
1733 # work up front, because calls to _Log() below depend on this
1734 # initialization.
1735 if options.production:
1736 cherrypy.config.update({'environment': 'production'})
1737 if not options.logfile:
1738 cherrypy.config.update({'log.screen': True})
1739 else:
1740 cherrypy.config.update({'log.error_file': '',
1741 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001742 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001743 # Pylint can't seem to process these two calls properly
1744 # pylint: disable=E1101
1745 cherrypy.log.access_log.addHandler(hdlr)
1746 cherrypy.log.error_log.addHandler(hdlr)
1747 # pylint: enable=E1101
1748
joychened64b222013-06-21 16:39:34 -07001749 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001750 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001751
joychened64b222013-06-21 16:39:34 -07001752 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001753 # If our devserver is only supposed to serve payloads, we shouldn't be
1754 # mucking with the cache at all. If the devserver hadn't previously
1755 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001756 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001757 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001758 else:
1759 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001760
Chris Sosadbc20082012-12-10 13:39:11 -08001761 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001762 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001763
joychen121fc9b2013-08-02 14:30:30 -07001764 _xbuddy = xbuddy.XBuddy(options.xbuddy_manage_builds,
1765 options.board,
joychen121fc9b2013-08-02 14:30:30 -07001766 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001767 if options.clear_cache and options.xbuddy_manage_builds:
1768 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001769
Chris Sosa6a3697f2013-01-29 16:44:43 -08001770 # We allow global use here to share with cherrypy classes.
1771 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001772 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001773 updater = autoupdate.Autoupdate(
joychen121fc9b2013-08-02 14:30:30 -07001774 _xbuddy,
joychened64b222013-06-21 16:39:34 -07001775 static_dir=options.static_dir,
Chris Sosa5d342a22010-09-28 16:54:41 -07001776 forced_image=options.image,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001777 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001778 proxy_port=options.proxy_port,
Chris Sosa4136e692010-10-28 23:42:37 -07001779 src_image=options.src_image,
Chris Sosa08d55a22011-01-19 16:08:02 -08001780 board=options.board,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001781 copy_to_static_root=not options.exit,
David Zeuthen52ccd012013-10-31 12:58:26 -07001782 public_key=options.public_key,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001783 critical_update=options.critical_update,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001784 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001785 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001786 )
Chris Sosa7c931362010-10-11 19:49:01 -07001787
Chris Sosa6a3697f2013-01-29 16:44:43 -08001788 if options.pregenerate_update:
1789 updater.PreGenerateUpdate()
Chris Sosa0356d3b2010-09-16 15:46:22 -07001790
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001791 if options.exit:
1792 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001793
joychen3cb228e2013-06-12 12:13:13 -07001794 dev_server = DevServerRoot(_xbuddy)
Congbin Guo3afae6c2019-08-13 16:29:42 -07001795 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 12:13:13 -07001796
Gilad Arnold11fbef42014-02-10 11:04:13 -08001797 # Patch CherryPy to support binding to any available port (--port=0).
1798 cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy)
1799
Chris Sosa855b8932013-08-21 13:24:55 -07001800 if options.pidfile:
1801 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1802
Gilad Arnold11fbef42014-02-10 11:04:13 -08001803 if options.portfile:
1804 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1805
Dan Shiafd5c6c2016-01-07 10:27:03 -08001806 if (options.android_build_credential and
1807 os.path.exists(options.android_build_credential)):
1808 try:
1809 with open(options.android_build_credential) as f:
1810 android_build.BuildAccessor.credential_info = json.load(f)
1811 except ValueError as e:
1812 _Log('Failed to load the android build credential: %s. Error: %s.' %
1813 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 16:29:42 -07001814
1815 cherrypy.tree.mount(health_checker_app, '/check_health',
1816 config=health_checker.get_config())
joychen3cb228e2013-06-12 12:13:13 -07001817 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001818
1819
1820if __name__ == '__main__':
1821 main()