blob: b4aa7eb64db7b36759c6d7a8249fa0ffd2ea8c93 [file] [log] [blame]
David Riley2fcb0122017-11-02 11:25:39 -07001#!/usr/bin/env python2
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -07002# -*- coding: utf-8 -*-
Chris Sosa781ba6d2012-04-11 12:44:43 -07003# Copyright (c) 2009-2012 The Chromium OS Authors. All rights reserved.
rtc@google.comded22402009-10-26 22:36:21 +00004# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Chris Sosa3ae4dc12013-03-29 11:47:00 -07007"""Chromium OS development server that can be used for all forms of update.
8
9This devserver can be used to perform system-wide autoupdate and update
10of specific portage packages on devices running Chromium OS derived operating
Amin Hassanie9ffb862019-09-25 17:10:40 -070011systems.
Chris Sosa3ae4dc12013-03-29 11:47:00 -070012
Amin Hassanie9ffb862019-09-25 17:10:40 -070013The devserver is configured to stage and
Chris Sosa3ae4dc12013-03-29 11:47:00 -070014serve artifacts from Google Storage using the credentials provided to it before
15it is run. The easiest way to understand this is that the devserver is
16functioning as a local cache for artifacts produced and uploaded by build
17servers. Users of this form of devserver can either download the artifacts
18from the devservers static directory OR use the update RPC to perform a
19system-wide autoupdate. Archive mode is always active.
20
Chris Sosa3ae4dc12013-03-29 11:47:00 -070021For autoupdates, there are many more advanced options that can help specify
22how to update and which payload to give to a requester.
23"""
24
Gabe Black3b567202015-09-23 14:07:59 -070025from __future__ import print_function
Chris Sosa7c931362010-10-11 19:49:01 -070026
Gilad Arnold55a2a372012-10-02 09:46:32 -070027import json
David Riley2fcb0122017-11-02 11:25:39 -070028import optparse # pylint: disable=deprecated-module
rtc@google.comded22402009-10-26 22:36:21 +000029import os
Scott Zawalski4647ce62012-01-03 17:17:28 -050030import re
Simran Basi4baad082013-02-14 13:39:18 -080031import shutil
xixuan52c2fba2016-05-20 17:02:48 -070032import signal
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -080033import socket
Chris Masone816e38c2012-05-02 12:22:36 -070034import subprocess
J. Richard Barnette3d977b82013-04-23 11:05:19 -070035import sys
Chris Masone816e38c2012-05-02 12:22:36 -070036import tempfile
Dan Shi59ae7092013-06-04 14:37:27 -070037import threading
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070038import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070039from logging import handlers
40
Amin Hassanid4e35392019-10-03 11:02:44 -070041from six.moves import http_client
42
43# pylint: disable=no-name-in-module, import-error
J. Richard Barnette3d977b82013-04-23 11:05:19 -070044import cherrypy
Chris Sosa855b8932013-08-21 13:24:55 -070045from cherrypy import _cplogging as cplogging
Amin Hassanid4e35392019-10-03 11:02:44 -070046from cherrypy.process import plugins
47# pylint: enable=no-name-in-module, import-error
rtc@google.comded22402009-10-26 22:36:21 +000048
Richard Barnettedf35c322017-08-18 17:02:13 -070049# This must happen before any local modules get a chance to import
50# anything from chromite. Otherwise, really bad things will happen, and
51# you will _not_ understand why.
Congbin Guo3afae6c2019-08-13 16:29:42 -070052import setup_chromite # pylint: disable=unused-import
Richard Barnettedf35c322017-08-18 17:02:13 -070053
Dan Shi2f136862016-02-11 15:38:38 -080054import artifact_info
Congbin Guo3afae6c2019-08-13 16:29:42 -070055import autoupdate
Chris Sosa75490802013-09-30 17:21:45 -070056import build_artifact
Gilad Arnold11fbef42014-02-10 11:04:13 -080057import cherrypy_ext
Gilad Arnoldc65330c2012-09-20 15:17:48 -070058import common_util
Simran Basief83d6a2014-08-28 14:32:01 -070059import devserver_constants
Chris Sosa47a7d4e2012-03-28 11:26:55 -070060import downloader
Congbin Guo3afae6c2019-08-13 16:29:42 -070061import health_checker
Gilad Arnoldc65330c2012-09-20 15:17:48 -070062import log_util
joychen3cb228e2013-06-12 12:13:13 -070063import xbuddy
Gilad Arnoldc65330c2012-09-20 15:17:48 -070064
Gilad Arnoldc65330c2012-09-20 15:17:48 -070065# Module-local log function.
Chris Sosa6a3697f2013-01-29 16:44:43 -080066def _Log(message, *args):
67 return log_util.LogWithTag('DEVSERVER', message, *args)
Chris Sosa0356d3b2010-09-16 15:46:22 -070068
Dan Shi94dcbe82015-06-08 20:51:13 -070069
xixuanac89ce82016-11-30 16:48:20 -080070# Use try-except to skip unneccesary import for simple use case, eg. running
71# devserver on host.
72try:
73 import cros_update
xixuanac89ce82016-11-30 16:48:20 -080074except ImportError as e:
75 _Log('cros_update cannot be imported: %r', e)
76 cros_update = None
xixuana4f4e712017-05-08 15:17:54 -070077
78try:
79 import cros_update_progress
80except ImportError as e:
81 _Log('cros_update_progress cannot be imported: %r', e)
xixuanac89ce82016-11-30 16:48:20 -080082 cros_update_progress = None
83
xixuanac89ce82016-11-30 16:48:20 -080084try:
Dan Shi72b16132015-10-08 12:10:33 -070085 import android_build
Amin Hassanid4e35392019-10-03 11:02:44 -070086except ImportError:
Dan Shi72b16132015-10-08 12:10:33 -070087 # Ignore android_build import failure. This is to support devserver running
88 # inside a ChromeOS device triggered by cros flash. Most ChromeOS test images
89 # do not have google-api-python-client module and they don't need to support
90 # Android updating, therefore, ignore the import failure here.
Dan Shi72b16132015-10-08 12:10:33 -070091 android_build = None
Frank Farzan40160872011-12-12 18:39:18 -080092
Chris Sosa417e55d2011-01-25 16:40:48 -080093CACHED_ENTRIES = 12
Don Garrettf90edf02010-11-16 17:36:14 -080094
Simran Basi4baad082013-02-14 13:39:18 -080095TELEMETRY_FOLDER = 'telemetry_src'
96TELEMETRY_DEPS = ['dep-telemetry_dep.tar.bz2',
97 'dep-page_cycler_dep.tar.bz2',
Simran Basi0d078682013-03-22 16:40:04 -070098 'dep-chrome_test.tar.bz2',
99 'dep-perf_data_dep.tar.bz2']
Simran Basi4baad082013-02-14 13:39:18 -0800100
Chris Sosa0356d3b2010-09-16 15:46:22 -0700101# Sets up global to share between classes.
rtc@google.com21a5ca32009-11-04 18:23:23 +0000102updater = None
rtc@google.comded22402009-10-26 22:36:21 +0000103
xixuan3d48bff2017-01-30 19:00:09 -0800104# Log rotation parameters. These settings correspond to twice a day once
105# devserver is started, with about two weeks (28 backup files) of old logs
106# kept for backup.
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700107#
xixuan3d48bff2017-01-30 19:00:09 -0800108# For more, see the documentation in standard python library for
J. Richard Barnette3d977b82013-04-23 11:05:19 -0700109# logging.handlers.TimedRotatingFileHandler
xixuan3d48bff2017-01-30 19:00:09 -0800110_LOG_ROTATION_TIME = 'H'
Congbin Guo3afae6c2019-08-13 16:29:42 -0700111_LOG_ROTATION_INTERVAL = 12 # hours
112_LOG_ROTATION_BACKUP = 28 # backup counts
Frank Farzan40160872011-12-12 18:39:18 -0800113
xixuan52c2fba2016-05-20 17:02:48 -0700114# Auto-update parameters
115
116# Error msg for missing key in CrOS auto-update.
Xixuan Wu32af9f12017-11-13 14:11:44 -0800117KEY_ERROR_MSG = 'Key Error in RPC: %s= is required'
xixuan52c2fba2016-05-20 17:02:48 -0700118
119# Command of running auto-update.
120AUTO_UPDATE_CMD = '/usr/bin/python -u %s -d %s -b %s --static_dir %s'
121
122
Amin Hassanid4e35392019-10-03 11:02:44 -0700123class DevServerError(Exception):
124 """Exception class used by DevServer."""
125
126
Gabe Black3b567202015-09-23 14:07:59 -0700127def _canonicalize_archive_url(archive_url):
128 """Canonicalizes archive_url strings.
129
130 Raises:
131 DevserverError: if archive_url is not set.
132 """
133 if archive_url:
134 if not archive_url.startswith('gs://'):
Amin Hassanid4e35392019-10-03 11:02:44 -0700135 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700136 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 14:07:59 -0700137
138 return archive_url.rstrip('/')
139 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700140 raise DevServerError('Must specify an archive_url in the request')
Gabe Black3b567202015-09-23 14:07:59 -0700141
142
143def _canonicalize_local_path(local_path):
144 """Canonicalizes |local_path| strings.
145
146 Raises:
147 DevserverError: if |local_path| is not set.
148 """
149 # Restrict staging of local content to only files within the static
150 # directory.
151 local_path = os.path.abspath(local_path)
152 if not local_path.startswith(updater.static_dir):
Amin Hassanid4e35392019-10-03 11:02:44 -0700153 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700154 'Local path %s must be a subdirectory of the static'
155 ' directory: %s' % (local_path, updater.static_dir))
Gabe Black3b567202015-09-23 14:07:59 -0700156
157 return local_path.rstrip('/')
158
159
160def _get_artifacts(kwargs):
161 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
162
163 Raises:
164 DevserverError if no artifacts would be returned.
165 """
166 artifacts = kwargs.get('artifacts')
167 files = kwargs.get('files')
168 if not artifacts and not files:
Amin Hassanid4e35392019-10-03 11:02:44 -0700169 raise DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700170
171 # Note we NEED to coerce files to a string as we get raw unicode from
172 # cherrypy and we treat files as strings elsewhere in the code.
173 return (str(artifacts).split(',') if artifacts else [],
174 str(files).split(',') if files else [])
175
176
Dan Shi61305df2015-10-26 16:52:35 -0700177def _is_android_build_request(kwargs):
178 """Check if a devserver call is for Android build, based on the arguments.
179
180 This method exams the request's arguments (os_type) to determine if the
181 request is for Android build. If os_type is set to `android`, returns True.
182 If os_type is not set or has other values, returns False.
183
184 Args:
185 kwargs: Keyword arguments for the request.
186
187 Returns:
188 True if the request is for Android build. False otherwise.
189 """
190 os_type = kwargs.get('os_type', None)
191 return os_type == 'android'
192
193
Gabe Black3b567202015-09-23 14:07:59 -0700194def _get_downloader(kwargs):
195 """Returns the downloader based on passed in arguments.
196
197 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700198 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700199 """
200 local_path = kwargs.get('local_path')
201 if local_path:
202 local_path = _canonicalize_local_path(local_path)
203
204 dl = None
205 if local_path:
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800206 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
207 dl = downloader.LocalDownloader(updater.static_dir, local_path,
208 delete_source=delete_source)
Gabe Black3b567202015-09-23 14:07:59 -0700209
Dan Shi61305df2015-10-26 16:52:35 -0700210 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700211 archive_url = kwargs.get('archive_url')
212 if not archive_url and not local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700213 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700214 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700215 if archive_url and local_path:
Amin Hassanid4e35392019-10-03 11:02:44 -0700216 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700217 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700218 if not dl:
219 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700220 dl = downloader.GoogleStorageDownloader(
221 updater.static_dir, archive_url,
222 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
223 archive_url))
Gabe Black3b567202015-09-23 14:07:59 -0700224 elif not dl:
225 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 12:10:33 -0700226 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 16:52:35 -0700227 build_id = kwargs.get('build_id', None)
228 if not target or not branch or not build_id:
Amin Hassanid4e35392019-10-03 11:02:44 -0700229 raise DevServerError('target, branch, build ID must all be specified for '
230 'downloading Android build.')
Dan Shi72b16132015-10-08 12:10:33 -0700231 dl = downloader.AndroidBuildDownloader(updater.static_dir, branch, build_id,
232 target)
Gabe Black3b567202015-09-23 14:07:59 -0700233
234 return dl
235
236
237def _get_downloader_and_factory(kwargs):
238 """Returns the downloader and artifact factory based on passed in arguments.
239
240 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700241 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700242 """
243 artifacts, files = _get_artifacts(kwargs)
244 dl = _get_downloader(kwargs)
245
246 if (isinstance(dl, downloader.GoogleStorageDownloader) or
247 isinstance(dl, downloader.LocalDownloader)):
248 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 12:10:33 -0700249 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 14:07:59 -0700250 factory_class = build_artifact.AndroidArtifactFactory
251 else:
Amin Hassanid4e35392019-10-03 11:02:44 -0700252 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700253 'Unrecognized value for downloader type: %s' % type(dl))
Gabe Black3b567202015-09-23 14:07:59 -0700254
255 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
256
257 return dl, factory
258
259
Scott Zawalski4647ce62012-01-03 17:17:28 -0500260def _LeadingWhiteSpaceCount(string):
261 """Count the amount of leading whitespace in a string.
262
263 Args:
264 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800265
Scott Zawalski4647ce62012-01-03 17:17:28 -0500266 Returns:
267 number of white space chars before characters start.
268 """
Gabe Black3b567202015-09-23 14:07:59 -0700269 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500270 if matched:
271 return len(matched.group())
272
273 return 0
274
275
276def _PrintDocStringAsHTML(func):
277 """Make a functions docstring somewhat HTML style.
278
279 Args:
280 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800281
Scott Zawalski4647ce62012-01-03 17:17:28 -0500282 Returns:
283 A string that is somewhat formated for a web browser.
284 """
285 # TODO(scottz): Make this parse Args/Returns in a prettier way.
286 # Arguments could be bolded and indented etc.
287 html_doc = []
288 for line in func.__doc__.splitlines():
289 leading_space = _LeadingWhiteSpaceCount(line)
290 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700291 line = ' ' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500292
293 html_doc.append('<BR>%s' % line)
294
295 return '\n'.join(html_doc)
296
297
Simran Basief83d6a2014-08-28 14:32:01 -0700298def _GetUpdateTimestampHandler(static_dir):
299 """Returns a handler to update directory staged.timestamp.
300
301 This handler resets the stage.timestamp whenever static content is accessed.
302
303 Args:
304 static_dir: Directory from which static content is being staged.
305
306 Returns:
Amin Hassani08e42d22019-06-03 00:31:30 -0700307 A cherrypy handler to update the timestamp of accessed content.
Simran Basief83d6a2014-08-28 14:32:01 -0700308 """
309 def UpdateTimestampHandler():
310 if not '404' in cherrypy.response.status:
311 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
312 cherrypy.request.path_info)
313 if build_match:
314 build_dir = os.path.join(static_dir, build_match.group('build'))
315 downloader.Downloader.TouchTimestampForStaged(build_dir)
316 return UpdateTimestampHandler
317
318
Chris Sosa7c931362010-10-11 19:49:01 -0700319def _GetConfig(options):
320 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800321
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800322 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800323 # Fall back to IPv4 when python is not configured with IPv6.
324 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800325 socket_host = '0.0.0.0'
326
Simran Basief83d6a2014-08-28 14:32:01 -0700327 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
328 # on the on_end_resource hook. This hook is called once processing is
329 # complete and the response is ready to be returned.
330 cherrypy.tools.update_timestamp = cherrypy.Tool(
331 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
332
David Riley2fcb0122017-11-02 11:25:39 -0700333 base_config = {
334 'global': {
335 'server.log_request_headers': True,
336 'server.protocol_version': 'HTTP/1.1',
337 'server.socket_host': socket_host,
338 'server.socket_port': int(options.port),
339 'response.timeout': 6000,
340 'request.show_tracebacks': True,
341 'server.socket_timeout': 60,
342 'server.thread_pool': 2,
343 'engine.autoreload.on': False,
344 },
345 '/api': {
346 # Gets rid of cherrypy parsing post file for args.
347 'request.process_request_body': False,
348 },
349 '/build': {
350 'response.timeout': 100000,
351 },
352 '/update': {
353 # Gets rid of cherrypy parsing post file for args.
354 'request.process_request_body': False,
355 'response.timeout': 10000,
356 },
357 # Sets up the static dir for file hosting.
358 '/static': {
359 'tools.staticdir.dir': options.static_dir,
360 'tools.staticdir.on': True,
361 'response.timeout': 10000,
362 'tools.update_timestamp.on': True,
363 },
364 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700365 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700366 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500367
Chris Sosa7c931362010-10-11 19:49:01 -0700368 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000369
Darin Petkove17164a2010-08-11 13:24:41 -0700370
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700371def _GetRecursiveMemberObject(root, member_list):
372 """Returns an object corresponding to a nested member list.
373
374 Args:
375 root: the root object to search
376 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800377
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700378 Returns:
379 An object corresponding to the member name list; None otherwise.
380 """
381 for member in member_list:
382 next_root = root.__class__.__dict__.get(member)
383 if not next_root:
384 return None
385 root = next_root
386 return root
387
388
389def _IsExposed(name):
390 """Returns True iff |name| has an `exposed' attribute and it is set."""
391 return hasattr(name, 'exposed') and name.exposed
392
393
Congbin Guo6bc32182019-08-20 17:54:30 -0700394def _GetExposedMethod(nested_member):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700395 """Returns a CherryPy-exposed method, if such exists.
396
397 Args:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700398 nested_member: a slash-joined path to the nested member
Don Garrettf84631a2014-01-07 18:21:26 -0800399
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700400 Returns:
Congbin Guo6bc32182019-08-20 17:54:30 -0700401 A function object corresponding to the path defined by |nested_member| from
402 the app root object registered, if the function is exposed; None otherwise.
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700403 """
Congbin Guo6bc32182019-08-20 17:54:30 -0700404 for app in cherrypy.tree.apps.values():
405 # Use the 'index' function doc as the doc of the app.
406 if nested_member == app.script_name.lstrip('/'):
407 nested_member = 'index'
408
409 method = _GetRecursiveMemberObject(app.root, nested_member.split('/'))
410 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
411 return method
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700412
413
Gilad Arnold748c8322012-10-12 09:51:35 -0700414def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700415 """Finds exposed CherryPy methods.
416
417 Args:
418 root: the root object for searching
419 prefix: slash-joined chain of members leading to current object
420 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800421
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700422 Returns:
423 List of exposed URLs that are not unlisted.
424 """
425 method_list = []
Congbin Guo6bc32182019-08-20 17:54:30 -0700426 for member in root.__class__.__dict__.keys():
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700427 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700428 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700429 continue
430 member_obj = root.__class__.__dict__[member]
431 if _IsExposed(member_obj):
Amin Hassani08e42d22019-06-03 00:31:30 -0700432 if isinstance(member_obj, types.FunctionType):
Congbin Guo6bc32182019-08-20 17:54:30 -0700433 # Regard the app name as exposed "method" name if it exposed 'index'
434 # function.
435 if prefix and member == 'index':
436 method_list.append(prefix)
437 else:
438 method_list.append(prefixed_member)
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700439 else:
440 method_list += _FindExposedMethods(
441 member_obj, prefixed_member, unlisted)
442 return method_list
443
444
xixuan52c2fba2016-05-20 17:02:48 -0700445def _check_base_args_for_auto_update(kwargs):
xixuanac89ce82016-11-30 16:48:20 -0800446 """Check basic args required for auto-update.
447
448 Args:
449 kwargs: the parameters to be checked.
450
451 Raises:
452 DevServerHTTPError if required parameters don't exist in kwargs.
453 """
xixuan52c2fba2016-05-20 17:02:48 -0700454 if 'host_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700455 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700456 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700457
458 if 'build_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700459 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700460 KEY_ERROR_MSG % 'build_name')
xixuan52c2fba2016-05-20 17:02:48 -0700461
462
463def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-11-30 16:48:20 -0800464 """Parse boolean arg from kwargs.
465
466 Args:
467 kwargs: the parameters to be checked.
468 key: the key to be parsed.
469
470 Returns:
471 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
472
473 Raises:
474 DevServerHTTPError if kwargs[key] is not a boolean variable.
475 """
xixuan52c2fba2016-05-20 17:02:48 -0700476 if key in kwargs:
477 if kwargs[key] == 'True':
478 return True
479 elif kwargs[key] == 'False':
480 return False
481 else:
482 raise common_util.DevServerHTTPError(
Amin Hassanid4e35392019-10-03 11:02:44 -0700483 http_client.INTERNAL_SERVER_ERROR,
xixuan52c2fba2016-05-20 17:02:48 -0700484 'The value for key %s is not boolean.' % key)
485 else:
486 return False
487
xixuan447ad9d2017-02-28 14:46:20 -0800488
xixuanac89ce82016-11-30 16:48:20 -0800489def _parse_string_arg(kwargs, key):
490 """Parse string arg from kwargs.
491
492 Args:
493 kwargs: the parameters to be checked.
494 key: the key to be parsed.
495
496 Returns:
497 The string value of kwargs[key], or None if key doesn't exist in kwargs.
498 """
499 if key in kwargs:
500 return kwargs[key]
501 else:
502 return None
503
xixuan447ad9d2017-02-28 14:46:20 -0800504
xixuanac89ce82016-11-30 16:48:20 -0800505def _build_uri_from_build_name(build_name):
506 """Get build url from a given build name.
507
508 Args:
509 build_name: the build name to be parsed, whose format is
510 'board/release_version'.
511
512 Returns:
513 The release_archive_url on Google Storage for this build name.
514 """
Amin Hassani08e42d22019-06-03 00:31:30 -0700515 # TODO(ahassani): This function doesn't seem to be used anywhere since its
516 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
517 # causing any runtime issues. So deprecate this in the future.
518 tokens = build_name.split('/')
519 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-20 17:02:48 -0700520
xixuan447ad9d2017-02-28 14:46:20 -0800521
522def _clear_process(host_name, pid):
523 """Clear AU process for given hostname and pid.
524
525 This clear includes:
526 1. kill process if it's alive.
527 2. delete the track status file of this process.
528 3. delete the executing log file of this process.
529
530 Args:
531 host_name: the host to execute auto-update.
532 pid: the background auto-update process id.
533 """
534 if cros_update_progress.IsProcessAlive(pid):
535 os.killpg(int(pid), signal.SIGKILL)
536
537 cros_update_progress.DelTrackStatusFile(host_name, pid)
538 cros_update_progress.DelExecuteLogFile(host_name, pid)
539
540
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700541class ApiRoot(object):
542 """RESTful API for Dev Server information."""
543 exposed = True
544
545 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800546 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700547 """Returns a JSON object containing a log of host event.
548
549 Args:
550 ip: address of host whose event log is requested, or `all'
Don Garrettf84631a2014-01-07 18:21:26 -0800551
Gilad Arnold1b908392012-10-05 11:36:27 -0700552 Returns:
Amin Hassani7c447852019-09-26 15:01:48 -0700553 A JSON dictionary containing all or some of the following fields:
Amin Hassanie7ead902019-10-11 16:42:43 -0700554 version: The Chromium OS version the device is running.
555 track: The channel the device is running on.
556 board: The device's board.
557 event_result: The event result of Omaha request.
558 event_type: The event type of Omaha request.
559 previous_version: The Chromium OS version we updated and rebooted from.
560 timestamp: The timestamp the event was received.
Amin Hassani7c447852019-09-26 15:01:48 -0700561 See the OmahaEvent class in update_engine/omaha_request_action.h for
562 event type and status code definitions. If the ip does not exist an empty
563 string is returned.
Gilad Arnold1b908392012-10-05 11:36:27 -0700564
565 Example URL:
566 http://myhost/api/hostlog?ip=192.168.1.5
567 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800568 return updater.HandleHostLogPing(ip)
569
570 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800571 def fileinfo(self, *args):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700572 """Returns information about a given staged file.
573
574 Args:
Don Garrettf84631a2014-01-07 18:21:26 -0800575 args: path to the file inside the server's static staging directory
576
Gilad Arnold55a2a372012-10-02 09:46:32 -0700577 Returns:
578 A JSON encoded dictionary with information about the said file, which may
579 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700580 size (int): the file size in bytes
Gilad Arnold1b908392012-10-05 11:36:27 -0700581 sha256 (string): a base64 encoded SHA256 hash
582
583 Example URL:
584 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700585 """
Don Garrettf84631a2014-01-07 18:21:26 -0800586 file_path = os.path.join(updater.static_dir, *args)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700587 if not os.path.exists(file_path):
Amin Hassanid4e35392019-10-03 11:02:44 -0700588 raise DevServerError('file not found: %s' % file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700589 try:
590 file_size = os.path.getsize(file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700591 file_sha256 = common_util.GetFileSha256(file_path)
592 except os.error, e:
Amin Hassanid4e35392019-10-03 11:02:44 -0700593 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700594 'failed to get info for file %s: %s' % (file_path, e))
Gilad Arnolde74b3812013-04-22 11:27:38 -0700595
Gilad Arnolde74b3812013-04-22 11:27:38 -0700596 return json.dumps({
597 autoupdate.Autoupdate.SIZE_ATTR: file_size,
Gilad Arnolde74b3812013-04-22 11:27:38 -0700598 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
Gilad Arnolde74b3812013-04-22 11:27:38 -0700599 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700600
Chris Sosa76e44b92013-01-31 12:11:38 -0800601
David Rochberg7c79a812011-01-19 14:24:45 -0500602class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700603 """The Root Class for the Dev Server.
604
605 CherryPy works as follows:
606 For each method in this class, cherrpy interprets root/path
607 as a call to an instance of DevServerRoot->method_name. For example,
608 a call to http://myhost/build will call build. CherryPy automatically
609 parses http args and places them as keyword arguments in each method.
610 For paths http://myhost/update/dir1/dir2, you can use *args so that
611 cherrypy uses the update method and puts the extra paths in args.
612 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700613 # Method names that should not be listed on the index page.
614 _UNLISTED_METHODS = ['index', 'doc']
615
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700616 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700617
Dan Shi59ae7092013-06-04 14:37:27 -0700618 # Number of threads that devserver is staging images.
619 _staging_thread_count = 0
620 # Lock used to lock increasing/decreasing count.
621 _staging_thread_count_lock = threading.Lock()
622
joychen3cb228e2013-06-12 12:13:13 -0700623 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700624 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800625 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700626 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500627
Congbin Guo3afae6c2019-08-13 16:29:42 -0700628 @property
629 def staging_thread_count(self):
630 """Get the staging thread count."""
631 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700632
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700633 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500634 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700635 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700636 import builder
637 if self._builder is None:
638 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500639 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700640
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700641 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700642 def is_staged(self, **kwargs):
643 """Check if artifacts have been downloaded.
644
Congbin Guo3afae6c2019-08-13 16:29:42 -0700645 Examples:
646 To check if autotest and test_suites are staged:
647 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
648 artifacts=autotest,test_suites
649
Amin Hassani08e42d22019-06-03 00:31:30 -0700650 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700651 async: True to return without waiting for download to complete.
652 artifacts: Comma separated list of named artifacts to download.
653 These are defined in artifact_info and have their implementation
654 in build_artifact.py.
655 files: Comma separated list of file artifacts to stage. These
656 will be available as is in the corresponding static directory with no
657 custom post-processing.
658
Congbin Guo3afae6c2019-08-13 16:29:42 -0700659 Returns:
660 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700661 """
Gabe Black3b567202015-09-23 14:07:59 -0700662 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700663 response = str(dl.IsStaged(factory))
664 _Log('Responding to is_staged %s request with %r', kwargs, response)
665 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700666
Chris Sosa76e44b92013-01-31 12:11:38 -0800667 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800668 def list_image_dir(self, **kwargs):
669 """Take an archive url and list the contents in its staged directory.
670
Amin Hassani08e42d22019-06-03 00:31:30 -0700671 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800672 To list the contents of where this devserver should have staged
673 gs://image-archive/<board>-release/<build> call:
674 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
675
Congbin Guo3afae6c2019-08-13 16:29:42 -0700676 Args:
677 archive_url: Google Storage URL for the build.
678
Prashanth Ba06d2d22014-03-07 15:35:19 -0800679 Returns:
680 A string with information about the contents of the image directory.
681 """
Gabe Black3b567202015-09-23 14:07:59 -0700682 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800683 try:
Gabe Black3b567202015-09-23 14:07:59 -0700684 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800685 except build_artifact.ArtifactDownloadError as e:
686 return 'Cannot list the contents of staged artifacts. %s' % e
687 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700688 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800689 return image_dir_contents
690
691 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800692 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700693 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800694
Gabe Black3b567202015-09-23 14:07:59 -0700695 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700696 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700697 on the devserver. A call to this will attempt to cache non-specified
698 artifacts in the background for the given from the given URL following
699 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800700 artifacts is explicitly defined in the build_artifact module.
701
702 These artifacts will then be available from the static/ sub-directory of
703 the devserver.
704
Amin Hassani08e42d22019-06-03 00:31:30 -0700705 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800706 To download the autotest and test suites tarballs:
707 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
708 artifacts=autotest,test_suites
709 To download the full update payload:
710 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
711 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700712 To download just a file called blah.bin:
713 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
714 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800715
716 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700717 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800718
719 Note for this example, relative path is the archive_url stripped of its
720 basename i.e. path/ in the examples above. Specific example:
721
722 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
723
724 Will get staged to:
725
joychened64b222013-06-21 16:39:34 -0700726 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700727
728 Args:
729 archive_url: Google Storage URL for the build.
730 local_path: Local path for the build.
731 delete_source: Only meaningful with local_path. bool to indicate if the
732 source files should be deleted. This is especially useful when staging
733 a file locally in resource constrained environments as it allows us to
734 move the relevant files locally instead of copying them.
735 async: True to return without waiting for download to complete.
736 artifacts: Comma separated list of named artifacts to download.
737 These are defined in artifact_info and have their implementation
738 in build_artifact.py.
739 files: Comma separated list of files to stage. These
740 will be available as is in the corresponding static directory with no
741 custom post-processing.
742 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800743 """
Gabe Black3b567202015-09-23 14:07:59 -0700744 dl, factory = _get_downloader_and_factory(kwargs)
745
Dan Shi59ae7092013-06-04 14:37:27 -0700746 with DevServerRoot._staging_thread_count_lock:
747 DevServerRoot._staging_thread_count += 1
748 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800749 boolean_string = kwargs.get('clean')
750 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
751 if clean and os.path.exists(dl.GetBuildDir()):
752 _Log('Removing %s' % dl.GetBuildDir())
753 shutil.rmtree(dl.GetBuildDir())
Gabe Black3b567202015-09-23 14:07:59 -0700754 async = kwargs.get('async', False)
755 dl.Download(factory, async=async)
Dan Shi59ae7092013-06-04 14:37:27 -0700756 finally:
757 with DevServerRoot._staging_thread_count_lock:
758 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800759 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700760
761 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700762 def cros_au(self, **kwargs):
763 """Auto-update a CrOS DUT.
764
765 Args:
766 kwargs:
767 host_name: the hostname of the DUT to auto-update.
768 build_name: the build name for update the DUT.
769 force_update: Force an update even if the version installed is the
770 same. Default: False.
771 full_update: If True, do not run stateful update, directly force a full
772 reimage. If False, try stateful update first if the dut is already
773 installed with the same version.
774 async: Whether the auto_update function is ran in the background.
David Rileyee75de22017-11-02 10:48:15 -0700775 quick_provision: Whether the quick provision path is attempted first.
xixuan52c2fba2016-05-20 17:02:48 -0700776
777 Returns:
778 A tuple includes two elements:
779 a boolean variable represents whether the auto-update process is
780 successfully started.
781 an integer represents the background auto-update process id.
782 """
783 _check_base_args_for_auto_update(kwargs)
784
785 host_name = kwargs['host_name']
786 build_name = kwargs['build_name']
787 force_update = _parse_boolean_arg(kwargs, 'force_update')
788 full_update = _parse_boolean_arg(kwargs, 'full_update')
789 async = _parse_boolean_arg(kwargs, 'async')
xixuanac89ce82016-11-30 16:48:20 -0800790 original_build = _parse_string_arg(kwargs, 'original_build')
David Haddock90e49442017-04-07 19:14:09 -0700791 payload_filename = _parse_string_arg(kwargs, 'payload_filename')
David Haddock20559612017-06-28 22:15:08 -0700792 clobber_stateful = _parse_boolean_arg(kwargs, 'clobber_stateful')
David Rileyee75de22017-11-02 10:48:15 -0700793 quick_provision = _parse_boolean_arg(kwargs, 'quick_provision')
794
795 devserver_url = updater.GetDevserverUrl()
796 static_url = updater.GetStaticUrl()
xixuan52c2fba2016-05-20 17:02:48 -0700797
798 if async:
799 path = os.path.dirname(os.path.abspath(__file__))
800 execute_file = os.path.join(path, 'cros_update.py')
801 args = (AUTO_UPDATE_CMD % (execute_file, host_name, build_name,
802 updater.static_dir))
xixuanac89ce82016-11-30 16:48:20 -0800803
804 # The original_build's format is like: link/3428.210.0
805 # The corresponding release_archive_url's format is like:
806 # gs://chromeos-releases/stable-channel/link/3428.210.0
807 if original_build:
808 release_archive_url = _build_uri_from_build_name(original_build)
809 # First staging the stateful.tgz synchronousely.
810 self.stage(files='stateful.tgz', async=False,
811 archive_url=release_archive_url)
812 args = ('%s --original_build %s' % (args, original_build))
813
xixuan52c2fba2016-05-20 17:02:48 -0700814 if force_update:
815 args = ('%s --force_update' % args)
816
817 if full_update:
818 args = ('%s --full_update' % args)
819
David Haddock90e49442017-04-07 19:14:09 -0700820 if payload_filename:
821 args = ('%s --payload_filename %s' % (args, payload_filename))
822
David Haddock20559612017-06-28 22:15:08 -0700823 if clobber_stateful:
824 args = ('%s --clobber_stateful' % args)
825
David Rileyee75de22017-11-02 10:48:15 -0700826 if quick_provision:
827 args = ('%s --quick_provision' % args)
828
829 if devserver_url:
830 args = ('%s --devserver_url %s' % (args, devserver_url))
831
832 if static_url:
833 args = ('%s --static_url %s' % (args, static_url))
834
xixuan2a0970a2016-08-10 12:12:44 -0700835 p = subprocess.Popen([args], shell=True, preexec_fn=os.setsid)
836 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-20 17:02:48 -0700837
838 # Pre-write status in the track_status_file before the first call of
839 # 'get_au_status' to make sure that the track_status_file exists.
xixuan2a0970a2016-08-10 12:12:44 -0700840 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700841 progress_tracker.WriteStatus('CrOS update is just started.')
842
xixuan2a0970a2016-08-10 12:12:44 -0700843 return json.dumps((True, pid))
xixuan52c2fba2016-05-20 17:02:48 -0700844 else:
845 cros_update_trigger = cros_update.CrOSUpdateTrigger(
xixuanac89ce82016-11-30 16:48:20 -0800846 host_name, build_name, updater.static_dir, force_update=force_update,
David Rileyee75de22017-11-02 10:48:15 -0700847 full_update=full_update, original_build=original_build,
848 quick_provision=quick_provision, devserver_url=devserver_url,
849 static_url=static_url)
xixuan52c2fba2016-05-20 17:02:48 -0700850 cros_update_trigger.TriggerAU()
xixuan27d50442017-08-09 10:38:25 -0700851 return json.dumps((True, -1))
xixuan52c2fba2016-05-20 17:02:48 -0700852
853 @cherrypy.expose
854 def get_au_status(self, **kwargs):
855 """Check if the auto-update task is finished.
856
857 It handles 4 cases:
858 1. If an error exists in the track_status_file, delete the track file and
859 raise it.
860 2. If cros-update process is finished, delete the file and return the
861 success result.
862 3. If the process is not running, delete the track file and raise an error
863 about 'the process is terminated due to unknown reason'.
864 4. If the track_status_file does not exist, kill the process if it exists,
865 and raise the IOError.
866
867 Args:
868 kwargs:
869 host_name: the hostname of the DUT to auto-update.
870 pid: the background process id of cros-update.
871
872 Returns:
xixuan28d99072016-10-06 12:24:16 -0700873 A dict with three elements:
xixuan52c2fba2016-05-20 17:02:48 -0700874 a boolean variable represents whether the auto-update process is
875 finished.
876 a string represents the current auto-update process status.
877 For example, 'Transfer Devserver/Stateful Update Package'.
xixuan28d99072016-10-06 12:24:16 -0700878 a detailed error message paragraph if there exists an Auto-Update
879 error, in which the last line shows the main exception. Empty
880 string otherwise.
xixuan52c2fba2016-05-20 17:02:48 -0700881 """
882 if 'host_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700883 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700884 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700885
886 if 'pid' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700887 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700888 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700889
890 host_name = kwargs['host_name']
891 pid = kwargs['pid']
892 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
893
xixuan28d99072016-10-06 12:24:16 -0700894 result_dict = {'finished': False, 'status': '', 'detailed_error_msg': ''}
xixuan52c2fba2016-05-20 17:02:48 -0700895 try:
896 result = progress_tracker.ReadStatus()
897 if result.startswith(cros_update_progress.ERROR_TAG):
xixuan28d99072016-10-06 12:24:16 -0700898 result_dict['detailed_error_msg'] = result[len(
899 cros_update_progress.ERROR_TAG):]
xixuan28681fd2016-11-23 11:13:56 -0800900 elif result == cros_update_progress.FINISHED:
xixuan28d99072016-10-06 12:24:16 -0700901 result_dict['finished'] = True
902 result_dict['status'] = result
xixuan28681fd2016-11-23 11:13:56 -0800903 elif not cros_update_progress.IsProcessAlive(pid):
xixuan28d99072016-10-06 12:24:16 -0700904 result_dict['detailed_error_msg'] = (
905 'Cros_update process terminated midway due to unknown reason. '
906 'Last update status was %s' % result)
xixuan28681fd2016-11-23 11:13:56 -0800907 else:
908 result_dict['status'] = result
909 except IOError as e:
910 if pid and cros_update_progress.IsProcessAlive(pid):
xixuan2a0970a2016-08-10 12:12:44 -0700911 os.killpg(int(pid), signal.SIGKILL)
xixuan52c2fba2016-05-20 17:02:48 -0700912
xixuan28681fd2016-11-23 11:13:56 -0800913 result_dict['detailed_error_msg'] = str(e)
914
915 return json.dumps(result_dict)
xixuan52c2fba2016-05-20 17:02:48 -0700916
917 @cherrypy.expose
David Riley6d5fca02017-10-31 10:35:47 -0700918 def post_au_status(self, status, **kwargs):
919 """Updates the status of an auto-update task.
920
921 Callers will need to POST to this URL with a body of MIME-type
922 "multipart/form-data".
923 The body should include a single argument, 'status', containing the
924 AU status to record.
925
926 Args:
927 status: The updated status.
928 kwargs:
929 host_name: the hostname of the DUT to auto-update.
930 pid: the background process id of cros-update.
931 """
932 if 'host_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700933 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700934 KEY_ERROR_MSG % 'host_name')
David Riley6d5fca02017-10-31 10:35:47 -0700935
936 if 'pid' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700937 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700938 KEY_ERROR_MSG % 'pid')
David Riley6d5fca02017-10-31 10:35:47 -0700939
940 host_name = kwargs['host_name']
941 pid = kwargs['pid']
David Riley3cea2582017-11-24 22:03:01 -0800942 status = status.rstrip()
943 _Log('Recording status for %s (%s): %s' % (host_name, pid, status))
David Riley6d5fca02017-10-31 10:35:47 -0700944 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
945
David Riley3cea2582017-11-24 22:03:01 -0800946 progress_tracker.WriteStatus(status)
David Riley6d5fca02017-10-31 10:35:47 -0700947
948 return 'True'
949
950 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700951 def handler_cleanup(self, **kwargs):
xixuan3bc974e2016-10-18 17:21:43 -0700952 """Clean track status log and temp directory for CrOS auto-update process.
xixuan52c2fba2016-05-20 17:02:48 -0700953
954 Args:
955 kwargs:
956 host_name: the hostname of the DUT to auto-update.
957 pid: the background process id of cros-update.
958 """
959 if 'host_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700960 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700961 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700962
963 if 'pid' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700964 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700965 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700966
967 host_name = kwargs['host_name']
968 pid = kwargs['pid']
969 cros_update_progress.DelTrackStatusFile(host_name, pid)
xixuan3bc974e2016-10-18 17:21:43 -0700970 cros_update_progress.DelAUTempDirectory(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700971
972 @cherrypy.expose
973 def kill_au_proc(self, **kwargs):
974 """Kill CrOS auto-update process using given process id.
975
976 Args:
977 kwargs:
978 host_name: Kill all the CrOS auto-update process of this host.
979
980 Returns:
981 True if all processes are killed properly.
982 """
983 if 'host_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700984 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700985 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700986
xixuan447ad9d2017-02-28 14:46:20 -0800987 cur_pid = kwargs.get('pid')
988
xixuan52c2fba2016-05-20 17:02:48 -0700989 host_name = kwargs['host_name']
xixuan3bc974e2016-10-18 17:21:43 -0700990 track_log_list = cros_update_progress.GetAllTrackStatusFileByHostName(
991 host_name)
xixuan52c2fba2016-05-20 17:02:48 -0700992 for log in track_log_list:
993 # The track log's full path is: path/host_name_pid.log
994 # Use splitext to remove file extension, then parse pid from the
995 # filename.
Congbin Guo3afae6c2019-08-13 16:29:42 -0700996 pid = os.path.splitext(os.path.basename(log))[0][len(host_name) + 1:]
xixuan447ad9d2017-02-28 14:46:20 -0800997 _clear_process(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700998
xixuan447ad9d2017-02-28 14:46:20 -0800999 if cur_pid:
1000 _clear_process(host_name, cur_pid)
xixuan52c2fba2016-05-20 17:02:48 -07001001
1002 return 'True'
1003
1004 @cherrypy.expose
1005 def collect_cros_au_log(self, **kwargs):
1006 """Collect CrOS auto-update log.
1007
1008 Args:
1009 kwargs:
1010 host_name: the hostname of the DUT to auto-update.
1011 pid: the background process id of cros-update.
1012
1013 Returns:
David Haddock9f459632017-05-11 14:45:46 -07001014 A dictionary containing the execute log file and any hostlog files.
xixuan52c2fba2016-05-20 17:02:48 -07001015 """
1016 if 'host_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001017 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001018 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001019
1020 if 'pid' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001021 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001022 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -07001023
1024 host_name = kwargs['host_name']
1025 pid = kwargs['pid']
xixuan3bc974e2016-10-18 17:21:43 -07001026
1027 # Fetch the execute log recorded by cros_update_progress.
xixuan1bbfaba2016-10-13 17:53:22 -07001028 au_log = cros_update_progress.ReadExecuteLogFile(host_name, pid)
1029 cros_update_progress.DelExecuteLogFile(host_name, pid)
David Haddock9f459632017-05-11 14:45:46 -07001030 # Fetch the cros_au host_logs if they exist
1031 au_hostlogs = cros_update_progress.ReadAUHostLogFiles(host_name, pid)
1032 return json.dumps({'cros_au_log': au_log, 'host_logs': au_hostlogs})
xixuan1bbfaba2016-10-13 17:53:22 -07001033
xixuan52c2fba2016-05-20 17:02:48 -07001034 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -08001035 def locate_file(self, **kwargs):
1036 """Get the path to the given file name.
1037
1038 This method looks up the given file name inside specified build artifacts.
1039 One use case is to help caller to locate an apk file inside a build
1040 artifact. The location of the apk file could be different based on the
1041 branch and target.
1042
1043 Args:
1044 file_name: Name of the file to look for.
1045 artifacts: A list of artifact names to search for the file.
1046
1047 Returns:
1048 Path to the file with the given name. It's relative to the folder for the
1049 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -08001050 """
1051 dl, _ = _get_downloader_and_factory(kwargs)
1052 try:
Joe Brennan1691f8e2017-03-15 15:53:36 -07001053 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 15:38:38 -08001054 artifacts = kwargs['artifacts']
1055 except KeyError:
Amin Hassanid4e35392019-10-03 11:02:44 -07001056 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001057 '`file_name` and `artifacts` are required to search '
1058 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 15:38:38 -08001059 build_path = dl.GetBuildDir()
1060 for artifact in artifacts:
1061 # Get the unzipped folder of the artifact. If it's not defined in
1062 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
1063 # directory directly.
1064 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
1065 artifact_path = os.path.join(build_path, folder)
1066 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 15:53:36 -07001067 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 15:38:38 -08001068 return os.path.relpath(os.path.join(root, file_name), build_path)
Amin Hassanid4e35392019-10-03 11:02:44 -07001069 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001070 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 15:38:38 -08001071
1072 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -08001073 def setup_telemetry(self, **kwargs):
1074 """Extracts and sets up telemetry
1075
1076 This method goes through the telemetry deps packages, and stages them on
1077 the devserver to be used by the drones and the telemetry tests.
1078
1079 Args:
1080 archive_url: Google Storage URL for the build.
1081
1082 Returns:
1083 Path to the source folder for the telemetry codebase once it is staged.
1084 """
Gabe Black3b567202015-09-23 14:07:59 -07001085 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 13:39:18 -08001086
Gabe Black3b567202015-09-23 14:07:59 -07001087 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -08001088 deps_path = os.path.join(build_path, 'autotest/packages')
1089 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
1090 src_folder = os.path.join(telemetry_path, 'src')
1091
1092 with self._telemetry_lock_dict.lock(telemetry_path):
1093 if os.path.exists(src_folder):
1094 # Telemetry is already fully stage return
1095 return src_folder
1096
1097 common_util.MkDirP(telemetry_path)
1098
1099 # Copy over the required deps tar balls to the telemetry directory.
1100 for dep in TELEMETRY_DEPS:
1101 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -07001102 if not os.path.exists(dep_path):
1103 # This dep does not exist (could be new), do not extract it.
1104 continue
Simran Basi4baad082013-02-14 13:39:18 -08001105 try:
1106 common_util.ExtractTarball(dep_path, telemetry_path)
1107 except common_util.CommonUtilError as e:
1108 shutil.rmtree(telemetry_path)
Amin Hassanid4e35392019-10-03 11:02:44 -07001109 raise DevServerError(str(e))
Simran Basi4baad082013-02-14 13:39:18 -08001110
1111 # By default all the tarballs extract to test_src but some parts of
1112 # the telemetry code specifically hardcoded to exist inside of 'src'.
1113 test_src = os.path.join(telemetry_path, 'test_src')
1114 try:
1115 shutil.move(test_src, src_folder)
1116 except shutil.Error:
1117 # This can occur if src_folder already exists. Remove and retry move.
1118 shutil.rmtree(src_folder)
Amin Hassanid4e35392019-10-03 11:02:44 -07001119 raise DevServerError(
Gabe Black3b567202015-09-23 14:07:59 -07001120 'Failure in telemetry setup for build %s. Appears that the '
1121 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -08001122
1123 return src_folder
1124
1125 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -08001126 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -07001127 """Symbolicates a minidump using pre-downloaded symbols, returns it.
1128
1129 Callers will need to POST to this URL with a body of MIME-type
1130 "multipart/form-data".
1131 The body should include a single argument, 'minidump', containing the
1132 binary-formatted minidump to symbolicate.
1133
Chris Masone816e38c2012-05-02 12:22:36 -07001134 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -08001135 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -07001136 minidump: The binary minidump file to symbolicate.
1137 """
Chris Sosa76e44b92013-01-31 12:11:38 -08001138 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -07001139 # Try debug.tar.xz first, then debug.tgz
1140 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
1141 kwargs['artifacts'] = artifact
1142 dl = _get_downloader(kwargs)
1143
1144 try:
1145 if self.stage(**kwargs) == 'Success':
1146 break
1147 except build_artifact.ArtifactDownloadError:
1148 continue
1149 else:
Amin Hassanid4e35392019-10-03 11:02:44 -07001150 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001151 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -08001152
Chris Masone816e38c2012-05-02 12:22:36 -07001153 to_return = ''
1154 with tempfile.NamedTemporaryFile() as local:
1155 while True:
1156 data = minidump.file.read(8192)
1157 if not data:
1158 break
1159 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -08001160
Chris Masone816e38c2012-05-02 12:22:36 -07001161 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -08001162
Gabe Black3b567202015-09-23 14:07:59 -07001163 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -08001164
xixuanab744382017-04-27 10:41:27 -07001165 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 12:11:38 -08001166 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 10:41:27 -07001167 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 12:11:38 -08001168 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1169
Chris Masone816e38c2012-05-02 12:22:36 -07001170 to_return, error_text = stackwalk.communicate()
1171 if stackwalk.returncode != 0:
Amin Hassanid4e35392019-10-03 11:02:44 -07001172 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001173 "Can't generate stack trace: %s (rc=%d)" % (error_text,
1174 stackwalk.returncode))
Chris Masone816e38c2012-05-02 12:22:36 -07001175
1176 return to_return
1177
1178 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001179 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -04001180 """Return a string representing the latest build for a given target.
1181
1182 Args:
1183 target: The build target, typically a combination of the board and the
1184 type of build e.g. x86-mario-release.
1185 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
1186 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -08001187
Scott Zawalski16954532012-03-20 15:31:36 -04001188 Returns:
1189 A string representation of the latest build if one exists, i.e.
1190 R19-1993.0.0-a1-b1480.
1191 An empty string if no latest could be found.
1192 """
Don Garrettf84631a2014-01-07 18:21:26 -08001193 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -04001194 return _PrintDocStringAsHTML(self.latestbuild)
1195
Don Garrettf84631a2014-01-07 18:21:26 -08001196 if 'target' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001197 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001198 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -07001199
1200 if _is_android_build_request(kwargs):
1201 branch = kwargs.get('branch', None)
1202 target = kwargs.get('target', None)
1203 if not target or not branch:
Amin Hassanid4e35392019-10-03 11:02:44 -07001204 raise DevServerError('Both target and branch must be specified to query'
1205 ' for the latest Android build.')
Dan Shi61305df2015-10-26 16:52:35 -07001206 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
1207
Scott Zawalski16954532012-03-20 15:31:36 -04001208 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001209 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-07 18:21:26 -08001210 updater.static_dir, kwargs['target'],
1211 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -07001212 except common_util.CommonUtilError as errmsg:
Amin Hassanid4e35392019-10-03 11:02:44 -07001213 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001214 str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -04001215
1216 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -07001217 def list_suite_controls(self, **kwargs):
1218 """Return a list of contents of all known control files.
1219
1220 Example URL:
1221 To List all control files' content:
1222 http://dev-server/list_suite_controls?suite_name=bvt&
1223 build=daisy_spring-release/R29-4279.0.0
1224
1225 Args:
1226 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
1227 suite_name: List the control files belonging to that suite.
1228
1229 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -07001230 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -07001231 """
1232 if not kwargs:
1233 return _PrintDocStringAsHTML(self.controlfiles)
1234
1235 if 'build' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001236 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001237 'Error: build= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001238
1239 if 'suite_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001240 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Dan Shia1cd6522016-04-18 16:07:21 -07001241 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001242
1243 control_file_list = [
1244 line.rstrip() for line in common_util.GetControlFileListForSuite(
1245 updater.static_dir, kwargs['build'],
1246 kwargs['suite_name']).splitlines()]
1247
Dan Shia1cd6522016-04-18 16:07:21 -07001248 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -07001249 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -07001250 control_file_content_dict[control_path] = (common_util.GetControlFile(
xixuan7efd0002016-04-14 15:34:01 -07001251 updater.static_dir, kwargs['build'], control_path))
1252
Dan Shia1cd6522016-04-18 16:07:21 -07001253 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -07001254
1255 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001256 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -05001257 """Return a control file or a list of all known control files.
1258
1259 Example URL:
1260 To List all control files:
beepsbd337242013-07-09 22:44:06 -07001261 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
1262 To List all control files for, say, the bvt suite:
1263 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -05001264 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001265 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 -05001266
1267 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001268 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -05001269 control_path: If you want the contents of a control file set this
1270 to the path. E.g. client/site_tests/sleeptest/control
1271 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -07001272 suite_name: If control_path is not specified but a suite_name is
1273 specified, list the control files belonging to that suite instead of
1274 all control files. The empty string for suite_name will list all control
1275 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -08001276
Scott Zawalski4647ce62012-01-03 17:17:28 -05001277 Returns:
1278 Contents of a control file if control_path is provided.
1279 A list of control files if no control_path is provided.
1280 """
Don Garrettf84631a2014-01-07 18:21:26 -08001281 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -05001282 return _PrintDocStringAsHTML(self.controlfiles)
1283
Don Garrettf84631a2014-01-07 18:21:26 -08001284 if 'build' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001285 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001286 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -05001287
Don Garrettf84631a2014-01-07 18:21:26 -08001288 if 'control_path' not in kwargs:
1289 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -07001290 return common_util.GetControlFileListForSuite(
Don Garrettf84631a2014-01-07 18:21:26 -08001291 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -07001292 else:
1293 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-07 18:21:26 -08001294 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -05001295 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001296 return common_util.GetControlFile(
Don Garrettf84631a2014-01-07 18:21:26 -08001297 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -08001298
1299 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -07001300 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001301 """Translates an xBuddy path to a real path to artifact if it exists.
1302
1303 Args:
Simran Basi99e63c02014-05-20 10:39:52 -07001304 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
1305 Local searches the devserver's static directory. Remote searches a
1306 Google Storage image archive.
1307
1308 Kwargs:
1309 image_dir: Google Storage image archive to search in if requesting a
1310 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001311
1312 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -07001313 String in the format of build_id/artifact as stored on the local server
1314 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001315 """
Simran Basi99e63c02014-05-20 10:39:52 -07001316 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -07001317 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001318 response = os.path.join(build_id, filename)
1319 _Log('Path translation requested, returning: %s', response)
1320 return response
1321
1322 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -07001323 def xbuddy(self, *args, **kwargs):
1324 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -07001325
1326 Args:
joycheneaf4cfc2013-07-02 08:38:57 -07001327 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -07001328 components of the path. The path can be understood as
1329 "{local|remote}/build_id/artifact" where build_id is composed of
1330 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -07001331
joychen121fc9b2013-08-02 14:30:30 -07001332 The first path element is optional, and can be "remote" or "local"
1333 If local (the default), devserver will not attempt to access Google
1334 Storage, and will only search the static directory for the files.
1335 If remote, devserver will try to obtain the artifact off GS if it's
1336 not found locally.
1337 The board is the familiar board name, optionally suffixed.
1338 The version can be the google storage version number, and may also be
1339 any of a number of xBuddy defined version aliases that will be
1340 translated into the latest built image that fits the description.
1341 Defaults to latest.
1342 The artifact is one of a number of image or artifact aliases used by
1343 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -07001344
1345 Kwargs:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001346 for_update: {true|false}
Amin Hassanie9ffb862019-09-25 17:10:40 -07001347 if true, prepares the update payloads for the image,
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001348 and returns the update uri to pass to the
1349 update_engine_client.
joychen3cb228e2013-06-12 12:13:13 -07001350 return_dir: {true|false}
1351 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001352 relative_path: {true|false}
1353 if set to true, returns the relative path to the payload
1354 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -07001355 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -07001356 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -07001357 or
joycheneaf4cfc2013-07-02 08:38:57 -07001358 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -07001359
1360 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001361 If |for_update|, returns a redirect to the image or update file
1362 on the devserver. E.g.,
1363 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
1364 chromium-test-image.bin
1365 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1366 http://host:port/static/x86-generic-release/R26-4000.0.0/
1367 If |relative_path| is true, return a relative path the folder where the
1368 payloads are. E.g.,
1369 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -07001370 """
Chris Sosa75490802013-09-30 17:21:45 -07001371 boolean_string = kwargs.get('for_update')
1372 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001373 boolean_string = kwargs.get('return_dir')
1374 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1375 boolean_string = kwargs.get('relative_path')
1376 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001377
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001378 if return_dir and relative_path:
Chris Sosa4b951602014-04-09 20:26:07 -07001379 raise common_util.DevServerHTTPError(
Amin Hassanid4e35392019-10-03 11:02:44 -07001380 http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001381 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001382
1383 # For updates, we optimize downloading of test images.
1384 file_name = None
1385 build_id = None
1386 if for_update:
1387 try:
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001388 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-09-30 17:21:45 -07001389 except build_artifact.ArtifactDownloadError:
1390 build_id = None
1391
1392 if not build_id:
1393 build_id, file_name = self._xbuddy.Get(args)
1394
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001395 if for_update:
Amin Hassanie9ffb862019-09-25 17:10:40 -07001396 _Log('Payloads requested.')
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001397 # Forces payload to be in cache and symlinked into build_id dir.
Amin Hassanie9ffb862019-09-25 17:10:40 -07001398 updater.GetUpdateForLabel(build_id)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001399
1400 response = None
1401 if return_dir:
1402 response = os.path.join(cherrypy.request.base, 'static', build_id)
1403 _Log('Directory requested, returning: %s', response)
1404 elif relative_path:
1405 response = build_id
1406 _Log('Relative path requested, returning: %s', response)
1407 elif for_update:
1408 response = os.path.join(cherrypy.request.base, 'update', build_id)
1409 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001410 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001411 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001412 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001413 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001414 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001415
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001416 return response
1417
joychen3cb228e2013-06-12 12:13:13 -07001418 @cherrypy.expose
1419 def xbuddy_list(self):
1420 """Lists the currently available images & time since last access.
1421
Gilad Arnold452fd272014-02-04 11:09:28 -08001422 Returns:
1423 A string representation of a list of tuples [(build_id, time since last
1424 access),...]
joychen3cb228e2013-06-12 12:13:13 -07001425 """
1426 return self._xbuddy.List()
1427
1428 @cherrypy.expose
1429 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001430 """Returns the number of images cached by xBuddy."""
joychen3cb228e2013-06-12 12:13:13 -07001431 return self._xbuddy.Capacity()
1432
1433 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001434 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001435 """Presents a welcome message and documentation links."""
Congbin Guo6bc32182019-08-20 17:54:30 -07001436 html_template = (
1437 'Welcome to the Dev Server!<br>\n'
1438 '<br>\n'
1439 'Here are the available methods, click for documentation:<br>\n'
1440 '<br>\n'
1441 '%s')
1442
1443 exposed_methods = []
1444 for app in cherrypy.tree.apps.values():
1445 exposed_methods += _FindExposedMethods(
1446 app.root, app.script_name.lstrip('/'),
1447 unlisted=self._UNLISTED_METHODS)
1448
1449 return html_template % '<br>\n'.join(
1450 ['<a href=doc/%s>%s</a>' % (name, name)
1451 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001452
1453 @cherrypy.expose
1454 def doc(self, *args):
1455 """Shows the documentation for available methods / URLs.
1456
Amin Hassani08e42d22019-06-03 00:31:30 -07001457 Examples:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001458 http://myhost/doc/update
1459 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001460 name = '/'.join(args)
Congbin Guo6bc32182019-08-20 17:54:30 -07001461 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001462 if not method:
Amin Hassanid4e35392019-10-03 11:02:44 -07001463 raise DevServerError("No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001464 if not method.__doc__:
Amin Hassanid4e35392019-10-03 11:02:44 -07001465 raise DevServerError("No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001466 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001467
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07001468 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001469 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001470 """Handles an update check from a Chrome OS client.
1471
1472 The HTTP request should contain the standard Omaha-style XML blob. The URL
1473 line may contain an additional intermediate path to the update payload.
1474
joychen121fc9b2013-08-02 14:30:30 -07001475 This request can be handled in one of 4 ways, depending on the devsever
1476 settings and intermediate path.
joychenb0dfe552013-07-30 10:02:06 -07001477
Amin Hassanie9ffb862019-09-25 17:10:40 -07001478 1. No intermediate path. DEPRECATED
joychen121fc9b2013-08-02 14:30:30 -07001479
1480 2. Path explicitly invokes XBuddy
1481 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1482 with 'xbuddy'. This path is then used to acquire an image binary for the
1483 devserver to generate an update payload from. Devserver then serves this
1484 payload.
1485
1486 3. Path is left for the devserver to interpret.
1487 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1488 to generate a payload from the test image in that directory and serve it.
1489
joychen121fc9b2013-08-02 14:30:30 -07001490 Examples:
joychen121fc9b2013-08-02 14:30:30 -07001491 2. Explicitly invoke xbuddy
1492 update_engine_client --omaha_url=
1493 http://myhost/update/xbuddy/remote/board/version/dev
1494 This would go to GS to download the dev image for the board, from which
1495 the devserver would generate a payload to serve.
1496
1497 3. Give a path for devserver to interpret
1498 update_engine_client --omaha_url=http://myhost/update/some/random/path
1499 This would attempt, in order to:
1500 a) Generate an update from a test image binary if found in
1501 static_dir/some/random/path.
1502 b) Serve an update payload found in static_dir/some/random/path.
1503 c) Hope that some/random/path takes the form "board/version" and
1504 and attempt to download an update payload for that board/version
1505 from GS.
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001506 """
joychen121fc9b2013-08-02 14:30:30 -07001507 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -08001508 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -07001509 data = cherrypy.request.rfile.read(body_length)
Chris Sosa7c931362010-10-11 19:49:01 -07001510
joychen121fc9b2013-08-02 14:30:30 -07001511 return updater.HandleUpdatePing(data, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001512
Dan Shif5ce2de2013-04-25 16:06:32 -07001513
Chris Sosadbc20082012-12-10 13:39:11 -08001514def _CleanCache(cache_dir, wipe):
1515 """Wipes any excess cached items in the cache_dir.
1516
1517 Args:
1518 cache_dir: the directory we are wiping from.
1519 wipe: If True, wipe all the contents -- not just the excess.
1520 """
1521 if wipe:
1522 # Clear the cache and exit on error.
1523 cmd = 'rm -rf %s/*' % cache_dir
1524 if os.system(cmd) != 0:
1525 _Log('Failed to clear the cache with %s' % cmd)
1526 sys.exit(1)
1527 else:
1528 # Clear all but the last N cached updates
1529 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1530 (cache_dir, CACHED_ENTRIES))
1531 if os.system(cmd) != 0:
1532 _Log('Failed to clean up old delta cache files with %s' % cmd)
1533 sys.exit(1)
1534
1535
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001536def _AddTestingOptions(parser):
1537 group = optparse.OptionGroup(
1538 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1539 'developers writing integration tests utilizing the devserver. They are '
1540 'not intended to be really used outside the scope of someone '
1541 'knowledgable about the test.')
1542 group.add_option('--exit',
1543 action='store_true',
Amin Hassanie9ffb862019-09-25 17:10:40 -07001544 help='do not start the server (yet clear cache)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001545 group.add_option('--host_log',
1546 action='store_true', default=False,
1547 help='record history of host update events (/api/hostlog)')
1548 group.add_option('--max_updates',
Gabe Black3b567202015-09-23 14:07:59 -07001549 metavar='NUM', default=-1, type='int',
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001550 help='maximum number of update checks handled positively '
1551 '(default: unlimited)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001552 group.add_option('--proxy_port',
1553 metavar='PORT', default=None, type='int',
1554 help='port to have the client connect to -- basically the '
1555 'devserver lies to the update to tell it to get the payload '
1556 'from a different port that will proxy the request back to '
1557 'the devserver. The proxy must be managed outside the '
1558 'devserver.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001559 parser.add_option_group(group)
1560
1561
1562def _AddUpdateOptions(parser):
1563 group = optparse.OptionGroup(
1564 parser, 'Autoupdate Options', 'These options can be used to change '
Amin Hassanie9ffb862019-09-25 17:10:40 -07001565 'how the devserver serve update payloads. Please '
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001566 'note that all of these option affect how a payload is generated and so '
1567 'do not work in archive-only mode.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001568 group.add_option('--critical_update',
1569 action='store_true', default=False,
1570 help='Present update payload as critical')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001571 group.add_option('--payload',
1572 metavar='PATH',
1573 help='use the update payload from specified directory '
1574 '(update.gz).')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001575 parser.add_option_group(group)
1576
1577
1578def _AddProductionOptions(parser):
1579 group = optparse.OptionGroup(
1580 parser, 'Advanced Server Options', 'These options can be used to changed '
1581 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001582 group.add_option('--clear_cache',
1583 action='store_true', default=False,
1584 help='At startup, removes all cached entries from the'
Amin Hassanid4e35392019-10-03 11:02:44 -07001585 "devserver's cache.")
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001586 group.add_option('--logfile',
1587 metavar='PATH',
1588 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001589 group.add_option('--pidfile',
1590 metavar='PATH',
1591 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001592 group.add_option('--portfile',
1593 metavar='PATH',
1594 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001595 group.add_option('--production',
1596 action='store_true', default=False,
1597 help='have the devserver use production values when '
1598 'starting up. This includes using more threads and '
1599 'performing less logging.')
1600 parser.add_option_group(group)
1601
1602
Paul Hobbsef4e0702016-06-27 17:01:42 -07001603def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001604 """Create a LogHandler instance used to log all messages."""
1605 hdlr_cls = handlers.TimedRotatingFileHandler
1606 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001607 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001608 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001609 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001610 return hdlr
1611
1612
Chris Sosacde6bf42012-05-31 18:36:39 -07001613def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001614 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001615 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001616
1617 # get directory that the devserver is run from
1618 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001619 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001620 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001621 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001622 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001623 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001624 parser.add_option('--port',
1625 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001626 help=('port for the dev server to use; if zero, binds to '
1627 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001628 parser.add_option('-t', '--test_image',
1629 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001630 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001631 parser.add_option('-x', '--xbuddy_manage_builds',
1632 action='store_true',
1633 default=False,
1634 help='If set, allow xbuddy to manage images in'
1635 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001636 parser.add_option('-a', '--android_build_credential',
1637 default=None,
1638 help='Path to a json file which contains the credential '
1639 'needed to access Android builds.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001640 _AddProductionOptions(parser)
1641 _AddUpdateOptions(parser)
1642 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001643 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001644
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001645 # Handle options that must be set globally in cherrypy. Do this
1646 # work up front, because calls to _Log() below depend on this
1647 # initialization.
1648 if options.production:
1649 cherrypy.config.update({'environment': 'production'})
1650 if not options.logfile:
1651 cherrypy.config.update({'log.screen': True})
1652 else:
1653 cherrypy.config.update({'log.error_file': '',
1654 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001655 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001656 # Pylint can't seem to process these two calls properly
1657 # pylint: disable=E1101
1658 cherrypy.log.access_log.addHandler(hdlr)
1659 cherrypy.log.error_log.addHandler(hdlr)
1660 # pylint: enable=E1101
1661
joychened64b222013-06-21 16:39:34 -07001662 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001663 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001664
joychened64b222013-06-21 16:39:34 -07001665 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001666 # If our devserver is only supposed to serve payloads, we shouldn't be
1667 # mucking with the cache at all. If the devserver hadn't previously
1668 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001669 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001670 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001671 else:
1672 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001673
Chris Sosadbc20082012-12-10 13:39:11 -08001674 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001675 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001676
Amin Hassanie9ffb862019-09-25 17:10:40 -07001677 _xbuddy = xbuddy.XBuddy(manage_builds=options.xbuddy_manage_builds,
joychen121fc9b2013-08-02 14:30:30 -07001678 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001679 if options.clear_cache and options.xbuddy_manage_builds:
1680 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001681
Chris Sosa6a3697f2013-01-29 16:44:43 -08001682 # We allow global use here to share with cherrypy classes.
1683 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001684 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001685 updater = autoupdate.Autoupdate(
joychen121fc9b2013-08-02 14:30:30 -07001686 _xbuddy,
joychened64b222013-06-21 16:39:34 -07001687 static_dir=options.static_dir,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001688 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001689 proxy_port=options.proxy_port,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001690 critical_update=options.critical_update,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001691 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001692 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001693 )
Chris Sosa7c931362010-10-11 19:49:01 -07001694
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001695 if options.exit:
1696 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001697
joychen3cb228e2013-06-12 12:13:13 -07001698 dev_server = DevServerRoot(_xbuddy)
Congbin Guo3afae6c2019-08-13 16:29:42 -07001699 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 12:13:13 -07001700
Gilad Arnold11fbef42014-02-10 11:04:13 -08001701 # Patch CherryPy to support binding to any available port (--port=0).
1702 cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy)
1703
Chris Sosa855b8932013-08-21 13:24:55 -07001704 if options.pidfile:
1705 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1706
Gilad Arnold11fbef42014-02-10 11:04:13 -08001707 if options.portfile:
1708 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1709
Dan Shiafd5c6c2016-01-07 10:27:03 -08001710 if (options.android_build_credential and
1711 os.path.exists(options.android_build_credential)):
1712 try:
1713 with open(options.android_build_credential) as f:
1714 android_build.BuildAccessor.credential_info = json.load(f)
1715 except ValueError as e:
1716 _Log('Failed to load the android build credential: %s. Error: %s.' %
1717 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 16:29:42 -07001718
1719 cherrypy.tree.mount(health_checker_app, '/check_health',
1720 config=health_checker.get_config())
joychen3cb228e2013-06-12 12:13:13 -07001721 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001722
1723
1724if __name__ == '__main__':
1725 main()