blob: 547a12a2adec7a7237b64e3498a6ddd46d6d16b4 [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:
554 last_event_type (int): last update event type received
555 last_event_status (int): last update event status received
556 last_known_version (string): last known version reported in update ping
557 timestamp: The timestamp the event was received.
558 See the OmahaEvent class in update_engine/omaha_request_action.h for
559 event type and status code definitions. If the ip does not exist an empty
560 string is returned.
Gilad Arnold1b908392012-10-05 11:36:27 -0700561
562 Example URL:
563 http://myhost/api/hostlog?ip=192.168.1.5
564 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800565 return updater.HandleHostLogPing(ip)
566
567 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800568 def fileinfo(self, *args):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700569 """Returns information about a given staged file.
570
571 Args:
Don Garrettf84631a2014-01-07 18:21:26 -0800572 args: path to the file inside the server's static staging directory
573
Gilad Arnold55a2a372012-10-02 09:46:32 -0700574 Returns:
575 A JSON encoded dictionary with information about the said file, which may
576 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700577 size (int): the file size in bytes
Gilad Arnold1b908392012-10-05 11:36:27 -0700578 sha256 (string): a base64 encoded SHA256 hash
579
580 Example URL:
581 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700582 """
Don Garrettf84631a2014-01-07 18:21:26 -0800583 file_path = os.path.join(updater.static_dir, *args)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700584 if not os.path.exists(file_path):
Amin Hassanid4e35392019-10-03 11:02:44 -0700585 raise DevServerError('file not found: %s' % file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700586 try:
587 file_size = os.path.getsize(file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700588 file_sha256 = common_util.GetFileSha256(file_path)
589 except os.error, e:
Amin Hassanid4e35392019-10-03 11:02:44 -0700590 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -0700591 'failed to get info for file %s: %s' % (file_path, e))
Gilad Arnolde74b3812013-04-22 11:27:38 -0700592
Gilad Arnolde74b3812013-04-22 11:27:38 -0700593 return json.dumps({
594 autoupdate.Autoupdate.SIZE_ATTR: file_size,
Gilad Arnolde74b3812013-04-22 11:27:38 -0700595 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
Gilad Arnolde74b3812013-04-22 11:27:38 -0700596 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700597
Chris Sosa76e44b92013-01-31 12:11:38 -0800598
David Rochberg7c79a812011-01-19 14:24:45 -0500599class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700600 """The Root Class for the Dev Server.
601
602 CherryPy works as follows:
603 For each method in this class, cherrpy interprets root/path
604 as a call to an instance of DevServerRoot->method_name. For example,
605 a call to http://myhost/build will call build. CherryPy automatically
606 parses http args and places them as keyword arguments in each method.
607 For paths http://myhost/update/dir1/dir2, you can use *args so that
608 cherrypy uses the update method and puts the extra paths in args.
609 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700610 # Method names that should not be listed on the index page.
611 _UNLISTED_METHODS = ['index', 'doc']
612
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700613 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700614
Dan Shi59ae7092013-06-04 14:37:27 -0700615 # Number of threads that devserver is staging images.
616 _staging_thread_count = 0
617 # Lock used to lock increasing/decreasing count.
618 _staging_thread_count_lock = threading.Lock()
619
joychen3cb228e2013-06-12 12:13:13 -0700620 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700621 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800622 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700623 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500624
Congbin Guo3afae6c2019-08-13 16:29:42 -0700625 @property
626 def staging_thread_count(self):
627 """Get the staging thread count."""
628 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700629
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700630 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500631 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700632 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700633 import builder
634 if self._builder is None:
635 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500636 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700637
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700638 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700639 def is_staged(self, **kwargs):
640 """Check if artifacts have been downloaded.
641
Congbin Guo3afae6c2019-08-13 16:29:42 -0700642 Examples:
643 To check if autotest and test_suites are staged:
644 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
645 artifacts=autotest,test_suites
646
Amin Hassani08e42d22019-06-03 00:31:30 -0700647 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700648 async: True to return without waiting for download to complete.
649 artifacts: Comma separated list of named artifacts to download.
650 These are defined in artifact_info and have their implementation
651 in build_artifact.py.
652 files: Comma separated list of file artifacts to stage. These
653 will be available as is in the corresponding static directory with no
654 custom post-processing.
655
Congbin Guo3afae6c2019-08-13 16:29:42 -0700656 Returns:
657 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700658 """
Gabe Black3b567202015-09-23 14:07:59 -0700659 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700660 response = str(dl.IsStaged(factory))
661 _Log('Responding to is_staged %s request with %r', kwargs, response)
662 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700663
Chris Sosa76e44b92013-01-31 12:11:38 -0800664 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800665 def list_image_dir(self, **kwargs):
666 """Take an archive url and list the contents in its staged directory.
667
Amin Hassani08e42d22019-06-03 00:31:30 -0700668 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800669 To list the contents of where this devserver should have staged
670 gs://image-archive/<board>-release/<build> call:
671 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
672
Congbin Guo3afae6c2019-08-13 16:29:42 -0700673 Args:
674 archive_url: Google Storage URL for the build.
675
Prashanth Ba06d2d22014-03-07 15:35:19 -0800676 Returns:
677 A string with information about the contents of the image directory.
678 """
Gabe Black3b567202015-09-23 14:07:59 -0700679 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800680 try:
Gabe Black3b567202015-09-23 14:07:59 -0700681 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800682 except build_artifact.ArtifactDownloadError as e:
683 return 'Cannot list the contents of staged artifacts. %s' % e
684 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700685 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800686 return image_dir_contents
687
688 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800689 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700690 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800691
Gabe Black3b567202015-09-23 14:07:59 -0700692 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700693 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700694 on the devserver. A call to this will attempt to cache non-specified
695 artifacts in the background for the given from the given URL following
696 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800697 artifacts is explicitly defined in the build_artifact module.
698
699 These artifacts will then be available from the static/ sub-directory of
700 the devserver.
701
Amin Hassani08e42d22019-06-03 00:31:30 -0700702 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800703 To download the autotest and test suites tarballs:
704 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
705 artifacts=autotest,test_suites
706 To download the full update payload:
707 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
708 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700709 To download just a file called blah.bin:
710 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
711 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800712
713 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700714 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800715
716 Note for this example, relative path is the archive_url stripped of its
717 basename i.e. path/ in the examples above. Specific example:
718
719 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
720
721 Will get staged to:
722
joychened64b222013-06-21 16:39:34 -0700723 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700724
725 Args:
726 archive_url: Google Storage URL for the build.
727 local_path: Local path for the build.
728 delete_source: Only meaningful with local_path. bool to indicate if the
729 source files should be deleted. This is especially useful when staging
730 a file locally in resource constrained environments as it allows us to
731 move the relevant files locally instead of copying them.
732 async: True to return without waiting for download to complete.
733 artifacts: Comma separated list of named artifacts to download.
734 These are defined in artifact_info and have their implementation
735 in build_artifact.py.
736 files: Comma separated list of files to stage. These
737 will be available as is in the corresponding static directory with no
738 custom post-processing.
739 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800740 """
Gabe Black3b567202015-09-23 14:07:59 -0700741 dl, factory = _get_downloader_and_factory(kwargs)
742
Dan Shi59ae7092013-06-04 14:37:27 -0700743 with DevServerRoot._staging_thread_count_lock:
744 DevServerRoot._staging_thread_count += 1
745 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800746 boolean_string = kwargs.get('clean')
747 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
748 if clean and os.path.exists(dl.GetBuildDir()):
749 _Log('Removing %s' % dl.GetBuildDir())
750 shutil.rmtree(dl.GetBuildDir())
Gabe Black3b567202015-09-23 14:07:59 -0700751 async = kwargs.get('async', False)
752 dl.Download(factory, async=async)
Dan Shi59ae7092013-06-04 14:37:27 -0700753 finally:
754 with DevServerRoot._staging_thread_count_lock:
755 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800756 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700757
758 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700759 def cros_au(self, **kwargs):
760 """Auto-update a CrOS DUT.
761
762 Args:
763 kwargs:
764 host_name: the hostname of the DUT to auto-update.
765 build_name: the build name for update the DUT.
766 force_update: Force an update even if the version installed is the
767 same. Default: False.
768 full_update: If True, do not run stateful update, directly force a full
769 reimage. If False, try stateful update first if the dut is already
770 installed with the same version.
771 async: Whether the auto_update function is ran in the background.
David Rileyee75de22017-11-02 10:48:15 -0700772 quick_provision: Whether the quick provision path is attempted first.
xixuan52c2fba2016-05-20 17:02:48 -0700773
774 Returns:
775 A tuple includes two elements:
776 a boolean variable represents whether the auto-update process is
777 successfully started.
778 an integer represents the background auto-update process id.
779 """
780 _check_base_args_for_auto_update(kwargs)
781
782 host_name = kwargs['host_name']
783 build_name = kwargs['build_name']
784 force_update = _parse_boolean_arg(kwargs, 'force_update')
785 full_update = _parse_boolean_arg(kwargs, 'full_update')
786 async = _parse_boolean_arg(kwargs, 'async')
xixuanac89ce82016-11-30 16:48:20 -0800787 original_build = _parse_string_arg(kwargs, 'original_build')
David Haddock90e49442017-04-07 19:14:09 -0700788 payload_filename = _parse_string_arg(kwargs, 'payload_filename')
David Haddock20559612017-06-28 22:15:08 -0700789 clobber_stateful = _parse_boolean_arg(kwargs, 'clobber_stateful')
David Rileyee75de22017-11-02 10:48:15 -0700790 quick_provision = _parse_boolean_arg(kwargs, 'quick_provision')
791
792 devserver_url = updater.GetDevserverUrl()
793 static_url = updater.GetStaticUrl()
xixuan52c2fba2016-05-20 17:02:48 -0700794
795 if async:
796 path = os.path.dirname(os.path.abspath(__file__))
797 execute_file = os.path.join(path, 'cros_update.py')
798 args = (AUTO_UPDATE_CMD % (execute_file, host_name, build_name,
799 updater.static_dir))
xixuanac89ce82016-11-30 16:48:20 -0800800
801 # The original_build's format is like: link/3428.210.0
802 # The corresponding release_archive_url's format is like:
803 # gs://chromeos-releases/stable-channel/link/3428.210.0
804 if original_build:
805 release_archive_url = _build_uri_from_build_name(original_build)
806 # First staging the stateful.tgz synchronousely.
807 self.stage(files='stateful.tgz', async=False,
808 archive_url=release_archive_url)
809 args = ('%s --original_build %s' % (args, original_build))
810
xixuan52c2fba2016-05-20 17:02:48 -0700811 if force_update:
812 args = ('%s --force_update' % args)
813
814 if full_update:
815 args = ('%s --full_update' % args)
816
David Haddock90e49442017-04-07 19:14:09 -0700817 if payload_filename:
818 args = ('%s --payload_filename %s' % (args, payload_filename))
819
David Haddock20559612017-06-28 22:15:08 -0700820 if clobber_stateful:
821 args = ('%s --clobber_stateful' % args)
822
David Rileyee75de22017-11-02 10:48:15 -0700823 if quick_provision:
824 args = ('%s --quick_provision' % args)
825
826 if devserver_url:
827 args = ('%s --devserver_url %s' % (args, devserver_url))
828
829 if static_url:
830 args = ('%s --static_url %s' % (args, static_url))
831
xixuan2a0970a2016-08-10 12:12:44 -0700832 p = subprocess.Popen([args], shell=True, preexec_fn=os.setsid)
833 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-20 17:02:48 -0700834
835 # Pre-write status in the track_status_file before the first call of
836 # 'get_au_status' to make sure that the track_status_file exists.
xixuan2a0970a2016-08-10 12:12:44 -0700837 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700838 progress_tracker.WriteStatus('CrOS update is just started.')
839
xixuan2a0970a2016-08-10 12:12:44 -0700840 return json.dumps((True, pid))
xixuan52c2fba2016-05-20 17:02:48 -0700841 else:
842 cros_update_trigger = cros_update.CrOSUpdateTrigger(
xixuanac89ce82016-11-30 16:48:20 -0800843 host_name, build_name, updater.static_dir, force_update=force_update,
David Rileyee75de22017-11-02 10:48:15 -0700844 full_update=full_update, original_build=original_build,
845 quick_provision=quick_provision, devserver_url=devserver_url,
846 static_url=static_url)
xixuan52c2fba2016-05-20 17:02:48 -0700847 cros_update_trigger.TriggerAU()
xixuan27d50442017-08-09 10:38:25 -0700848 return json.dumps((True, -1))
xixuan52c2fba2016-05-20 17:02:48 -0700849
850 @cherrypy.expose
851 def get_au_status(self, **kwargs):
852 """Check if the auto-update task is finished.
853
854 It handles 4 cases:
855 1. If an error exists in the track_status_file, delete the track file and
856 raise it.
857 2. If cros-update process is finished, delete the file and return the
858 success result.
859 3. If the process is not running, delete the track file and raise an error
860 about 'the process is terminated due to unknown reason'.
861 4. If the track_status_file does not exist, kill the process if it exists,
862 and raise the IOError.
863
864 Args:
865 kwargs:
866 host_name: the hostname of the DUT to auto-update.
867 pid: the background process id of cros-update.
868
869 Returns:
xixuan28d99072016-10-06 12:24:16 -0700870 A dict with three elements:
xixuan52c2fba2016-05-20 17:02:48 -0700871 a boolean variable represents whether the auto-update process is
872 finished.
873 a string represents the current auto-update process status.
874 For example, 'Transfer Devserver/Stateful Update Package'.
xixuan28d99072016-10-06 12:24:16 -0700875 a detailed error message paragraph if there exists an Auto-Update
876 error, in which the last line shows the main exception. Empty
877 string otherwise.
xixuan52c2fba2016-05-20 17:02:48 -0700878 """
879 if 'host_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700880 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700881 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700882
883 if 'pid' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700884 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700885 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700886
887 host_name = kwargs['host_name']
888 pid = kwargs['pid']
889 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
890
xixuan28d99072016-10-06 12:24:16 -0700891 result_dict = {'finished': False, 'status': '', 'detailed_error_msg': ''}
xixuan52c2fba2016-05-20 17:02:48 -0700892 try:
893 result = progress_tracker.ReadStatus()
894 if result.startswith(cros_update_progress.ERROR_TAG):
xixuan28d99072016-10-06 12:24:16 -0700895 result_dict['detailed_error_msg'] = result[len(
896 cros_update_progress.ERROR_TAG):]
xixuan28681fd2016-11-23 11:13:56 -0800897 elif result == cros_update_progress.FINISHED:
xixuan28d99072016-10-06 12:24:16 -0700898 result_dict['finished'] = True
899 result_dict['status'] = result
xixuan28681fd2016-11-23 11:13:56 -0800900 elif not cros_update_progress.IsProcessAlive(pid):
xixuan28d99072016-10-06 12:24:16 -0700901 result_dict['detailed_error_msg'] = (
902 'Cros_update process terminated midway due to unknown reason. '
903 'Last update status was %s' % result)
xixuan28681fd2016-11-23 11:13:56 -0800904 else:
905 result_dict['status'] = result
906 except IOError as e:
907 if pid and cros_update_progress.IsProcessAlive(pid):
xixuan2a0970a2016-08-10 12:12:44 -0700908 os.killpg(int(pid), signal.SIGKILL)
xixuan52c2fba2016-05-20 17:02:48 -0700909
xixuan28681fd2016-11-23 11:13:56 -0800910 result_dict['detailed_error_msg'] = str(e)
911
912 return json.dumps(result_dict)
xixuan52c2fba2016-05-20 17:02:48 -0700913
914 @cherrypy.expose
David Riley6d5fca02017-10-31 10:35:47 -0700915 def post_au_status(self, status, **kwargs):
916 """Updates the status of an auto-update task.
917
918 Callers will need to POST to this URL with a body of MIME-type
919 "multipart/form-data".
920 The body should include a single argument, 'status', containing the
921 AU status to record.
922
923 Args:
924 status: The updated status.
925 kwargs:
926 host_name: the hostname of the DUT to auto-update.
927 pid: the background process id of cros-update.
928 """
929 if 'host_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700930 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700931 KEY_ERROR_MSG % 'host_name')
David Riley6d5fca02017-10-31 10:35:47 -0700932
933 if 'pid' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700934 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700935 KEY_ERROR_MSG % 'pid')
David Riley6d5fca02017-10-31 10:35:47 -0700936
937 host_name = kwargs['host_name']
938 pid = kwargs['pid']
David Riley3cea2582017-11-24 22:03:01 -0800939 status = status.rstrip()
940 _Log('Recording status for %s (%s): %s' % (host_name, pid, status))
David Riley6d5fca02017-10-31 10:35:47 -0700941 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
942
David Riley3cea2582017-11-24 22:03:01 -0800943 progress_tracker.WriteStatus(status)
David Riley6d5fca02017-10-31 10:35:47 -0700944
945 return 'True'
946
947 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700948 def handler_cleanup(self, **kwargs):
xixuan3bc974e2016-10-18 17:21:43 -0700949 """Clean track status log and temp directory for CrOS auto-update process.
xixuan52c2fba2016-05-20 17:02:48 -0700950
951 Args:
952 kwargs:
953 host_name: the hostname of the DUT to auto-update.
954 pid: the background process id of cros-update.
955 """
956 if 'host_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700957 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700958 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700959
960 if 'pid' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700961 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700962 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700963
964 host_name = kwargs['host_name']
965 pid = kwargs['pid']
966 cros_update_progress.DelTrackStatusFile(host_name, pid)
xixuan3bc974e2016-10-18 17:21:43 -0700967 cros_update_progress.DelAUTempDirectory(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700968
969 @cherrypy.expose
970 def kill_au_proc(self, **kwargs):
971 """Kill CrOS auto-update process using given process id.
972
973 Args:
974 kwargs:
975 host_name: Kill all the CrOS auto-update process of this host.
976
977 Returns:
978 True if all processes are killed properly.
979 """
980 if 'host_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -0700981 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -0700982 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700983
xixuan447ad9d2017-02-28 14:46:20 -0800984 cur_pid = kwargs.get('pid')
985
xixuan52c2fba2016-05-20 17:02:48 -0700986 host_name = kwargs['host_name']
xixuan3bc974e2016-10-18 17:21:43 -0700987 track_log_list = cros_update_progress.GetAllTrackStatusFileByHostName(
988 host_name)
xixuan52c2fba2016-05-20 17:02:48 -0700989 for log in track_log_list:
990 # The track log's full path is: path/host_name_pid.log
991 # Use splitext to remove file extension, then parse pid from the
992 # filename.
Congbin Guo3afae6c2019-08-13 16:29:42 -0700993 pid = os.path.splitext(os.path.basename(log))[0][len(host_name) + 1:]
xixuan447ad9d2017-02-28 14:46:20 -0800994 _clear_process(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700995
xixuan447ad9d2017-02-28 14:46:20 -0800996 if cur_pid:
997 _clear_process(host_name, cur_pid)
xixuan52c2fba2016-05-20 17:02:48 -0700998
999 return 'True'
1000
1001 @cherrypy.expose
1002 def collect_cros_au_log(self, **kwargs):
1003 """Collect CrOS auto-update log.
1004
1005 Args:
1006 kwargs:
1007 host_name: the hostname of the DUT to auto-update.
1008 pid: the background process id of cros-update.
1009
1010 Returns:
David Haddock9f459632017-05-11 14:45:46 -07001011 A dictionary containing the execute log file and any hostlog files.
xixuan52c2fba2016-05-20 17:02:48 -07001012 """
1013 if 'host_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001014 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001015 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001016
1017 if 'pid' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001018 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001019 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -07001020
1021 host_name = kwargs['host_name']
1022 pid = kwargs['pid']
xixuan3bc974e2016-10-18 17:21:43 -07001023
1024 # Fetch the execute log recorded by cros_update_progress.
xixuan1bbfaba2016-10-13 17:53:22 -07001025 au_log = cros_update_progress.ReadExecuteLogFile(host_name, pid)
1026 cros_update_progress.DelExecuteLogFile(host_name, pid)
David Haddock9f459632017-05-11 14:45:46 -07001027 # Fetch the cros_au host_logs if they exist
1028 au_hostlogs = cros_update_progress.ReadAUHostLogFiles(host_name, pid)
1029 return json.dumps({'cros_au_log': au_log, 'host_logs': au_hostlogs})
xixuan1bbfaba2016-10-13 17:53:22 -07001030
xixuan52c2fba2016-05-20 17:02:48 -07001031 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -08001032 def locate_file(self, **kwargs):
1033 """Get the path to the given file name.
1034
1035 This method looks up the given file name inside specified build artifacts.
1036 One use case is to help caller to locate an apk file inside a build
1037 artifact. The location of the apk file could be different based on the
1038 branch and target.
1039
1040 Args:
1041 file_name: Name of the file to look for.
1042 artifacts: A list of artifact names to search for the file.
1043
1044 Returns:
1045 Path to the file with the given name. It's relative to the folder for the
1046 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -08001047 """
1048 dl, _ = _get_downloader_and_factory(kwargs)
1049 try:
Joe Brennan1691f8e2017-03-15 15:53:36 -07001050 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 15:38:38 -08001051 artifacts = kwargs['artifacts']
1052 except KeyError:
Amin Hassanid4e35392019-10-03 11:02:44 -07001053 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001054 '`file_name` and `artifacts` are required to search '
1055 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 15:38:38 -08001056 build_path = dl.GetBuildDir()
1057 for artifact in artifacts:
1058 # Get the unzipped folder of the artifact. If it's not defined in
1059 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
1060 # directory directly.
1061 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
1062 artifact_path = os.path.join(build_path, folder)
1063 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 15:53:36 -07001064 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 15:38:38 -08001065 return os.path.relpath(os.path.join(root, file_name), build_path)
Amin Hassanid4e35392019-10-03 11:02:44 -07001066 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001067 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 15:38:38 -08001068
1069 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -08001070 def setup_telemetry(self, **kwargs):
1071 """Extracts and sets up telemetry
1072
1073 This method goes through the telemetry deps packages, and stages them on
1074 the devserver to be used by the drones and the telemetry tests.
1075
1076 Args:
1077 archive_url: Google Storage URL for the build.
1078
1079 Returns:
1080 Path to the source folder for the telemetry codebase once it is staged.
1081 """
Gabe Black3b567202015-09-23 14:07:59 -07001082 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 13:39:18 -08001083
Gabe Black3b567202015-09-23 14:07:59 -07001084 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -08001085 deps_path = os.path.join(build_path, 'autotest/packages')
1086 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
1087 src_folder = os.path.join(telemetry_path, 'src')
1088
1089 with self._telemetry_lock_dict.lock(telemetry_path):
1090 if os.path.exists(src_folder):
1091 # Telemetry is already fully stage return
1092 return src_folder
1093
1094 common_util.MkDirP(telemetry_path)
1095
1096 # Copy over the required deps tar balls to the telemetry directory.
1097 for dep in TELEMETRY_DEPS:
1098 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -07001099 if not os.path.exists(dep_path):
1100 # This dep does not exist (could be new), do not extract it.
1101 continue
Simran Basi4baad082013-02-14 13:39:18 -08001102 try:
1103 common_util.ExtractTarball(dep_path, telemetry_path)
1104 except common_util.CommonUtilError as e:
1105 shutil.rmtree(telemetry_path)
Amin Hassanid4e35392019-10-03 11:02:44 -07001106 raise DevServerError(str(e))
Simran Basi4baad082013-02-14 13:39:18 -08001107
1108 # By default all the tarballs extract to test_src but some parts of
1109 # the telemetry code specifically hardcoded to exist inside of 'src'.
1110 test_src = os.path.join(telemetry_path, 'test_src')
1111 try:
1112 shutil.move(test_src, src_folder)
1113 except shutil.Error:
1114 # This can occur if src_folder already exists. Remove and retry move.
1115 shutil.rmtree(src_folder)
Amin Hassanid4e35392019-10-03 11:02:44 -07001116 raise DevServerError(
Gabe Black3b567202015-09-23 14:07:59 -07001117 'Failure in telemetry setup for build %s. Appears that the '
1118 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -08001119
1120 return src_folder
1121
1122 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -08001123 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -07001124 """Symbolicates a minidump using pre-downloaded symbols, returns it.
1125
1126 Callers will need to POST to this URL with a body of MIME-type
1127 "multipart/form-data".
1128 The body should include a single argument, 'minidump', containing the
1129 binary-formatted minidump to symbolicate.
1130
Chris Masone816e38c2012-05-02 12:22:36 -07001131 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -08001132 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -07001133 minidump: The binary minidump file to symbolicate.
1134 """
Chris Sosa76e44b92013-01-31 12:11:38 -08001135 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -07001136 # Try debug.tar.xz first, then debug.tgz
1137 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
1138 kwargs['artifacts'] = artifact
1139 dl = _get_downloader(kwargs)
1140
1141 try:
1142 if self.stage(**kwargs) == 'Success':
1143 break
1144 except build_artifact.ArtifactDownloadError:
1145 continue
1146 else:
Amin Hassanid4e35392019-10-03 11:02:44 -07001147 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001148 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -08001149
Chris Masone816e38c2012-05-02 12:22:36 -07001150 to_return = ''
1151 with tempfile.NamedTemporaryFile() as local:
1152 while True:
1153 data = minidump.file.read(8192)
1154 if not data:
1155 break
1156 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -08001157
Chris Masone816e38c2012-05-02 12:22:36 -07001158 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -08001159
Gabe Black3b567202015-09-23 14:07:59 -07001160 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -08001161
xixuanab744382017-04-27 10:41:27 -07001162 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 12:11:38 -08001163 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 10:41:27 -07001164 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 12:11:38 -08001165 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1166
Chris Masone816e38c2012-05-02 12:22:36 -07001167 to_return, error_text = stackwalk.communicate()
1168 if stackwalk.returncode != 0:
Amin Hassanid4e35392019-10-03 11:02:44 -07001169 raise DevServerError(
Congbin Guo4132a272019-08-20 12:32:14 -07001170 "Can't generate stack trace: %s (rc=%d)" % (error_text,
1171 stackwalk.returncode))
Chris Masone816e38c2012-05-02 12:22:36 -07001172
1173 return to_return
1174
1175 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001176 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -04001177 """Return a string representing the latest build for a given target.
1178
1179 Args:
1180 target: The build target, typically a combination of the board and the
1181 type of build e.g. x86-mario-release.
1182 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
1183 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -08001184
Scott Zawalski16954532012-03-20 15:31:36 -04001185 Returns:
1186 A string representation of the latest build if one exists, i.e.
1187 R19-1993.0.0-a1-b1480.
1188 An empty string if no latest could be found.
1189 """
Don Garrettf84631a2014-01-07 18:21:26 -08001190 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -04001191 return _PrintDocStringAsHTML(self.latestbuild)
1192
Don Garrettf84631a2014-01-07 18:21:26 -08001193 if 'target' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001194 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001195 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -07001196
1197 if _is_android_build_request(kwargs):
1198 branch = kwargs.get('branch', None)
1199 target = kwargs.get('target', None)
1200 if not target or not branch:
Amin Hassanid4e35392019-10-03 11:02:44 -07001201 raise DevServerError('Both target and branch must be specified to query'
1202 ' for the latest Android build.')
Dan Shi61305df2015-10-26 16:52:35 -07001203 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
1204
Scott Zawalski16954532012-03-20 15:31:36 -04001205 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001206 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-07 18:21:26 -08001207 updater.static_dir, kwargs['target'],
1208 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -07001209 except common_util.CommonUtilError as errmsg:
Amin Hassanid4e35392019-10-03 11:02:44 -07001210 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001211 str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -04001212
1213 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -07001214 def list_suite_controls(self, **kwargs):
1215 """Return a list of contents of all known control files.
1216
1217 Example URL:
1218 To List all control files' content:
1219 http://dev-server/list_suite_controls?suite_name=bvt&
1220 build=daisy_spring-release/R29-4279.0.0
1221
1222 Args:
1223 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
1224 suite_name: List the control files belonging to that suite.
1225
1226 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -07001227 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -07001228 """
1229 if not kwargs:
1230 return _PrintDocStringAsHTML(self.controlfiles)
1231
1232 if 'build' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001233 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001234 'Error: build= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001235
1236 if 'suite_name' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001237 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Dan Shia1cd6522016-04-18 16:07:21 -07001238 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001239
1240 control_file_list = [
1241 line.rstrip() for line in common_util.GetControlFileListForSuite(
1242 updater.static_dir, kwargs['build'],
1243 kwargs['suite_name']).splitlines()]
1244
Dan Shia1cd6522016-04-18 16:07:21 -07001245 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -07001246 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -07001247 control_file_content_dict[control_path] = (common_util.GetControlFile(
xixuan7efd0002016-04-14 15:34:01 -07001248 updater.static_dir, kwargs['build'], control_path))
1249
Dan Shia1cd6522016-04-18 16:07:21 -07001250 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -07001251
1252 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001253 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -05001254 """Return a control file or a list of all known control files.
1255
1256 Example URL:
1257 To List all control files:
beepsbd337242013-07-09 22:44:06 -07001258 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
1259 To List all control files for, say, the bvt suite:
1260 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -05001261 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001262 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 -05001263
1264 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001265 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -05001266 control_path: If you want the contents of a control file set this
1267 to the path. E.g. client/site_tests/sleeptest/control
1268 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -07001269 suite_name: If control_path is not specified but a suite_name is
1270 specified, list the control files belonging to that suite instead of
1271 all control files. The empty string for suite_name will list all control
1272 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -08001273
Scott Zawalski4647ce62012-01-03 17:17:28 -05001274 Returns:
1275 Contents of a control file if control_path is provided.
1276 A list of control files if no control_path is provided.
1277 """
Don Garrettf84631a2014-01-07 18:21:26 -08001278 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -05001279 return _PrintDocStringAsHTML(self.controlfiles)
1280
Don Garrettf84631a2014-01-07 18:21:26 -08001281 if 'build' not in kwargs:
Amin Hassanid4e35392019-10-03 11:02:44 -07001282 raise common_util.DevServerHTTPError(http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001283 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -05001284
Don Garrettf84631a2014-01-07 18:21:26 -08001285 if 'control_path' not in kwargs:
1286 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -07001287 return common_util.GetControlFileListForSuite(
Don Garrettf84631a2014-01-07 18:21:26 -08001288 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -07001289 else:
1290 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-07 18:21:26 -08001291 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -05001292 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001293 return common_util.GetControlFile(
Don Garrettf84631a2014-01-07 18:21:26 -08001294 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -08001295
1296 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -07001297 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001298 """Translates an xBuddy path to a real path to artifact if it exists.
1299
1300 Args:
Simran Basi99e63c02014-05-20 10:39:52 -07001301 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
1302 Local searches the devserver's static directory. Remote searches a
1303 Google Storage image archive.
1304
1305 Kwargs:
1306 image_dir: Google Storage image archive to search in if requesting a
1307 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001308
1309 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -07001310 String in the format of build_id/artifact as stored on the local server
1311 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001312 """
Simran Basi99e63c02014-05-20 10:39:52 -07001313 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -07001314 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001315 response = os.path.join(build_id, filename)
1316 _Log('Path translation requested, returning: %s', response)
1317 return response
1318
1319 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -07001320 def xbuddy(self, *args, **kwargs):
1321 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -07001322
1323 Args:
joycheneaf4cfc2013-07-02 08:38:57 -07001324 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -07001325 components of the path. The path can be understood as
1326 "{local|remote}/build_id/artifact" where build_id is composed of
1327 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -07001328
joychen121fc9b2013-08-02 14:30:30 -07001329 The first path element is optional, and can be "remote" or "local"
1330 If local (the default), devserver will not attempt to access Google
1331 Storage, and will only search the static directory for the files.
1332 If remote, devserver will try to obtain the artifact off GS if it's
1333 not found locally.
1334 The board is the familiar board name, optionally suffixed.
1335 The version can be the google storage version number, and may also be
1336 any of a number of xBuddy defined version aliases that will be
1337 translated into the latest built image that fits the description.
1338 Defaults to latest.
1339 The artifact is one of a number of image or artifact aliases used by
1340 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -07001341
1342 Kwargs:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001343 for_update: {true|false}
Amin Hassanie9ffb862019-09-25 17:10:40 -07001344 if true, prepares the update payloads for the image,
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001345 and returns the update uri to pass to the
1346 update_engine_client.
joychen3cb228e2013-06-12 12:13:13 -07001347 return_dir: {true|false}
1348 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001349 relative_path: {true|false}
1350 if set to true, returns the relative path to the payload
1351 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -07001352 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -07001353 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -07001354 or
joycheneaf4cfc2013-07-02 08:38:57 -07001355 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -07001356
1357 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001358 If |for_update|, returns a redirect to the image or update file
1359 on the devserver. E.g.,
1360 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
1361 chromium-test-image.bin
1362 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1363 http://host:port/static/x86-generic-release/R26-4000.0.0/
1364 If |relative_path| is true, return a relative path the folder where the
1365 payloads are. E.g.,
1366 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -07001367 """
Chris Sosa75490802013-09-30 17:21:45 -07001368 boolean_string = kwargs.get('for_update')
1369 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001370 boolean_string = kwargs.get('return_dir')
1371 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1372 boolean_string = kwargs.get('relative_path')
1373 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001374
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001375 if return_dir and relative_path:
Chris Sosa4b951602014-04-09 20:26:07 -07001376 raise common_util.DevServerHTTPError(
Amin Hassanid4e35392019-10-03 11:02:44 -07001377 http_client.INTERNAL_SERVER_ERROR,
Amin Hassani08e42d22019-06-03 00:31:30 -07001378 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001379
1380 # For updates, we optimize downloading of test images.
1381 file_name = None
1382 build_id = None
1383 if for_update:
1384 try:
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001385 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-09-30 17:21:45 -07001386 except build_artifact.ArtifactDownloadError:
1387 build_id = None
1388
1389 if not build_id:
1390 build_id, file_name = self._xbuddy.Get(args)
1391
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001392 if for_update:
Amin Hassanie9ffb862019-09-25 17:10:40 -07001393 _Log('Payloads requested.')
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001394 # Forces payload to be in cache and symlinked into build_id dir.
Amin Hassanie9ffb862019-09-25 17:10:40 -07001395 updater.GetUpdateForLabel(build_id)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001396
1397 response = None
1398 if return_dir:
1399 response = os.path.join(cherrypy.request.base, 'static', build_id)
1400 _Log('Directory requested, returning: %s', response)
1401 elif relative_path:
1402 response = build_id
1403 _Log('Relative path requested, returning: %s', response)
1404 elif for_update:
1405 response = os.path.join(cherrypy.request.base, 'update', build_id)
1406 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001407 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001408 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001409 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001410 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001411 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001412
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001413 return response
1414
joychen3cb228e2013-06-12 12:13:13 -07001415 @cherrypy.expose
1416 def xbuddy_list(self):
1417 """Lists the currently available images & time since last access.
1418
Gilad Arnold452fd272014-02-04 11:09:28 -08001419 Returns:
1420 A string representation of a list of tuples [(build_id, time since last
1421 access),...]
joychen3cb228e2013-06-12 12:13:13 -07001422 """
1423 return self._xbuddy.List()
1424
1425 @cherrypy.expose
1426 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001427 """Returns the number of images cached by xBuddy."""
joychen3cb228e2013-06-12 12:13:13 -07001428 return self._xbuddy.Capacity()
1429
1430 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001431 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001432 """Presents a welcome message and documentation links."""
Congbin Guo6bc32182019-08-20 17:54:30 -07001433 html_template = (
1434 'Welcome to the Dev Server!<br>\n'
1435 '<br>\n'
1436 'Here are the available methods, click for documentation:<br>\n'
1437 '<br>\n'
1438 '%s')
1439
1440 exposed_methods = []
1441 for app in cherrypy.tree.apps.values():
1442 exposed_methods += _FindExposedMethods(
1443 app.root, app.script_name.lstrip('/'),
1444 unlisted=self._UNLISTED_METHODS)
1445
1446 return html_template % '<br>\n'.join(
1447 ['<a href=doc/%s>%s</a>' % (name, name)
1448 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001449
1450 @cherrypy.expose
1451 def doc(self, *args):
1452 """Shows the documentation for available methods / URLs.
1453
Amin Hassani08e42d22019-06-03 00:31:30 -07001454 Examples:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001455 http://myhost/doc/update
1456 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001457 name = '/'.join(args)
Congbin Guo6bc32182019-08-20 17:54:30 -07001458 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001459 if not method:
Amin Hassanid4e35392019-10-03 11:02:44 -07001460 raise DevServerError("No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001461 if not method.__doc__:
Amin Hassanid4e35392019-10-03 11:02:44 -07001462 raise DevServerError("No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001463 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001464
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07001465 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001466 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001467 """Handles an update check from a Chrome OS client.
1468
1469 The HTTP request should contain the standard Omaha-style XML blob. The URL
1470 line may contain an additional intermediate path to the update payload.
1471
joychen121fc9b2013-08-02 14:30:30 -07001472 This request can be handled in one of 4 ways, depending on the devsever
1473 settings and intermediate path.
joychenb0dfe552013-07-30 10:02:06 -07001474
Amin Hassanie9ffb862019-09-25 17:10:40 -07001475 1. No intermediate path. DEPRECATED
joychen121fc9b2013-08-02 14:30:30 -07001476
1477 2. Path explicitly invokes XBuddy
1478 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1479 with 'xbuddy'. This path is then used to acquire an image binary for the
1480 devserver to generate an update payload from. Devserver then serves this
1481 payload.
1482
1483 3. Path is left for the devserver to interpret.
1484 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1485 to generate a payload from the test image in that directory and serve it.
1486
joychen121fc9b2013-08-02 14:30:30 -07001487 Examples:
joychen121fc9b2013-08-02 14:30:30 -07001488 2. Explicitly invoke xbuddy
1489 update_engine_client --omaha_url=
1490 http://myhost/update/xbuddy/remote/board/version/dev
1491 This would go to GS to download the dev image for the board, from which
1492 the devserver would generate a payload to serve.
1493
1494 3. Give a path for devserver to interpret
1495 update_engine_client --omaha_url=http://myhost/update/some/random/path
1496 This would attempt, in order to:
1497 a) Generate an update from a test image binary if found in
1498 static_dir/some/random/path.
1499 b) Serve an update payload found in static_dir/some/random/path.
1500 c) Hope that some/random/path takes the form "board/version" and
1501 and attempt to download an update payload for that board/version
1502 from GS.
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001503 """
joychen121fc9b2013-08-02 14:30:30 -07001504 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -08001505 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -07001506 data = cherrypy.request.rfile.read(body_length)
Chris Sosa7c931362010-10-11 19:49:01 -07001507
joychen121fc9b2013-08-02 14:30:30 -07001508 return updater.HandleUpdatePing(data, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001509
Dan Shif5ce2de2013-04-25 16:06:32 -07001510
Chris Sosadbc20082012-12-10 13:39:11 -08001511def _CleanCache(cache_dir, wipe):
1512 """Wipes any excess cached items in the cache_dir.
1513
1514 Args:
1515 cache_dir: the directory we are wiping from.
1516 wipe: If True, wipe all the contents -- not just the excess.
1517 """
1518 if wipe:
1519 # Clear the cache and exit on error.
1520 cmd = 'rm -rf %s/*' % cache_dir
1521 if os.system(cmd) != 0:
1522 _Log('Failed to clear the cache with %s' % cmd)
1523 sys.exit(1)
1524 else:
1525 # Clear all but the last N cached updates
1526 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1527 (cache_dir, CACHED_ENTRIES))
1528 if os.system(cmd) != 0:
1529 _Log('Failed to clean up old delta cache files with %s' % cmd)
1530 sys.exit(1)
1531
1532
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001533def _AddTestingOptions(parser):
1534 group = optparse.OptionGroup(
1535 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1536 'developers writing integration tests utilizing the devserver. They are '
1537 'not intended to be really used outside the scope of someone '
1538 'knowledgable about the test.')
1539 group.add_option('--exit',
1540 action='store_true',
Amin Hassanie9ffb862019-09-25 17:10:40 -07001541 help='do not start the server (yet clear cache)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001542 group.add_option('--host_log',
1543 action='store_true', default=False,
1544 help='record history of host update events (/api/hostlog)')
1545 group.add_option('--max_updates',
Gabe Black3b567202015-09-23 14:07:59 -07001546 metavar='NUM', default=-1, type='int',
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001547 help='maximum number of update checks handled positively '
1548 '(default: unlimited)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001549 group.add_option('--proxy_port',
1550 metavar='PORT', default=None, type='int',
1551 help='port to have the client connect to -- basically the '
1552 'devserver lies to the update to tell it to get the payload '
1553 'from a different port that will proxy the request back to '
1554 'the devserver. The proxy must be managed outside the '
1555 'devserver.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001556 parser.add_option_group(group)
1557
1558
1559def _AddUpdateOptions(parser):
1560 group = optparse.OptionGroup(
1561 parser, 'Autoupdate Options', 'These options can be used to change '
Amin Hassanie9ffb862019-09-25 17:10:40 -07001562 'how the devserver serve update payloads. Please '
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001563 'note that all of these option affect how a payload is generated and so '
1564 'do not work in archive-only mode.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001565 group.add_option('--critical_update',
1566 action='store_true', default=False,
1567 help='Present update payload as critical')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001568 group.add_option('--payload',
1569 metavar='PATH',
1570 help='use the update payload from specified directory '
1571 '(update.gz).')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001572 parser.add_option_group(group)
1573
1574
1575def _AddProductionOptions(parser):
1576 group = optparse.OptionGroup(
1577 parser, 'Advanced Server Options', 'These options can be used to changed '
1578 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001579 group.add_option('--clear_cache',
1580 action='store_true', default=False,
1581 help='At startup, removes all cached entries from the'
Amin Hassanid4e35392019-10-03 11:02:44 -07001582 "devserver's cache.")
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001583 group.add_option('--logfile',
1584 metavar='PATH',
1585 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001586 group.add_option('--pidfile',
1587 metavar='PATH',
1588 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001589 group.add_option('--portfile',
1590 metavar='PATH',
1591 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001592 group.add_option('--production',
1593 action='store_true', default=False,
1594 help='have the devserver use production values when '
1595 'starting up. This includes using more threads and '
1596 'performing less logging.')
1597 parser.add_option_group(group)
1598
1599
Paul Hobbsef4e0702016-06-27 17:01:42 -07001600def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001601 """Create a LogHandler instance used to log all messages."""
1602 hdlr_cls = handlers.TimedRotatingFileHandler
1603 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001604 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001605 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001606 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001607 return hdlr
1608
1609
Chris Sosacde6bf42012-05-31 18:36:39 -07001610def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001611 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001612 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001613
1614 # get directory that the devserver is run from
1615 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001616 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001617 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001618 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001619 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001620 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001621 parser.add_option('--port',
1622 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001623 help=('port for the dev server to use; if zero, binds to '
1624 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001625 parser.add_option('-t', '--test_image',
1626 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001627 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001628 parser.add_option('-x', '--xbuddy_manage_builds',
1629 action='store_true',
1630 default=False,
1631 help='If set, allow xbuddy to manage images in'
1632 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001633 parser.add_option('-a', '--android_build_credential',
1634 default=None,
1635 help='Path to a json file which contains the credential '
1636 'needed to access Android builds.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001637 _AddProductionOptions(parser)
1638 _AddUpdateOptions(parser)
1639 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001640 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001641
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001642 # Handle options that must be set globally in cherrypy. Do this
1643 # work up front, because calls to _Log() below depend on this
1644 # initialization.
1645 if options.production:
1646 cherrypy.config.update({'environment': 'production'})
1647 if not options.logfile:
1648 cherrypy.config.update({'log.screen': True})
1649 else:
1650 cherrypy.config.update({'log.error_file': '',
1651 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001652 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001653 # Pylint can't seem to process these two calls properly
1654 # pylint: disable=E1101
1655 cherrypy.log.access_log.addHandler(hdlr)
1656 cherrypy.log.error_log.addHandler(hdlr)
1657 # pylint: enable=E1101
1658
joychened64b222013-06-21 16:39:34 -07001659 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001660 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001661
joychened64b222013-06-21 16:39:34 -07001662 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001663 # If our devserver is only supposed to serve payloads, we shouldn't be
1664 # mucking with the cache at all. If the devserver hadn't previously
1665 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001666 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001667 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001668 else:
1669 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001670
Chris Sosadbc20082012-12-10 13:39:11 -08001671 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001672 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001673
Amin Hassanie9ffb862019-09-25 17:10:40 -07001674 _xbuddy = xbuddy.XBuddy(manage_builds=options.xbuddy_manage_builds,
joychen121fc9b2013-08-02 14:30:30 -07001675 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001676 if options.clear_cache and options.xbuddy_manage_builds:
1677 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001678
Chris Sosa6a3697f2013-01-29 16:44:43 -08001679 # We allow global use here to share with cherrypy classes.
1680 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001681 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001682 updater = autoupdate.Autoupdate(
joychen121fc9b2013-08-02 14:30:30 -07001683 _xbuddy,
joychened64b222013-06-21 16:39:34 -07001684 static_dir=options.static_dir,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001685 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001686 proxy_port=options.proxy_port,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001687 critical_update=options.critical_update,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001688 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001689 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001690 )
Chris Sosa7c931362010-10-11 19:49:01 -07001691
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001692 if options.exit:
1693 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001694
joychen3cb228e2013-06-12 12:13:13 -07001695 dev_server = DevServerRoot(_xbuddy)
Congbin Guo3afae6c2019-08-13 16:29:42 -07001696 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 12:13:13 -07001697
Gilad Arnold11fbef42014-02-10 11:04:13 -08001698 # Patch CherryPy to support binding to any available port (--port=0).
1699 cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy)
1700
Chris Sosa855b8932013-08-21 13:24:55 -07001701 if options.pidfile:
1702 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1703
Gilad Arnold11fbef42014-02-10 11:04:13 -08001704 if options.portfile:
1705 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1706
Dan Shiafd5c6c2016-01-07 10:27:03 -08001707 if (options.android_build_credential and
1708 os.path.exists(options.android_build_credential)):
1709 try:
1710 with open(options.android_build_credential) as f:
1711 android_build.BuildAccessor.credential_info = json.load(f)
1712 except ValueError as e:
1713 _Log('Failed to load the android build credential: %s. Error: %s.' %
1714 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 16:29:42 -07001715
1716 cherrypy.tree.mount(health_checker_app, '/check_health',
1717 config=health_checker.get_config())
joychen3cb228e2013-06-12 12:13:13 -07001718 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001719
1720
1721if __name__ == '__main__':
1722 main()