blob: de1ec56c303193a7c9fd30636fcbdc942a0ff3b3 [file] [log] [blame]
David Riley2fcb0122017-11-02 11:25:39 -07001#!/usr/bin/env python2
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -07002# -*- coding: utf-8 -*-
Chris Sosa781ba6d2012-04-11 12:44:43 -07003# Copyright (c) 2009-2012 The Chromium OS Authors. All rights reserved.
rtc@google.comded22402009-10-26 22:36:21 +00004# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Chris Sosa3ae4dc12013-03-29 11:47:00 -07007"""Chromium OS development server that can be used for all forms of update.
8
9This devserver can be used to perform system-wide autoupdate and update
10of specific portage packages on devices running Chromium OS derived operating
Amin Hassanie9ffb862019-09-25 17:10:40 -070011systems.
Chris Sosa3ae4dc12013-03-29 11:47:00 -070012
Amin Hassanie9ffb862019-09-25 17:10:40 -070013The devserver is configured to stage and
Chris Sosa3ae4dc12013-03-29 11:47:00 -070014serve artifacts from Google Storage using the credentials provided to it before
15it is run. The easiest way to understand this is that the devserver is
16functioning as a local cache for artifacts produced and uploaded by build
17servers. Users of this form of devserver can either download the artifacts
18from the devservers static directory OR use the update RPC to perform a
19system-wide autoupdate. Archive mode is always active.
20
Chris Sosa3ae4dc12013-03-29 11:47:00 -070021For autoupdates, there are many more advanced options that can help specify
22how to update and which payload to give to a requester.
23"""
24
Gabe Black3b567202015-09-23 14:07:59 -070025from __future__ import print_function
Chris Sosa7c931362010-10-11 19:49:01 -070026
Amin Hassani08e42d22019-06-03 00:31:30 -070027import httplib
Gilad Arnold55a2a372012-10-02 09:46:32 -070028import json
David Riley2fcb0122017-11-02 11:25:39 -070029import optparse # pylint: disable=deprecated-module
rtc@google.comded22402009-10-26 22:36:21 +000030import os
Scott Zawalski4647ce62012-01-03 17:17:28 -050031import re
Simran Basi4baad082013-02-14 13:39:18 -080032import shutil
xixuan52c2fba2016-05-20 17:02:48 -070033import signal
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -080034import socket
Chris Masone816e38c2012-05-02 12:22:36 -070035import subprocess
J. Richard Barnette3d977b82013-04-23 11:05:19 -070036import sys
Chris Masone816e38c2012-05-02 12:22:36 -070037import tempfile
Dan Shi59ae7092013-06-04 14:37:27 -070038import threading
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -070039import types
J. Richard Barnette3d977b82013-04-23 11:05:19 -070040from logging import handlers
41
42import cherrypy
David Riley2fcb0122017-11-02 11:25:39 -070043# pylint: disable=no-name-in-module
Chris Sosa855b8932013-08-21 13:24:55 -070044from cherrypy import _cplogging as cplogging
Congbin Guo3afae6c2019-08-13 16:29:42 -070045from cherrypy.process import plugins # pylint: disable=import-error
David Riley2fcb0122017-11-02 11:25:39 -070046# pylint: enable=no-name-in-module
rtc@google.comded22402009-10-26 22:36:21 +000047
Richard Barnettedf35c322017-08-18 17:02:13 -070048# This must happen before any local modules get a chance to import
49# anything from chromite. Otherwise, really bad things will happen, and
50# you will _not_ understand why.
Congbin Guo3afae6c2019-08-13 16:29:42 -070051import setup_chromite # pylint: disable=unused-import
Richard Barnettedf35c322017-08-18 17:02:13 -070052
Dan Shi2f136862016-02-11 15:38:38 -080053import artifact_info
Congbin Guo3afae6c2019-08-13 16:29:42 -070054import autoupdate
Chris Sosa75490802013-09-30 17:21:45 -070055import build_artifact
Gilad Arnold11fbef42014-02-10 11:04:13 -080056import cherrypy_ext
Gilad Arnoldc65330c2012-09-20 15:17:48 -070057import common_util
Simran Basief83d6a2014-08-28 14:32:01 -070058import devserver_constants
Congbin Guo4132a272019-08-20 12:32:14 -070059import devserver_exceptions
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
86except ImportError as e:
87 # 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
Gabe Black3b567202015-09-23 14:07:59 -0700123def _canonicalize_archive_url(archive_url):
124 """Canonicalizes archive_url strings.
125
126 Raises:
127 DevserverError: if archive_url is not set.
128 """
129 if archive_url:
130 if not archive_url.startswith('gs://'):
Congbin Guo4132a272019-08-20 12:32:14 -0700131 raise devserver_exceptions.DevServerError(
132 "Archive URL isn't from Google Storage (%s) ." % archive_url)
Gabe Black3b567202015-09-23 14:07:59 -0700133
134 return archive_url.rstrip('/')
135 else:
Congbin Guo4132a272019-08-20 12:32:14 -0700136 raise devserver_exceptions.DevServerError(
137 "Must specify an archive_url in the request")
Gabe Black3b567202015-09-23 14:07:59 -0700138
139
140def _canonicalize_local_path(local_path):
141 """Canonicalizes |local_path| strings.
142
143 Raises:
144 DevserverError: if |local_path| is not set.
145 """
146 # Restrict staging of local content to only files within the static
147 # directory.
148 local_path = os.path.abspath(local_path)
149 if not local_path.startswith(updater.static_dir):
Congbin Guo4132a272019-08-20 12:32:14 -0700150 raise devserver_exceptions.DevServerError(
151 'Local path %s must be a subdirectory of the static'
152 ' directory: %s' % (local_path, updater.static_dir))
Gabe Black3b567202015-09-23 14:07:59 -0700153
154 return local_path.rstrip('/')
155
156
157def _get_artifacts(kwargs):
158 """Returns a tuple of named and file artifacts given the stage rpc kwargs.
159
160 Raises:
161 DevserverError if no artifacts would be returned.
162 """
163 artifacts = kwargs.get('artifacts')
164 files = kwargs.get('files')
165 if not artifacts and not files:
Congbin Guo4132a272019-08-20 12:32:14 -0700166 raise devserver_exceptions.DevServerError('No artifacts specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700167
168 # Note we NEED to coerce files to a string as we get raw unicode from
169 # cherrypy and we treat files as strings elsewhere in the code.
170 return (str(artifacts).split(',') if artifacts else [],
171 str(files).split(',') if files else [])
172
173
Dan Shi61305df2015-10-26 16:52:35 -0700174def _is_android_build_request(kwargs):
175 """Check if a devserver call is for Android build, based on the arguments.
176
177 This method exams the request's arguments (os_type) to determine if the
178 request is for Android build. If os_type is set to `android`, returns True.
179 If os_type is not set or has other values, returns False.
180
181 Args:
182 kwargs: Keyword arguments for the request.
183
184 Returns:
185 True if the request is for Android build. False otherwise.
186 """
187 os_type = kwargs.get('os_type', None)
188 return os_type == 'android'
189
190
Gabe Black3b567202015-09-23 14:07:59 -0700191def _get_downloader(kwargs):
192 """Returns the downloader based on passed in arguments.
193
194 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700195 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700196 """
197 local_path = kwargs.get('local_path')
198 if local_path:
199 local_path = _canonicalize_local_path(local_path)
200
201 dl = None
202 if local_path:
Prathmesh Prabhu58d08932018-01-19 15:08:19 -0800203 delete_source = _parse_boolean_arg(kwargs, 'delete_source')
204 dl = downloader.LocalDownloader(updater.static_dir, local_path,
205 delete_source=delete_source)
Gabe Black3b567202015-09-23 14:07:59 -0700206
Dan Shi61305df2015-10-26 16:52:35 -0700207 if not _is_android_build_request(kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700208 archive_url = kwargs.get('archive_url')
209 if not archive_url and not local_path:
Congbin Guo4132a272019-08-20 12:32:14 -0700210 raise devserver_exceptions.DevServerError(
211 'Requires archive_url or local_path to be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700212 if archive_url and local_path:
Congbin Guo4132a272019-08-20 12:32:14 -0700213 raise devserver_exceptions.DevServerError(
214 'archive_url and local_path can not both be specified.')
Gabe Black3b567202015-09-23 14:07:59 -0700215 if not dl:
216 archive_url = _canonicalize_archive_url(archive_url)
Luis Hector Chavezdca9dd72018-06-12 12:56:30 -0700217 dl = downloader.GoogleStorageDownloader(
218 updater.static_dir, archive_url,
219 downloader.GoogleStorageDownloader.GetBuildIdFromArchiveURL(
220 archive_url))
Gabe Black3b567202015-09-23 14:07:59 -0700221 elif not dl:
222 target = kwargs.get('target', None)
Dan Shi72b16132015-10-08 12:10:33 -0700223 branch = kwargs.get('branch', None)
Dan Shi61305df2015-10-26 16:52:35 -0700224 build_id = kwargs.get('build_id', None)
225 if not target or not branch or not build_id:
Congbin Guo4132a272019-08-20 12:32:14 -0700226 raise devserver_exceptions.DevServerError(
Dan Shi61305df2015-10-26 16:52:35 -0700227 'target, branch, build ID must all be specified for downloading '
228 'Android build.')
Dan Shi72b16132015-10-08 12:10:33 -0700229 dl = downloader.AndroidBuildDownloader(updater.static_dir, branch, build_id,
230 target)
Gabe Black3b567202015-09-23 14:07:59 -0700231
232 return dl
233
234
235def _get_downloader_and_factory(kwargs):
236 """Returns the downloader and artifact factory based on passed in arguments.
237
238 Args:
Amin Hassani08e42d22019-06-03 00:31:30 -0700239 kwargs: Keyword arguments for the request.
Gabe Black3b567202015-09-23 14:07:59 -0700240 """
241 artifacts, files = _get_artifacts(kwargs)
242 dl = _get_downloader(kwargs)
243
244 if (isinstance(dl, downloader.GoogleStorageDownloader) or
245 isinstance(dl, downloader.LocalDownloader)):
246 factory_class = build_artifact.ChromeOSArtifactFactory
Dan Shi72b16132015-10-08 12:10:33 -0700247 elif isinstance(dl, downloader.AndroidBuildDownloader):
Gabe Black3b567202015-09-23 14:07:59 -0700248 factory_class = build_artifact.AndroidArtifactFactory
249 else:
Congbin Guo4132a272019-08-20 12:32:14 -0700250 raise devserver_exceptions.DevServerError(
251 'Unrecognized value for downloader type: %s' % type(dl))
Gabe Black3b567202015-09-23 14:07:59 -0700252
253 factory = factory_class(dl.GetBuildDir(), artifacts, files, dl.GetBuild())
254
255 return dl, factory
256
257
Scott Zawalski4647ce62012-01-03 17:17:28 -0500258def _LeadingWhiteSpaceCount(string):
259 """Count the amount of leading whitespace in a string.
260
261 Args:
262 string: The string to count leading whitespace in.
Don Garrettf84631a2014-01-07 18:21:26 -0800263
Scott Zawalski4647ce62012-01-03 17:17:28 -0500264 Returns:
265 number of white space chars before characters start.
266 """
Gabe Black3b567202015-09-23 14:07:59 -0700267 matched = re.match(r'^\s+', string)
Scott Zawalski4647ce62012-01-03 17:17:28 -0500268 if matched:
269 return len(matched.group())
270
271 return 0
272
273
274def _PrintDocStringAsHTML(func):
275 """Make a functions docstring somewhat HTML style.
276
277 Args:
278 func: The function to return the docstring from.
Don Garrettf84631a2014-01-07 18:21:26 -0800279
Scott Zawalski4647ce62012-01-03 17:17:28 -0500280 Returns:
281 A string that is somewhat formated for a web browser.
282 """
283 # TODO(scottz): Make this parse Args/Returns in a prettier way.
284 # Arguments could be bolded and indented etc.
285 html_doc = []
286 for line in func.__doc__.splitlines():
287 leading_space = _LeadingWhiteSpaceCount(line)
288 if leading_space > 0:
Chris Sosa47a7d4e2012-03-28 11:26:55 -0700289 line = ' ' * leading_space + line
Scott Zawalski4647ce62012-01-03 17:17:28 -0500290
291 html_doc.append('<BR>%s' % line)
292
293 return '\n'.join(html_doc)
294
295
Simran Basief83d6a2014-08-28 14:32:01 -0700296def _GetUpdateTimestampHandler(static_dir):
297 """Returns a handler to update directory staged.timestamp.
298
299 This handler resets the stage.timestamp whenever static content is accessed.
300
301 Args:
302 static_dir: Directory from which static content is being staged.
303
304 Returns:
Amin Hassani08e42d22019-06-03 00:31:30 -0700305 A cherrypy handler to update the timestamp of accessed content.
Simran Basief83d6a2014-08-28 14:32:01 -0700306 """
307 def UpdateTimestampHandler():
308 if not '404' in cherrypy.response.status:
309 build_match = re.match(devserver_constants.STAGED_BUILD_REGEX,
310 cherrypy.request.path_info)
311 if build_match:
312 build_dir = os.path.join(static_dir, build_match.group('build'))
313 downloader.Downloader.TouchTimestampForStaged(build_dir)
314 return UpdateTimestampHandler
315
316
Chris Sosa7c931362010-10-11 19:49:01 -0700317def _GetConfig(options):
318 """Returns the configuration for the devserver."""
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800319
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800320 socket_host = '::'
Yu-Ju Hongc8d4af32013-11-12 15:14:26 -0800321 # Fall back to IPv4 when python is not configured with IPv6.
322 if not socket.has_ipv6:
Mandeep Singh Baines38dcdda2012-12-07 17:55:33 -0800323 socket_host = '0.0.0.0'
324
Simran Basief83d6a2014-08-28 14:32:01 -0700325 # Adds the UpdateTimestampHandler to cherrypy's tools. This tools executes
326 # on the on_end_resource hook. This hook is called once processing is
327 # complete and the response is ready to be returned.
328 cherrypy.tools.update_timestamp = cherrypy.Tool(
329 'on_end_resource', _GetUpdateTimestampHandler(options.static_dir))
330
David Riley2fcb0122017-11-02 11:25:39 -0700331 base_config = {
332 'global': {
333 'server.log_request_headers': True,
334 'server.protocol_version': 'HTTP/1.1',
335 'server.socket_host': socket_host,
336 'server.socket_port': int(options.port),
337 'response.timeout': 6000,
338 'request.show_tracebacks': True,
339 'server.socket_timeout': 60,
340 'server.thread_pool': 2,
341 'engine.autoreload.on': False,
342 },
343 '/api': {
344 # Gets rid of cherrypy parsing post file for args.
345 'request.process_request_body': False,
346 },
347 '/build': {
348 'response.timeout': 100000,
349 },
350 '/update': {
351 # Gets rid of cherrypy parsing post file for args.
352 'request.process_request_body': False,
353 'response.timeout': 10000,
354 },
355 # Sets up the static dir for file hosting.
356 '/static': {
357 'tools.staticdir.dir': options.static_dir,
358 'tools.staticdir.on': True,
359 'response.timeout': 10000,
360 'tools.update_timestamp.on': True,
361 },
362 }
Chris Sosa5f118ef2012-07-12 11:37:50 -0700363 if options.production:
Alex Miller93beca52013-07-30 19:25:09 -0700364 base_config['global'].update({'server.thread_pool': 150})
Scott Zawalski1c5e7cd2012-02-27 13:12:52 -0500365
Chris Sosa7c931362010-10-11 19:49:01 -0700366 return base_config
rtc@google.com64244662009-11-12 00:52:08 +0000367
Darin Petkove17164a2010-08-11 13:24:41 -0700368
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700369def _GetRecursiveMemberObject(root, member_list):
370 """Returns an object corresponding to a nested member list.
371
372 Args:
373 root: the root object to search
374 member_list: list of nested members to search
Don Garrettf84631a2014-01-07 18:21:26 -0800375
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700376 Returns:
377 An object corresponding to the member name list; None otherwise.
378 """
379 for member in member_list:
380 next_root = root.__class__.__dict__.get(member)
381 if not next_root:
382 return None
383 root = next_root
384 return root
385
386
387def _IsExposed(name):
388 """Returns True iff |name| has an `exposed' attribute and it is set."""
389 return hasattr(name, 'exposed') and name.exposed
390
391
Congbin Guo6bc32182019-08-20 17:54:30 -0700392def _GetExposedMethod(nested_member):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700393 """Returns a CherryPy-exposed method, if such exists.
394
395 Args:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700396 nested_member: a slash-joined path to the nested member
Don Garrettf84631a2014-01-07 18:21:26 -0800397
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700398 Returns:
Congbin Guo6bc32182019-08-20 17:54:30 -0700399 A function object corresponding to the path defined by |nested_member| from
400 the app root object registered, if the function is exposed; None otherwise.
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700401 """
Congbin Guo6bc32182019-08-20 17:54:30 -0700402 for app in cherrypy.tree.apps.values():
403 # Use the 'index' function doc as the doc of the app.
404 if nested_member == app.script_name.lstrip('/'):
405 nested_member = 'index'
406
407 method = _GetRecursiveMemberObject(app.root, nested_member.split('/'))
408 if method and isinstance(method, types.FunctionType) and _IsExposed(method):
409 return method
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700410
411
Gilad Arnold748c8322012-10-12 09:51:35 -0700412def _FindExposedMethods(root, prefix, unlisted=None):
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700413 """Finds exposed CherryPy methods.
414
415 Args:
416 root: the root object for searching
417 prefix: slash-joined chain of members leading to current object
418 unlisted: URLs to be excluded regardless of their exposed status
Don Garrettf84631a2014-01-07 18:21:26 -0800419
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700420 Returns:
421 List of exposed URLs that are not unlisted.
422 """
423 method_list = []
Congbin Guo6bc32182019-08-20 17:54:30 -0700424 for member in root.__class__.__dict__.keys():
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700425 prefixed_member = prefix + '/' + member if prefix else member
Gilad Arnold748c8322012-10-12 09:51:35 -0700426 if unlisted and prefixed_member in unlisted:
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700427 continue
428 member_obj = root.__class__.__dict__[member]
429 if _IsExposed(member_obj):
Amin Hassani08e42d22019-06-03 00:31:30 -0700430 if isinstance(member_obj, types.FunctionType):
Congbin Guo6bc32182019-08-20 17:54:30 -0700431 # Regard the app name as exposed "method" name if it exposed 'index'
432 # function.
433 if prefix and member == 'index':
434 method_list.append(prefix)
435 else:
436 method_list.append(prefixed_member)
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -0700437 else:
438 method_list += _FindExposedMethods(
439 member_obj, prefixed_member, unlisted)
440 return method_list
441
442
xixuan52c2fba2016-05-20 17:02:48 -0700443def _check_base_args_for_auto_update(kwargs):
xixuanac89ce82016-11-30 16:48:20 -0800444 """Check basic args required for auto-update.
445
446 Args:
447 kwargs: the parameters to be checked.
448
449 Raises:
450 DevServerHTTPError if required parameters don't exist in kwargs.
451 """
xixuan52c2fba2016-05-20 17:02:48 -0700452 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700453 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
454 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700455
456 if 'build_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700457 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
458 KEY_ERROR_MSG % 'build_name')
xixuan52c2fba2016-05-20 17:02:48 -0700459
460
461def _parse_boolean_arg(kwargs, key):
xixuanac89ce82016-11-30 16:48:20 -0800462 """Parse boolean arg from kwargs.
463
464 Args:
465 kwargs: the parameters to be checked.
466 key: the key to be parsed.
467
468 Returns:
469 The boolean value of kwargs[key], or False if key doesn't exist in kwargs.
470
471 Raises:
472 DevServerHTTPError if kwargs[key] is not a boolean variable.
473 """
xixuan52c2fba2016-05-20 17:02:48 -0700474 if key in kwargs:
475 if kwargs[key] == 'True':
476 return True
477 elif kwargs[key] == 'False':
478 return False
479 else:
480 raise common_util.DevServerHTTPError(
Amin Hassani08e42d22019-06-03 00:31:30 -0700481 httplib.INTERNAL_SERVER_ERROR,
xixuan52c2fba2016-05-20 17:02:48 -0700482 'The value for key %s is not boolean.' % key)
483 else:
484 return False
485
xixuan447ad9d2017-02-28 14:46:20 -0800486
xixuanac89ce82016-11-30 16:48:20 -0800487def _parse_string_arg(kwargs, key):
488 """Parse string arg from kwargs.
489
490 Args:
491 kwargs: the parameters to be checked.
492 key: the key to be parsed.
493
494 Returns:
495 The string value of kwargs[key], or None if key doesn't exist in kwargs.
496 """
497 if key in kwargs:
498 return kwargs[key]
499 else:
500 return None
501
xixuan447ad9d2017-02-28 14:46:20 -0800502
xixuanac89ce82016-11-30 16:48:20 -0800503def _build_uri_from_build_name(build_name):
504 """Get build url from a given build name.
505
506 Args:
507 build_name: the build name to be parsed, whose format is
508 'board/release_version'.
509
510 Returns:
511 The release_archive_url on Google Storage for this build name.
512 """
Amin Hassani08e42d22019-06-03 00:31:30 -0700513 # TODO(ahassani): This function doesn't seem to be used anywhere since its
514 # previous use of lib.paygen.gspath was broken and it doesn't seem to be
515 # causing any runtime issues. So deprecate this in the future.
516 tokens = build_name.split('/')
517 return 'gs://chromeos-releases/stable-channel/%s/%s' % (tokens[0], tokens[1])
xixuan52c2fba2016-05-20 17:02:48 -0700518
xixuan447ad9d2017-02-28 14:46:20 -0800519
520def _clear_process(host_name, pid):
521 """Clear AU process for given hostname and pid.
522
523 This clear includes:
524 1. kill process if it's alive.
525 2. delete the track status file of this process.
526 3. delete the executing log file of this process.
527
528 Args:
529 host_name: the host to execute auto-update.
530 pid: the background auto-update process id.
531 """
532 if cros_update_progress.IsProcessAlive(pid):
533 os.killpg(int(pid), signal.SIGKILL)
534
535 cros_update_progress.DelTrackStatusFile(host_name, pid)
536 cros_update_progress.DelExecuteLogFile(host_name, pid)
537
538
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700539class ApiRoot(object):
540 """RESTful API for Dev Server information."""
541 exposed = True
542
543 @cherrypy.expose
544 def hostinfo(self, ip):
545 """Returns a JSON dictionary containing information about the given ip.
546
Gilad Arnold1b908392012-10-05 11:36:27 -0700547 Args:
548 ip: address of host whose info is requested
Don Garrettf84631a2014-01-07 18:21:26 -0800549
Gilad Arnold1b908392012-10-05 11:36:27 -0700550 Returns:
551 A JSON dictionary containing all or some of the following fields:
552 last_event_type (int): last update event type received
553 last_event_status (int): last update event status received
554 last_known_version (string): last known version reported in update ping
Gilad Arnold1b908392012-10-05 11:36:27 -0700555 See the OmahaEvent class in update_engine/omaha_request_action.h for
556 event type and status code definitions. If the ip does not exist an empty
557 string is returned.
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700558
Gilad Arnold1b908392012-10-05 11:36:27 -0700559 Example URL:
560 http://myhost/api/hostinfo?ip=192.168.1.5
561 """
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700562 return updater.HandleHostInfoPing(ip)
563
564 @cherrypy.expose
Gilad Arnold286a0062012-01-12 13:47:02 -0800565 def hostlog(self, ip):
Gilad Arnold1b908392012-10-05 11:36:27 -0700566 """Returns a JSON object containing a log of host event.
567
568 Args:
569 ip: address of host whose event log is requested, or `all'
Don Garrettf84631a2014-01-07 18:21:26 -0800570
Gilad Arnold1b908392012-10-05 11:36:27 -0700571 Returns:
572 A JSON encoded list (log) of dictionaries (events), each of which
573 containing a `timestamp' and other event fields, as described under
574 /api/hostinfo.
575
576 Example URL:
577 http://myhost/api/hostlog?ip=192.168.1.5
578 """
Gilad Arnold286a0062012-01-12 13:47:02 -0800579 return updater.HandleHostLogPing(ip)
580
581 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -0800582 def fileinfo(self, *args):
Gilad Arnold55a2a372012-10-02 09:46:32 -0700583 """Returns information about a given staged file.
584
585 Args:
Don Garrettf84631a2014-01-07 18:21:26 -0800586 args: path to the file inside the server's static staging directory
587
Gilad Arnold55a2a372012-10-02 09:46:32 -0700588 Returns:
589 A JSON encoded dictionary with information about the said file, which may
590 contain the following keys/values:
Gilad Arnold1b908392012-10-05 11:36:27 -0700591 size (int): the file size in bytes
Gilad Arnold1b908392012-10-05 11:36:27 -0700592 sha256 (string): a base64 encoded SHA256 hash
593
594 Example URL:
595 http://myhost/api/fileinfo/some/path/to/file
Gilad Arnold55a2a372012-10-02 09:46:32 -0700596 """
Don Garrettf84631a2014-01-07 18:21:26 -0800597 file_path = os.path.join(updater.static_dir, *args)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700598 if not os.path.exists(file_path):
Congbin Guo4132a272019-08-20 12:32:14 -0700599 raise devserver_exceptions.DevServerError(
600 'file not found: %s' % file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700601 try:
602 file_size = os.path.getsize(file_path)
Gilad Arnold55a2a372012-10-02 09:46:32 -0700603 file_sha256 = common_util.GetFileSha256(file_path)
604 except os.error, e:
Congbin Guo4132a272019-08-20 12:32:14 -0700605 raise devserver_exceptions.DevServerError(
606 'failed to get info for file %s: %s' % (file_path, e))
Gilad Arnolde74b3812013-04-22 11:27:38 -0700607
Gilad Arnolde74b3812013-04-22 11:27:38 -0700608 return json.dumps({
609 autoupdate.Autoupdate.SIZE_ATTR: file_size,
Gilad Arnolde74b3812013-04-22 11:27:38 -0700610 autoupdate.Autoupdate.SHA256_ATTR: file_sha256,
Gilad Arnolde74b3812013-04-22 11:27:38 -0700611 })
Gilad Arnold55a2a372012-10-02 09:46:32 -0700612
Chris Sosa76e44b92013-01-31 12:11:38 -0800613
David Rochberg7c79a812011-01-19 14:24:45 -0500614class DevServerRoot(object):
Chris Sosa7c931362010-10-11 19:49:01 -0700615 """The Root Class for the Dev Server.
616
617 CherryPy works as follows:
618 For each method in this class, cherrpy interprets root/path
619 as a call to an instance of DevServerRoot->method_name. For example,
620 a call to http://myhost/build will call build. CherryPy automatically
621 parses http args and places them as keyword arguments in each method.
622 For paths http://myhost/update/dir1/dir2, you can use *args so that
623 cherrypy uses the update method and puts the extra paths in args.
624 """
Gilad Arnoldf8f769f2012-09-24 08:43:01 -0700625 # Method names that should not be listed on the index page.
626 _UNLISTED_METHODS = ['index', 'doc']
627
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700628 api = ApiRoot()
Chris Sosa7c931362010-10-11 19:49:01 -0700629
Dan Shi59ae7092013-06-04 14:37:27 -0700630 # Number of threads that devserver is staging images.
631 _staging_thread_count = 0
632 # Lock used to lock increasing/decreasing count.
633 _staging_thread_count_lock = threading.Lock()
634
joychen3cb228e2013-06-12 12:13:13 -0700635 def __init__(self, _xbuddy):
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700636 self._builder = None
Simran Basi4baad082013-02-14 13:39:18 -0800637 self._telemetry_lock_dict = common_util.LockDict()
joychen3cb228e2013-06-12 12:13:13 -0700638 self._xbuddy = _xbuddy
David Rochberg7c79a812011-01-19 14:24:45 -0500639
Congbin Guo3afae6c2019-08-13 16:29:42 -0700640 @property
641 def staging_thread_count(self):
642 """Get the staging thread count."""
643 return self._staging_thread_count
Dan Shiafd0e492015-05-27 14:23:51 -0700644
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700645 @cherrypy.expose
David Rochberg7c79a812011-01-19 14:24:45 -0500646 def build(self, board, pkg, **kwargs):
Chris Sosa7c931362010-10-11 19:49:01 -0700647 """Builds the package specified."""
Nick Sanders7dcaa2e2011-08-04 15:20:41 -0700648 import builder
649 if self._builder is None:
650 self._builder = builder.Builder()
David Rochberg7c79a812011-01-19 14:24:45 -0500651 return self._builder.Build(board, pkg, kwargs)
Chris Sosa7c931362010-10-11 19:49:01 -0700652
Dale Curtisc9aaf3a2011-08-09 15:47:40 -0700653 @cherrypy.expose
Dan Shif8eb0d12013-08-01 17:52:06 -0700654 def is_staged(self, **kwargs):
655 """Check if artifacts have been downloaded.
656
Congbin Guo3afae6c2019-08-13 16:29:42 -0700657 Examples:
658 To check if autotest and test_suites are staged:
659 http://devserver_url:<port>/is_staged?archive_url=gs://your_url/path&
660 artifacts=autotest,test_suites
661
Amin Hassani08e42d22019-06-03 00:31:30 -0700662 Args:
Chris Sosa6b0c6172013-08-05 17:01:33 -0700663 async: True to return without waiting for download to complete.
664 artifacts: Comma separated list of named artifacts to download.
665 These are defined in artifact_info and have their implementation
666 in build_artifact.py.
667 files: Comma separated list of file artifacts to stage. These
668 will be available as is in the corresponding static directory with no
669 custom post-processing.
670
Congbin Guo3afae6c2019-08-13 16:29:42 -0700671 Returns:
672 True of all artifacts are staged.
Dan Shif8eb0d12013-08-01 17:52:06 -0700673 """
Gabe Black3b567202015-09-23 14:07:59 -0700674 dl, factory = _get_downloader_and_factory(kwargs)
Aviv Keshet57d18172016-06-18 20:39:09 -0700675 response = str(dl.IsStaged(factory))
676 _Log('Responding to is_staged %s request with %r', kwargs, response)
677 return response
Dan Shi59ae7092013-06-04 14:37:27 -0700678
Chris Sosa76e44b92013-01-31 12:11:38 -0800679 @cherrypy.expose
Prashanth Ba06d2d22014-03-07 15:35:19 -0800680 def list_image_dir(self, **kwargs):
681 """Take an archive url and list the contents in its staged directory.
682
Amin Hassani08e42d22019-06-03 00:31:30 -0700683 Examples:
Prashanth Ba06d2d22014-03-07 15:35:19 -0800684 To list the contents of where this devserver should have staged
685 gs://image-archive/<board>-release/<build> call:
686 http://devserver_url:<port>/list_image_dir?archive_url=<gs://..>
687
Congbin Guo3afae6c2019-08-13 16:29:42 -0700688 Args:
689 archive_url: Google Storage URL for the build.
690
Prashanth Ba06d2d22014-03-07 15:35:19 -0800691 Returns:
692 A string with information about the contents of the image directory.
693 """
Gabe Black3b567202015-09-23 14:07:59 -0700694 dl = _get_downloader(kwargs)
Prashanth Ba06d2d22014-03-07 15:35:19 -0800695 try:
Gabe Black3b567202015-09-23 14:07:59 -0700696 image_dir_contents = dl.ListBuildDir()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800697 except build_artifact.ArtifactDownloadError as e:
698 return 'Cannot list the contents of staged artifacts. %s' % e
699 if not image_dir_contents:
Gabe Black3b567202015-09-23 14:07:59 -0700700 return '%s has not been staged on this devserver.' % dl.DescribeSource()
Prashanth Ba06d2d22014-03-07 15:35:19 -0800701 return image_dir_contents
702
703 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -0800704 def stage(self, **kwargs):
Gabe Black3b567202015-09-23 14:07:59 -0700705 """Downloads and caches build artifacts.
Chris Sosa76e44b92013-01-31 12:11:38 -0800706
Gabe Black3b567202015-09-23 14:07:59 -0700707 Downloads and caches build artifacts, possibly from a Google Storage URL,
Dan Shi72b16132015-10-08 12:10:33 -0700708 or from Android's build server. Returns once these have been downloaded
Gabe Black3b567202015-09-23 14:07:59 -0700709 on the devserver. A call to this will attempt to cache non-specified
710 artifacts in the background for the given from the given URL following
711 the principle of spatial locality. Spatial locality of different
Chris Sosa76e44b92013-01-31 12:11:38 -0800712 artifacts is explicitly defined in the build_artifact module.
713
714 These artifacts will then be available from the static/ sub-directory of
715 the devserver.
716
Amin Hassani08e42d22019-06-03 00:31:30 -0700717 Examples:
Chris Sosa76e44b92013-01-31 12:11:38 -0800718 To download the autotest and test suites tarballs:
719 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
720 artifacts=autotest,test_suites
721 To download the full update payload:
722 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
723 artifacts=full_payload
Chris Sosa6b0c6172013-08-05 17:01:33 -0700724 To download just a file called blah.bin:
725 http://devserver_url:<port>/stage?archive_url=gs://your_url/path&
726 files=blah.bin
Chris Sosa76e44b92013-01-31 12:11:38 -0800727
728 For both these examples, one could find these artifacts at:
joychened64b222013-06-21 16:39:34 -0700729 http://devserver_url:<port>/static/<relative_path>*
Chris Sosa76e44b92013-01-31 12:11:38 -0800730
731 Note for this example, relative path is the archive_url stripped of its
732 basename i.e. path/ in the examples above. Specific example:
733
734 gs://chromeos-image-archive/x86-mario-release/R26-3920.0.0
735
736 Will get staged to:
737
joychened64b222013-06-21 16:39:34 -0700738 http://devserver_url:<port>/static/x86-mario-release/R26-3920.0.0
Congbin Guo3afae6c2019-08-13 16:29:42 -0700739
740 Args:
741 archive_url: Google Storage URL for the build.
742 local_path: Local path for the build.
743 delete_source: Only meaningful with local_path. bool to indicate if the
744 source files should be deleted. This is especially useful when staging
745 a file locally in resource constrained environments as it allows us to
746 move the relevant files locally instead of copying them.
747 async: True to return without waiting for download to complete.
748 artifacts: Comma separated list of named artifacts to download.
749 These are defined in artifact_info and have their implementation
750 in build_artifact.py.
751 files: Comma separated list of files to stage. These
752 will be available as is in the corresponding static directory with no
753 custom post-processing.
754 clean: True to remove any previously staged artifacts first.
Chris Sosa76e44b92013-01-31 12:11:38 -0800755 """
Gabe Black3b567202015-09-23 14:07:59 -0700756 dl, factory = _get_downloader_and_factory(kwargs)
757
Dan Shi59ae7092013-06-04 14:37:27 -0700758 with DevServerRoot._staging_thread_count_lock:
759 DevServerRoot._staging_thread_count += 1
760 try:
Laurence Goodbyf5c958d2016-01-14 18:23:56 -0800761 boolean_string = kwargs.get('clean')
762 clean = xbuddy.XBuddy.ParseBoolean(boolean_string)
763 if clean and os.path.exists(dl.GetBuildDir()):
764 _Log('Removing %s' % dl.GetBuildDir())
765 shutil.rmtree(dl.GetBuildDir())
Gabe Black3b567202015-09-23 14:07:59 -0700766 async = kwargs.get('async', False)
767 dl.Download(factory, async=async)
Dan Shi59ae7092013-06-04 14:37:27 -0700768 finally:
769 with DevServerRoot._staging_thread_count_lock:
770 DevServerRoot._staging_thread_count -= 1
Chris Sosa76e44b92013-01-31 12:11:38 -0800771 return 'Success'
Chris Sosacde6bf42012-05-31 18:36:39 -0700772
773 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700774 def cros_au(self, **kwargs):
775 """Auto-update a CrOS DUT.
776
777 Args:
778 kwargs:
779 host_name: the hostname of the DUT to auto-update.
780 build_name: the build name for update the DUT.
781 force_update: Force an update even if the version installed is the
782 same. Default: False.
783 full_update: If True, do not run stateful update, directly force a full
784 reimage. If False, try stateful update first if the dut is already
785 installed with the same version.
786 async: Whether the auto_update function is ran in the background.
David Rileyee75de22017-11-02 10:48:15 -0700787 quick_provision: Whether the quick provision path is attempted first.
xixuan52c2fba2016-05-20 17:02:48 -0700788
789 Returns:
790 A tuple includes two elements:
791 a boolean variable represents whether the auto-update process is
792 successfully started.
793 an integer represents the background auto-update process id.
794 """
795 _check_base_args_for_auto_update(kwargs)
796
797 host_name = kwargs['host_name']
798 build_name = kwargs['build_name']
799 force_update = _parse_boolean_arg(kwargs, 'force_update')
800 full_update = _parse_boolean_arg(kwargs, 'full_update')
801 async = _parse_boolean_arg(kwargs, 'async')
xixuanac89ce82016-11-30 16:48:20 -0800802 original_build = _parse_string_arg(kwargs, 'original_build')
David Haddock90e49442017-04-07 19:14:09 -0700803 payload_filename = _parse_string_arg(kwargs, 'payload_filename')
David Haddock20559612017-06-28 22:15:08 -0700804 clobber_stateful = _parse_boolean_arg(kwargs, 'clobber_stateful')
David Rileyee75de22017-11-02 10:48:15 -0700805 quick_provision = _parse_boolean_arg(kwargs, 'quick_provision')
806
807 devserver_url = updater.GetDevserverUrl()
808 static_url = updater.GetStaticUrl()
xixuan52c2fba2016-05-20 17:02:48 -0700809
810 if async:
811 path = os.path.dirname(os.path.abspath(__file__))
812 execute_file = os.path.join(path, 'cros_update.py')
813 args = (AUTO_UPDATE_CMD % (execute_file, host_name, build_name,
814 updater.static_dir))
xixuanac89ce82016-11-30 16:48:20 -0800815
816 # The original_build's format is like: link/3428.210.0
817 # The corresponding release_archive_url's format is like:
818 # gs://chromeos-releases/stable-channel/link/3428.210.0
819 if original_build:
820 release_archive_url = _build_uri_from_build_name(original_build)
821 # First staging the stateful.tgz synchronousely.
822 self.stage(files='stateful.tgz', async=False,
823 archive_url=release_archive_url)
824 args = ('%s --original_build %s' % (args, original_build))
825
xixuan52c2fba2016-05-20 17:02:48 -0700826 if force_update:
827 args = ('%s --force_update' % args)
828
829 if full_update:
830 args = ('%s --full_update' % args)
831
David Haddock90e49442017-04-07 19:14:09 -0700832 if payload_filename:
833 args = ('%s --payload_filename %s' % (args, payload_filename))
834
David Haddock20559612017-06-28 22:15:08 -0700835 if clobber_stateful:
836 args = ('%s --clobber_stateful' % args)
837
David Rileyee75de22017-11-02 10:48:15 -0700838 if quick_provision:
839 args = ('%s --quick_provision' % args)
840
841 if devserver_url:
842 args = ('%s --devserver_url %s' % (args, devserver_url))
843
844 if static_url:
845 args = ('%s --static_url %s' % (args, static_url))
846
xixuan2a0970a2016-08-10 12:12:44 -0700847 p = subprocess.Popen([args], shell=True, preexec_fn=os.setsid)
848 pid = os.getpgid(p.pid)
xixuan52c2fba2016-05-20 17:02:48 -0700849
850 # Pre-write status in the track_status_file before the first call of
851 # 'get_au_status' to make sure that the track_status_file exists.
xixuan2a0970a2016-08-10 12:12:44 -0700852 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700853 progress_tracker.WriteStatus('CrOS update is just started.')
854
xixuan2a0970a2016-08-10 12:12:44 -0700855 return json.dumps((True, pid))
xixuan52c2fba2016-05-20 17:02:48 -0700856 else:
857 cros_update_trigger = cros_update.CrOSUpdateTrigger(
xixuanac89ce82016-11-30 16:48:20 -0800858 host_name, build_name, updater.static_dir, force_update=force_update,
David Rileyee75de22017-11-02 10:48:15 -0700859 full_update=full_update, original_build=original_build,
860 quick_provision=quick_provision, devserver_url=devserver_url,
861 static_url=static_url)
xixuan52c2fba2016-05-20 17:02:48 -0700862 cros_update_trigger.TriggerAU()
xixuan27d50442017-08-09 10:38:25 -0700863 return json.dumps((True, -1))
xixuan52c2fba2016-05-20 17:02:48 -0700864
865 @cherrypy.expose
866 def get_au_status(self, **kwargs):
867 """Check if the auto-update task is finished.
868
869 It handles 4 cases:
870 1. If an error exists in the track_status_file, delete the track file and
871 raise it.
872 2. If cros-update process is finished, delete the file and return the
873 success result.
874 3. If the process is not running, delete the track file and raise an error
875 about 'the process is terminated due to unknown reason'.
876 4. If the track_status_file does not exist, kill the process if it exists,
877 and raise the IOError.
878
879 Args:
880 kwargs:
881 host_name: the hostname of the DUT to auto-update.
882 pid: the background process id of cros-update.
883
884 Returns:
xixuan28d99072016-10-06 12:24:16 -0700885 A dict with three elements:
xixuan52c2fba2016-05-20 17:02:48 -0700886 a boolean variable represents whether the auto-update process is
887 finished.
888 a string represents the current auto-update process status.
889 For example, 'Transfer Devserver/Stateful Update Package'.
xixuan28d99072016-10-06 12:24:16 -0700890 a detailed error message paragraph if there exists an Auto-Update
891 error, in which the last line shows the main exception. Empty
892 string otherwise.
xixuan52c2fba2016-05-20 17:02:48 -0700893 """
894 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700895 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
896 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700897
898 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700899 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
900 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700901
902 host_name = kwargs['host_name']
903 pid = kwargs['pid']
904 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
905
xixuan28d99072016-10-06 12:24:16 -0700906 result_dict = {'finished': False, 'status': '', 'detailed_error_msg': ''}
xixuan52c2fba2016-05-20 17:02:48 -0700907 try:
908 result = progress_tracker.ReadStatus()
909 if result.startswith(cros_update_progress.ERROR_TAG):
xixuan28d99072016-10-06 12:24:16 -0700910 result_dict['detailed_error_msg'] = result[len(
911 cros_update_progress.ERROR_TAG):]
xixuan28681fd2016-11-23 11:13:56 -0800912 elif result == cros_update_progress.FINISHED:
xixuan28d99072016-10-06 12:24:16 -0700913 result_dict['finished'] = True
914 result_dict['status'] = result
xixuan28681fd2016-11-23 11:13:56 -0800915 elif not cros_update_progress.IsProcessAlive(pid):
xixuan28d99072016-10-06 12:24:16 -0700916 result_dict['detailed_error_msg'] = (
917 'Cros_update process terminated midway due to unknown reason. '
918 'Last update status was %s' % result)
xixuan28681fd2016-11-23 11:13:56 -0800919 else:
920 result_dict['status'] = result
921 except IOError as e:
922 if pid and cros_update_progress.IsProcessAlive(pid):
xixuan2a0970a2016-08-10 12:12:44 -0700923 os.killpg(int(pid), signal.SIGKILL)
xixuan52c2fba2016-05-20 17:02:48 -0700924
xixuan28681fd2016-11-23 11:13:56 -0800925 result_dict['detailed_error_msg'] = str(e)
926
927 return json.dumps(result_dict)
xixuan52c2fba2016-05-20 17:02:48 -0700928
929 @cherrypy.expose
David Riley6d5fca02017-10-31 10:35:47 -0700930 def post_au_status(self, status, **kwargs):
931 """Updates the status of an auto-update task.
932
933 Callers will need to POST to this URL with a body of MIME-type
934 "multipart/form-data".
935 The body should include a single argument, 'status', containing the
936 AU status to record.
937
938 Args:
939 status: The updated status.
940 kwargs:
941 host_name: the hostname of the DUT to auto-update.
942 pid: the background process id of cros-update.
943 """
944 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700945 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
946 KEY_ERROR_MSG % 'host_name')
David Riley6d5fca02017-10-31 10:35:47 -0700947
948 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700949 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
950 KEY_ERROR_MSG % 'pid')
David Riley6d5fca02017-10-31 10:35:47 -0700951
952 host_name = kwargs['host_name']
953 pid = kwargs['pid']
David Riley3cea2582017-11-24 22:03:01 -0800954 status = status.rstrip()
955 _Log('Recording status for %s (%s): %s' % (host_name, pid, status))
David Riley6d5fca02017-10-31 10:35:47 -0700956 progress_tracker = cros_update_progress.AUProgress(host_name, pid)
957
David Riley3cea2582017-11-24 22:03:01 -0800958 progress_tracker.WriteStatus(status)
David Riley6d5fca02017-10-31 10:35:47 -0700959
960 return 'True'
961
962 @cherrypy.expose
xixuan52c2fba2016-05-20 17:02:48 -0700963 def handler_cleanup(self, **kwargs):
xixuan3bc974e2016-10-18 17:21:43 -0700964 """Clean track status log and temp directory for CrOS auto-update process.
xixuan52c2fba2016-05-20 17:02:48 -0700965
966 Args:
967 kwargs:
968 host_name: the hostname of the DUT to auto-update.
969 pid: the background process id of cros-update.
970 """
971 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700972 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
973 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700974
975 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700976 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
977 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -0700978
979 host_name = kwargs['host_name']
980 pid = kwargs['pid']
981 cros_update_progress.DelTrackStatusFile(host_name, pid)
xixuan3bc974e2016-10-18 17:21:43 -0700982 cros_update_progress.DelAUTempDirectory(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -0700983
984 @cherrypy.expose
985 def kill_au_proc(self, **kwargs):
986 """Kill CrOS auto-update process using given process id.
987
988 Args:
989 kwargs:
990 host_name: Kill all the CrOS auto-update process of this host.
991
992 Returns:
993 True if all processes are killed properly.
994 """
995 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -0700996 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
997 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -0700998
xixuan447ad9d2017-02-28 14:46:20 -0800999 cur_pid = kwargs.get('pid')
1000
xixuan52c2fba2016-05-20 17:02:48 -07001001 host_name = kwargs['host_name']
xixuan3bc974e2016-10-18 17:21:43 -07001002 track_log_list = cros_update_progress.GetAllTrackStatusFileByHostName(
1003 host_name)
xixuan52c2fba2016-05-20 17:02:48 -07001004 for log in track_log_list:
1005 # The track log's full path is: path/host_name_pid.log
1006 # Use splitext to remove file extension, then parse pid from the
1007 # filename.
Congbin Guo3afae6c2019-08-13 16:29:42 -07001008 pid = os.path.splitext(os.path.basename(log))[0][len(host_name) + 1:]
xixuan447ad9d2017-02-28 14:46:20 -08001009 _clear_process(host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -07001010
xixuan447ad9d2017-02-28 14:46:20 -08001011 if cur_pid:
1012 _clear_process(host_name, cur_pid)
xixuan52c2fba2016-05-20 17:02:48 -07001013
1014 return 'True'
1015
1016 @cherrypy.expose
1017 def collect_cros_au_log(self, **kwargs):
1018 """Collect CrOS auto-update log.
1019
1020 Args:
1021 kwargs:
1022 host_name: the hostname of the DUT to auto-update.
1023 pid: the background process id of cros-update.
1024
1025 Returns:
David Haddock9f459632017-05-11 14:45:46 -07001026 A dictionary containing the execute log file and any hostlog files.
xixuan52c2fba2016-05-20 17:02:48 -07001027 """
1028 if 'host_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001029 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1030 KEY_ERROR_MSG % 'host_name')
xixuan52c2fba2016-05-20 17:02:48 -07001031
1032 if 'pid' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001033 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1034 KEY_ERROR_MSG % 'pid')
xixuan52c2fba2016-05-20 17:02:48 -07001035
1036 host_name = kwargs['host_name']
1037 pid = kwargs['pid']
xixuan3bc974e2016-10-18 17:21:43 -07001038
1039 # Fetch the execute log recorded by cros_update_progress.
xixuan1bbfaba2016-10-13 17:53:22 -07001040 au_log = cros_update_progress.ReadExecuteLogFile(host_name, pid)
1041 cros_update_progress.DelExecuteLogFile(host_name, pid)
David Haddock9f459632017-05-11 14:45:46 -07001042 # Fetch the cros_au host_logs if they exist
1043 au_hostlogs = cros_update_progress.ReadAUHostLogFiles(host_name, pid)
1044 return json.dumps({'cros_au_log': au_log, 'host_logs': au_hostlogs})
xixuan1bbfaba2016-10-13 17:53:22 -07001045
xixuan52c2fba2016-05-20 17:02:48 -07001046 @cherrypy.expose
Dan Shi2f136862016-02-11 15:38:38 -08001047 def locate_file(self, **kwargs):
1048 """Get the path to the given file name.
1049
1050 This method looks up the given file name inside specified build artifacts.
1051 One use case is to help caller to locate an apk file inside a build
1052 artifact. The location of the apk file could be different based on the
1053 branch and target.
1054
1055 Args:
1056 file_name: Name of the file to look for.
1057 artifacts: A list of artifact names to search for the file.
1058
1059 Returns:
1060 Path to the file with the given name. It's relative to the folder for the
1061 build, e.g., DATA/priv-app/sl4a/sl4a.apk
Dan Shi2f136862016-02-11 15:38:38 -08001062 """
1063 dl, _ = _get_downloader_and_factory(kwargs)
1064 try:
Joe Brennan1691f8e2017-03-15 15:53:36 -07001065 file_name = kwargs['file_name']
Dan Shi2f136862016-02-11 15:38:38 -08001066 artifacts = kwargs['artifacts']
1067 except KeyError:
Congbin Guo4132a272019-08-20 12:32:14 -07001068 raise devserver_exceptions.DevServerError(
1069 '`file_name` and `artifacts` are required to search '
1070 'for a file in build artifacts.')
Dan Shi2f136862016-02-11 15:38:38 -08001071 build_path = dl.GetBuildDir()
1072 for artifact in artifacts:
1073 # Get the unzipped folder of the artifact. If it's not defined in
1074 # ARTIFACT_UNZIP_FOLDER_MAP, assume the files are unzipped to the build
1075 # directory directly.
1076 folder = artifact_info.ARTIFACT_UNZIP_FOLDER_MAP.get(artifact, '')
1077 artifact_path = os.path.join(build_path, folder)
1078 for root, _, filenames in os.walk(artifact_path):
Joe Brennan1691f8e2017-03-15 15:53:36 -07001079 if file_name in set([f for f in filenames]):
Dan Shi2f136862016-02-11 15:38:38 -08001080 return os.path.relpath(os.path.join(root, file_name), build_path)
Congbin Guo4132a272019-08-20 12:32:14 -07001081 raise devserver_exceptions.DevServerError(
1082 'File `%s` can not be found in artifacts: %s' % (file_name, artifacts))
Dan Shi2f136862016-02-11 15:38:38 -08001083
1084 @cherrypy.expose
Simran Basi4baad082013-02-14 13:39:18 -08001085 def setup_telemetry(self, **kwargs):
1086 """Extracts and sets up telemetry
1087
1088 This method goes through the telemetry deps packages, and stages them on
1089 the devserver to be used by the drones and the telemetry tests.
1090
1091 Args:
1092 archive_url: Google Storage URL for the build.
1093
1094 Returns:
1095 Path to the source folder for the telemetry codebase once it is staged.
1096 """
Gabe Black3b567202015-09-23 14:07:59 -07001097 dl = _get_downloader(kwargs)
Simran Basi4baad082013-02-14 13:39:18 -08001098
Gabe Black3b567202015-09-23 14:07:59 -07001099 build_path = dl.GetBuildDir()
Simran Basi4baad082013-02-14 13:39:18 -08001100 deps_path = os.path.join(build_path, 'autotest/packages')
1101 telemetry_path = os.path.join(build_path, TELEMETRY_FOLDER)
1102 src_folder = os.path.join(telemetry_path, 'src')
1103
1104 with self._telemetry_lock_dict.lock(telemetry_path):
1105 if os.path.exists(src_folder):
1106 # Telemetry is already fully stage return
1107 return src_folder
1108
1109 common_util.MkDirP(telemetry_path)
1110
1111 # Copy over the required deps tar balls to the telemetry directory.
1112 for dep in TELEMETRY_DEPS:
1113 dep_path = os.path.join(deps_path, dep)
Simran Basi0d078682013-03-22 16:40:04 -07001114 if not os.path.exists(dep_path):
1115 # This dep does not exist (could be new), do not extract it.
1116 continue
Simran Basi4baad082013-02-14 13:39:18 -08001117 try:
1118 common_util.ExtractTarball(dep_path, telemetry_path)
1119 except common_util.CommonUtilError as e:
1120 shutil.rmtree(telemetry_path)
Congbin Guo4132a272019-08-20 12:32:14 -07001121 raise devserver_exceptions.DevServerError(str(e))
Simran Basi4baad082013-02-14 13:39:18 -08001122
1123 # By default all the tarballs extract to test_src but some parts of
1124 # the telemetry code specifically hardcoded to exist inside of 'src'.
1125 test_src = os.path.join(telemetry_path, 'test_src')
1126 try:
1127 shutil.move(test_src, src_folder)
1128 except shutil.Error:
1129 # This can occur if src_folder already exists. Remove and retry move.
1130 shutil.rmtree(src_folder)
Congbin Guo4132a272019-08-20 12:32:14 -07001131 raise devserver_exceptions.DevServerError(
Gabe Black3b567202015-09-23 14:07:59 -07001132 'Failure in telemetry setup for build %s. Appears that the '
1133 'test_src to src move failed.' % dl.GetBuild())
Simran Basi4baad082013-02-14 13:39:18 -08001134
1135 return src_folder
1136
1137 @cherrypy.expose
Chris Sosa76e44b92013-01-31 12:11:38 -08001138 def symbolicate_dump(self, minidump, **kwargs):
Chris Masone816e38c2012-05-02 12:22:36 -07001139 """Symbolicates a minidump using pre-downloaded symbols, returns it.
1140
1141 Callers will need to POST to this URL with a body of MIME-type
1142 "multipart/form-data".
1143 The body should include a single argument, 'minidump', containing the
1144 binary-formatted minidump to symbolicate.
1145
Chris Masone816e38c2012-05-02 12:22:36 -07001146 Args:
Chris Sosa76e44b92013-01-31 12:11:38 -08001147 archive_url: Google Storage URL for the build.
Chris Masone816e38c2012-05-02 12:22:36 -07001148 minidump: The binary minidump file to symbolicate.
1149 """
Chris Sosa76e44b92013-01-31 12:11:38 -08001150 # Ensure the symbols have been staged.
Dan Shif08fe492016-10-04 14:39:25 -07001151 # Try debug.tar.xz first, then debug.tgz
1152 for artifact in (artifact_info.SYMBOLS_ONLY, artifact_info.SYMBOLS):
1153 kwargs['artifacts'] = artifact
1154 dl = _get_downloader(kwargs)
1155
1156 try:
1157 if self.stage(**kwargs) == 'Success':
1158 break
1159 except build_artifact.ArtifactDownloadError:
1160 continue
1161 else:
Congbin Guo4132a272019-08-20 12:32:14 -07001162 raise devserver_exceptions.DevServerError(
1163 'Failed to stage symbols for %s' % dl.DescribeSource())
Chris Sosa76e44b92013-01-31 12:11:38 -08001164
Chris Masone816e38c2012-05-02 12:22:36 -07001165 to_return = ''
1166 with tempfile.NamedTemporaryFile() as local:
1167 while True:
1168 data = minidump.file.read(8192)
1169 if not data:
1170 break
1171 local.write(data)
Chris Sosa76e44b92013-01-31 12:11:38 -08001172
Chris Masone816e38c2012-05-02 12:22:36 -07001173 local.flush()
Chris Sosa76e44b92013-01-31 12:11:38 -08001174
Gabe Black3b567202015-09-23 14:07:59 -07001175 symbols_directory = os.path.join(dl.GetBuildDir(), 'debug', 'breakpad')
Chris Sosa76e44b92013-01-31 12:11:38 -08001176
xixuanab744382017-04-27 10:41:27 -07001177 # The location of minidump_stackwalk is defined in chromeos-admin.
Chris Sosa76e44b92013-01-31 12:11:38 -08001178 stackwalk = subprocess.Popen(
xixuanab744382017-04-27 10:41:27 -07001179 ['/usr/local/bin/minidump_stackwalk', local.name, symbols_directory],
Chris Sosa76e44b92013-01-31 12:11:38 -08001180 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1181
Chris Masone816e38c2012-05-02 12:22:36 -07001182 to_return, error_text = stackwalk.communicate()
1183 if stackwalk.returncode != 0:
Congbin Guo4132a272019-08-20 12:32:14 -07001184 raise devserver_exceptions.DevServerError(
1185 "Can't generate stack trace: %s (rc=%d)" % (error_text,
1186 stackwalk.returncode))
Chris Masone816e38c2012-05-02 12:22:36 -07001187
1188 return to_return
1189
1190 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001191 def latestbuild(self, **kwargs):
Scott Zawalski16954532012-03-20 15:31:36 -04001192 """Return a string representing the latest build for a given target.
1193
1194 Args:
1195 target: The build target, typically a combination of the board and the
1196 type of build e.g. x86-mario-release.
1197 milestone: The milestone to filter builds on. E.g. R16. Optional, if not
1198 provided the latest RXX build will be returned.
Don Garrettf84631a2014-01-07 18:21:26 -08001199
Scott Zawalski16954532012-03-20 15:31:36 -04001200 Returns:
1201 A string representation of the latest build if one exists, i.e.
1202 R19-1993.0.0-a1-b1480.
1203 An empty string if no latest could be found.
1204 """
Don Garrettf84631a2014-01-07 18:21:26 -08001205 if not kwargs:
Scott Zawalski16954532012-03-20 15:31:36 -04001206 return _PrintDocStringAsHTML(self.latestbuild)
1207
Don Garrettf84631a2014-01-07 18:21:26 -08001208 if 'target' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001209 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1210 'Error: target= is required!')
Dan Shi61305df2015-10-26 16:52:35 -07001211
1212 if _is_android_build_request(kwargs):
1213 branch = kwargs.get('branch', None)
1214 target = kwargs.get('target', None)
1215 if not target or not branch:
Congbin Guo4132a272019-08-20 12:32:14 -07001216 raise devserver_exceptions.DevServerError(
xixuan52c2fba2016-05-20 17:02:48 -07001217 'Both target and branch must be specified to query for the latest '
1218 'Android build.')
Dan Shi61305df2015-10-26 16:52:35 -07001219 return android_build.BuildAccessor.GetLatestBuildID(target, branch)
1220
Scott Zawalski16954532012-03-20 15:31:36 -04001221 try:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001222 return common_util.GetLatestBuildVersion(
Don Garrettf84631a2014-01-07 18:21:26 -08001223 updater.static_dir, kwargs['target'],
1224 milestone=kwargs.get('milestone'))
Gilad Arnold17fe03d2012-10-02 10:05:01 -07001225 except common_util.CommonUtilError as errmsg:
Amin Hassani08e42d22019-06-03 00:31:30 -07001226 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1227 str(errmsg))
Scott Zawalski16954532012-03-20 15:31:36 -04001228
1229 @cherrypy.expose
xixuan7efd0002016-04-14 15:34:01 -07001230 def list_suite_controls(self, **kwargs):
1231 """Return a list of contents of all known control files.
1232
1233 Example URL:
1234 To List all control files' content:
1235 http://dev-server/list_suite_controls?suite_name=bvt&
1236 build=daisy_spring-release/R29-4279.0.0
1237
1238 Args:
1239 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
1240 suite_name: List the control files belonging to that suite.
1241
1242 Returns:
Dan Shia1cd6522016-04-18 16:07:21 -07001243 A dictionary of all control files's path to its content for given suite.
xixuan7efd0002016-04-14 15:34:01 -07001244 """
1245 if not kwargs:
1246 return _PrintDocStringAsHTML(self.controlfiles)
1247
1248 if 'build' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001249 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1250 'Error: build= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001251
1252 if 'suite_name' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001253 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
Dan Shia1cd6522016-04-18 16:07:21 -07001254 'Error: suite_name= is required!')
xixuan7efd0002016-04-14 15:34:01 -07001255
1256 control_file_list = [
1257 line.rstrip() for line in common_util.GetControlFileListForSuite(
1258 updater.static_dir, kwargs['build'],
1259 kwargs['suite_name']).splitlines()]
1260
Dan Shia1cd6522016-04-18 16:07:21 -07001261 control_file_content_dict = {}
xixuan7efd0002016-04-14 15:34:01 -07001262 for control_path in control_file_list:
Dan Shia1cd6522016-04-18 16:07:21 -07001263 control_file_content_dict[control_path] = (common_util.GetControlFile(
xixuan7efd0002016-04-14 15:34:01 -07001264 updater.static_dir, kwargs['build'], control_path))
1265
Dan Shia1cd6522016-04-18 16:07:21 -07001266 return json.dumps(control_file_content_dict)
xixuan7efd0002016-04-14 15:34:01 -07001267
1268 @cherrypy.expose
Don Garrettf84631a2014-01-07 18:21:26 -08001269 def controlfiles(self, **kwargs):
Scott Zawalski4647ce62012-01-03 17:17:28 -05001270 """Return a control file or a list of all known control files.
1271
1272 Example URL:
1273 To List all control files:
beepsbd337242013-07-09 22:44:06 -07001274 http://dev-server/controlfiles?suite_name=&build=daisy_spring-release/R29-4279.0.0
1275 To List all control files for, say, the bvt suite:
1276 http://dev-server/controlfiles?suite_name=bvt&build=daisy_spring-release/R29-4279.0.0
Scott Zawalski4647ce62012-01-03 17:17:28 -05001277 To return the contents of a path:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001278 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 -05001279
1280 Args:
Scott Zawalski84a39c92012-01-13 15:12:42 -05001281 build: The build i.e. x86-alex-release/R18-1514.0.0-a1-b1450.
Scott Zawalski4647ce62012-01-03 17:17:28 -05001282 control_path: If you want the contents of a control file set this
1283 to the path. E.g. client/site_tests/sleeptest/control
1284 Optional, if not provided return a list of control files is returned.
beepsbd337242013-07-09 22:44:06 -07001285 suite_name: If control_path is not specified but a suite_name is
1286 specified, list the control files belonging to that suite instead of
1287 all control files. The empty string for suite_name will list all control
1288 files for the build.
Don Garrettf84631a2014-01-07 18:21:26 -08001289
Scott Zawalski4647ce62012-01-03 17:17:28 -05001290 Returns:
1291 Contents of a control file if control_path is provided.
1292 A list of control files if no control_path is provided.
1293 """
Don Garrettf84631a2014-01-07 18:21:26 -08001294 if not kwargs:
Scott Zawalski4647ce62012-01-03 17:17:28 -05001295 return _PrintDocStringAsHTML(self.controlfiles)
1296
Don Garrettf84631a2014-01-07 18:21:26 -08001297 if 'build' not in kwargs:
Amin Hassani08e42d22019-06-03 00:31:30 -07001298 raise common_util.DevServerHTTPError(httplib.INTERNAL_SERVER_ERROR,
1299 'Error: build= is required!')
Scott Zawalski4647ce62012-01-03 17:17:28 -05001300
Don Garrettf84631a2014-01-07 18:21:26 -08001301 if 'control_path' not in kwargs:
1302 if 'suite_name' in kwargs and kwargs['suite_name']:
beepsbd337242013-07-09 22:44:06 -07001303 return common_util.GetControlFileListForSuite(
Don Garrettf84631a2014-01-07 18:21:26 -08001304 updater.static_dir, kwargs['build'], kwargs['suite_name'])
beepsbd337242013-07-09 22:44:06 -07001305 else:
1306 return common_util.GetControlFileList(
Don Garrettf84631a2014-01-07 18:21:26 -08001307 updater.static_dir, kwargs['build'])
Scott Zawalski4647ce62012-01-03 17:17:28 -05001308 else:
Gilad Arnoldc65330c2012-09-20 15:17:48 -07001309 return common_util.GetControlFile(
Don Garrettf84631a2014-01-07 18:21:26 -08001310 updater.static_dir, kwargs['build'], kwargs['control_path'])
Frank Farzan40160872011-12-12 18:39:18 -08001311
1312 @cherrypy.expose
Simran Basi99e63c02014-05-20 10:39:52 -07001313 def xbuddy_translate(self, *args, **kwargs):
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001314 """Translates an xBuddy path to a real path to artifact if it exists.
1315
1316 Args:
Simran Basi99e63c02014-05-20 10:39:52 -07001317 args: An xbuddy path in the form of {local|remote}/build_id/artifact.
1318 Local searches the devserver's static directory. Remote searches a
1319 Google Storage image archive.
1320
1321 Kwargs:
1322 image_dir: Google Storage image archive to search in if requesting a
1323 remote artifact. If none uses the default bucket.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001324
1325 Returns:
Simran Basi99e63c02014-05-20 10:39:52 -07001326 String in the format of build_id/artifact as stored on the local server
1327 or in Google Storage.
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001328 """
Simran Basi99e63c02014-05-20 10:39:52 -07001329 build_id, filename = self._xbuddy.Translate(
Gabe Black3b567202015-09-23 14:07:59 -07001330 args, image_dir=kwargs.get('image_dir'))
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001331 response = os.path.join(build_id, filename)
1332 _Log('Path translation requested, returning: %s', response)
1333 return response
1334
1335 @cherrypy.expose
joycheneaf4cfc2013-07-02 08:38:57 -07001336 def xbuddy(self, *args, **kwargs):
1337 """The full xBuddy call, returns resource specified by path_parts.
joychen3cb228e2013-06-12 12:13:13 -07001338
1339 Args:
joycheneaf4cfc2013-07-02 08:38:57 -07001340 path_parts: the path following xbuddy/ in the call url is split into the
joychen121fc9b2013-08-02 14:30:30 -07001341 components of the path. The path can be understood as
1342 "{local|remote}/build_id/artifact" where build_id is composed of
1343 "board/version."
joycheneaf4cfc2013-07-02 08:38:57 -07001344
joychen121fc9b2013-08-02 14:30:30 -07001345 The first path element is optional, and can be "remote" or "local"
1346 If local (the default), devserver will not attempt to access Google
1347 Storage, and will only search the static directory for the files.
1348 If remote, devserver will try to obtain the artifact off GS if it's
1349 not found locally.
1350 The board is the familiar board name, optionally suffixed.
1351 The version can be the google storage version number, and may also be
1352 any of a number of xBuddy defined version aliases that will be
1353 translated into the latest built image that fits the description.
1354 Defaults to latest.
1355 The artifact is one of a number of image or artifact aliases used by
1356 xbuddy, defined in xbuddy:ALIASES. Defaults to test.
joycheneaf4cfc2013-07-02 08:38:57 -07001357
1358 Kwargs:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001359 for_update: {true|false}
Amin Hassanie9ffb862019-09-25 17:10:40 -07001360 if true, prepares the update payloads for the image,
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001361 and returns the update uri to pass to the
1362 update_engine_client.
joychen3cb228e2013-06-12 12:13:13 -07001363 return_dir: {true|false}
1364 if set to true, returns the url to the update.gz
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001365 relative_path: {true|false}
1366 if set to true, returns the relative path to the payload
1367 directory from static_dir.
joychen3cb228e2013-06-12 12:13:13 -07001368 Example URL:
joycheneaf4cfc2013-07-02 08:38:57 -07001369 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test
joychen3cb228e2013-06-12 12:13:13 -07001370 or
joycheneaf4cfc2013-07-02 08:38:57 -07001371 http://host:port/xbuddy/x86-generic/R26-4000.0.0/test?return_dir=true
joychen3cb228e2013-06-12 12:13:13 -07001372
1373 Returns:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001374 If |for_update|, returns a redirect to the image or update file
1375 on the devserver. E.g.,
1376 http://host:port/static/archive/x86-generic-release/R26-4000.0.0/
1377 chromium-test-image.bin
1378 If |return_dir|, return a uri to the folder where the artifact is. E.g.,
1379 http://host:port/static/x86-generic-release/R26-4000.0.0/
1380 If |relative_path| is true, return a relative path the folder where the
1381 payloads are. E.g.,
1382 archive/x86-generic-release/R26-4000.0.0
joychen3cb228e2013-06-12 12:13:13 -07001383 """
Chris Sosa75490802013-09-30 17:21:45 -07001384 boolean_string = kwargs.get('for_update')
1385 for_update = xbuddy.XBuddy.ParseBoolean(boolean_string)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001386 boolean_string = kwargs.get('return_dir')
1387 return_dir = xbuddy.XBuddy.ParseBoolean(boolean_string)
1388 boolean_string = kwargs.get('relative_path')
1389 relative_path = xbuddy.XBuddy.ParseBoolean(boolean_string)
joychen121fc9b2013-08-02 14:30:30 -07001390
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001391 if return_dir and relative_path:
Chris Sosa4b951602014-04-09 20:26:07 -07001392 raise common_util.DevServerHTTPError(
Amin Hassani08e42d22019-06-03 00:31:30 -07001393 httplib.INTERNAL_SERVER_ERROR,
1394 'Cannot specify both return_dir and relative_path')
Chris Sosa75490802013-09-30 17:21:45 -07001395
1396 # For updates, we optimize downloading of test images.
1397 file_name = None
1398 build_id = None
1399 if for_update:
1400 try:
Yu-Ju Hong1bdb7a92014-04-10 16:02:11 -07001401 build_id = self._xbuddy.StageTestArtifactsForUpdate(args)
Chris Sosa75490802013-09-30 17:21:45 -07001402 except build_artifact.ArtifactDownloadError:
1403 build_id = None
1404
1405 if not build_id:
1406 build_id, file_name = self._xbuddy.Get(args)
1407
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001408 if for_update:
Amin Hassanie9ffb862019-09-25 17:10:40 -07001409 _Log('Payloads requested.')
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001410 # Forces payload to be in cache and symlinked into build_id dir.
Amin Hassanie9ffb862019-09-25 17:10:40 -07001411 updater.GetUpdateForLabel(build_id)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001412
1413 response = None
1414 if return_dir:
1415 response = os.path.join(cherrypy.request.base, 'static', build_id)
1416 _Log('Directory requested, returning: %s', response)
1417 elif relative_path:
1418 response = build_id
1419 _Log('Relative path requested, returning: %s', response)
1420 elif for_update:
1421 response = os.path.join(cherrypy.request.base, 'update', build_id)
1422 _Log('Update URI requested, returning: %s', response)
joychen3cb228e2013-06-12 12:13:13 -07001423 else:
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001424 # Redirect to download the payload if no kwargs are set.
joychen121fc9b2013-08-02 14:30:30 -07001425 build_id = '/' + os.path.join('static', build_id, file_name)
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001426 _Log('Payload requested, returning: %s', build_id)
joychen121fc9b2013-08-02 14:30:30 -07001427 raise cherrypy.HTTPRedirect(build_id, 302)
joychen3cb228e2013-06-12 12:13:13 -07001428
Yu-Ju Hong51495eb2013-12-12 17:08:43 -08001429 return response
1430
joychen3cb228e2013-06-12 12:13:13 -07001431 @cherrypy.expose
1432 def xbuddy_list(self):
1433 """Lists the currently available images & time since last access.
1434
Gilad Arnold452fd272014-02-04 11:09:28 -08001435 Returns:
1436 A string representation of a list of tuples [(build_id, time since last
1437 access),...]
joychen3cb228e2013-06-12 12:13:13 -07001438 """
1439 return self._xbuddy.List()
1440
1441 @cherrypy.expose
1442 def xbuddy_capacity(self):
Gilad Arnold452fd272014-02-04 11:09:28 -08001443 """Returns the number of images cached by xBuddy."""
joychen3cb228e2013-06-12 12:13:13 -07001444 return self._xbuddy.Capacity()
1445
1446 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001447 def index(self):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001448 """Presents a welcome message and documentation links."""
Congbin Guo6bc32182019-08-20 17:54:30 -07001449 html_template = (
1450 'Welcome to the Dev Server!<br>\n'
1451 '<br>\n'
1452 'Here are the available methods, click for documentation:<br>\n'
1453 '<br>\n'
1454 '%s')
1455
1456 exposed_methods = []
1457 for app in cherrypy.tree.apps.values():
1458 exposed_methods += _FindExposedMethods(
1459 app.root, app.script_name.lstrip('/'),
1460 unlisted=self._UNLISTED_METHODS)
1461
1462 return html_template % '<br>\n'.join(
1463 ['<a href=doc/%s>%s</a>' % (name, name)
1464 for name in sorted(exposed_methods)])
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001465
1466 @cherrypy.expose
1467 def doc(self, *args):
1468 """Shows the documentation for available methods / URLs.
1469
Amin Hassani08e42d22019-06-03 00:31:30 -07001470 Examples:
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001471 http://myhost/doc/update
1472 """
Gilad Arnoldd5ebaaa2012-10-02 11:52:38 -07001473 name = '/'.join(args)
Congbin Guo6bc32182019-08-20 17:54:30 -07001474 method = _GetExposedMethod(name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001475 if not method:
Congbin Guo4132a272019-08-20 12:32:14 -07001476 raise devserver_exceptions.DevServerError(
1477 "No exposed method named `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001478 if not method.__doc__:
Congbin Guo4132a272019-08-20 12:32:14 -07001479 raise devserver_exceptions.DevServerError(
1480 "No documentation for exposed method `%s'" % name)
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001481 return '<pre>\n%s</pre>' % method.__doc__
Chris Sosa7c931362010-10-11 19:49:01 -07001482
Dale Curtisc9aaf3a2011-08-09 15:47:40 -07001483 @cherrypy.expose
Chris Sosa7c931362010-10-11 19:49:01 -07001484 def update(self, *args):
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001485 """Handles an update check from a Chrome OS client.
1486
1487 The HTTP request should contain the standard Omaha-style XML blob. The URL
1488 line may contain an additional intermediate path to the update payload.
1489
joychen121fc9b2013-08-02 14:30:30 -07001490 This request can be handled in one of 4 ways, depending on the devsever
1491 settings and intermediate path.
joychenb0dfe552013-07-30 10:02:06 -07001492
Amin Hassanie9ffb862019-09-25 17:10:40 -07001493 1. No intermediate path. DEPRECATED
joychen121fc9b2013-08-02 14:30:30 -07001494
1495 2. Path explicitly invokes XBuddy
1496 If there is a path given, it can explicitly invoke xbuddy by prefixing it
1497 with 'xbuddy'. This path is then used to acquire an image binary for the
1498 devserver to generate an update payload from. Devserver then serves this
1499 payload.
1500
1501 3. Path is left for the devserver to interpret.
1502 If the path given doesn't explicitly invoke xbuddy, devserver will attempt
1503 to generate a payload from the test image in that directory and serve it.
1504
joychen121fc9b2013-08-02 14:30:30 -07001505 Examples:
joychen121fc9b2013-08-02 14:30:30 -07001506 2. Explicitly invoke xbuddy
1507 update_engine_client --omaha_url=
1508 http://myhost/update/xbuddy/remote/board/version/dev
1509 This would go to GS to download the dev image for the board, from which
1510 the devserver would generate a payload to serve.
1511
1512 3. Give a path for devserver to interpret
1513 update_engine_client --omaha_url=http://myhost/update/some/random/path
1514 This would attempt, in order to:
1515 a) Generate an update from a test image binary if found in
1516 static_dir/some/random/path.
1517 b) Serve an update payload found in static_dir/some/random/path.
1518 c) Hope that some/random/path takes the form "board/version" and
1519 and attempt to download an update payload for that board/version
1520 from GS.
Gilad Arnoldf8f769f2012-09-24 08:43:01 -07001521 """
joychen121fc9b2013-08-02 14:30:30 -07001522 label = '/'.join(args)
Gilad Arnold286a0062012-01-12 13:47:02 -08001523 body_length = int(cherrypy.request.headers.get('Content-Length', 0))
Chris Sosa7c931362010-10-11 19:49:01 -07001524 data = cherrypy.request.rfile.read(body_length)
Chris Sosa7c931362010-10-11 19:49:01 -07001525
joychen121fc9b2013-08-02 14:30:30 -07001526 return updater.HandleUpdatePing(data, label)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001527
Dan Shif5ce2de2013-04-25 16:06:32 -07001528
Chris Sosadbc20082012-12-10 13:39:11 -08001529def _CleanCache(cache_dir, wipe):
1530 """Wipes any excess cached items in the cache_dir.
1531
1532 Args:
1533 cache_dir: the directory we are wiping from.
1534 wipe: If True, wipe all the contents -- not just the excess.
1535 """
1536 if wipe:
1537 # Clear the cache and exit on error.
1538 cmd = 'rm -rf %s/*' % cache_dir
1539 if os.system(cmd) != 0:
1540 _Log('Failed to clear the cache with %s' % cmd)
1541 sys.exit(1)
1542 else:
1543 # Clear all but the last N cached updates
1544 cmd = ('cd %s; ls -tr | head --lines=-%d | xargs rm -rf' %
1545 (cache_dir, CACHED_ENTRIES))
1546 if os.system(cmd) != 0:
1547 _Log('Failed to clean up old delta cache files with %s' % cmd)
1548 sys.exit(1)
1549
1550
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001551def _AddTestingOptions(parser):
1552 group = optparse.OptionGroup(
1553 parser, 'Advanced Testing Options', 'These are used by test scripts and '
1554 'developers writing integration tests utilizing the devserver. They are '
1555 'not intended to be really used outside the scope of someone '
1556 'knowledgable about the test.')
1557 group.add_option('--exit',
1558 action='store_true',
Amin Hassanie9ffb862019-09-25 17:10:40 -07001559 help='do not start the server (yet clear cache)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001560 group.add_option('--host_log',
1561 action='store_true', default=False,
1562 help='record history of host update events (/api/hostlog)')
1563 group.add_option('--max_updates',
Gabe Black3b567202015-09-23 14:07:59 -07001564 metavar='NUM', default=-1, type='int',
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001565 help='maximum number of update checks handled positively '
1566 '(default: unlimited)')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001567 group.add_option('--proxy_port',
1568 metavar='PORT', default=None, type='int',
1569 help='port to have the client connect to -- basically the '
1570 'devserver lies to the update to tell it to get the payload '
1571 'from a different port that will proxy the request back to '
1572 'the devserver. The proxy must be managed outside the '
1573 'devserver.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001574 parser.add_option_group(group)
1575
1576
1577def _AddUpdateOptions(parser):
1578 group = optparse.OptionGroup(
1579 parser, 'Autoupdate Options', 'These options can be used to change '
Amin Hassanie9ffb862019-09-25 17:10:40 -07001580 'how the devserver serve update payloads. Please '
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001581 'note that all of these option affect how a payload is generated and so '
1582 'do not work in archive-only mode.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001583 group.add_option('--critical_update',
1584 action='store_true', default=False,
1585 help='Present update payload as critical')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001586 group.add_option('--payload',
1587 metavar='PATH',
1588 help='use the update payload from specified directory '
1589 '(update.gz).')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001590 parser.add_option_group(group)
1591
1592
1593def _AddProductionOptions(parser):
1594 group = optparse.OptionGroup(
1595 parser, 'Advanced Server Options', 'These options can be used to changed '
1596 'for advanced server behavior.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001597 group.add_option('--clear_cache',
1598 action='store_true', default=False,
1599 help='At startup, removes all cached entries from the'
1600 'devserver\'s cache.')
1601 group.add_option('--logfile',
1602 metavar='PATH',
1603 help='log output to this file instead of stdout')
Chris Sosa855b8932013-08-21 13:24:55 -07001604 group.add_option('--pidfile',
1605 metavar='PATH',
1606 help='path to output a pid file for the server.')
Gilad Arnold11fbef42014-02-10 11:04:13 -08001607 group.add_option('--portfile',
1608 metavar='PATH',
1609 help='path to output the port number being served on.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001610 group.add_option('--production',
1611 action='store_true', default=False,
1612 help='have the devserver use production values when '
1613 'starting up. This includes using more threads and '
1614 'performing less logging.')
1615 parser.add_option_group(group)
1616
1617
Paul Hobbsef4e0702016-06-27 17:01:42 -07001618def MakeLogHandler(logfile):
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001619 """Create a LogHandler instance used to log all messages."""
1620 hdlr_cls = handlers.TimedRotatingFileHandler
1621 hdlr = hdlr_cls(logfile, when=_LOG_ROTATION_TIME,
xixuan3d48bff2017-01-30 19:00:09 -08001622 interval=_LOG_ROTATION_INTERVAL,
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001623 backupCount=_LOG_ROTATION_BACKUP)
Chris Sosa855b8932013-08-21 13:24:55 -07001624 hdlr.setFormatter(cplogging.logfmt)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001625 return hdlr
1626
1627
Chris Sosacde6bf42012-05-31 18:36:39 -07001628def main():
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001629 usage = '\n\n'.join(['usage: %prog [options]', __doc__])
Gilad Arnold286a0062012-01-12 13:47:02 -08001630 parser = optparse.OptionParser(usage=usage)
joychened64b222013-06-21 16:39:34 -07001631
1632 # get directory that the devserver is run from
1633 devserver_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
joychen84d13772013-08-06 09:17:23 -07001634 default_static_dir = '%s/static' % devserver_dir
joychened64b222013-06-21 16:39:34 -07001635 parser.add_option('--static_dir',
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001636 metavar='PATH',
joychen84d13772013-08-06 09:17:23 -07001637 default=default_static_dir,
joychened64b222013-06-21 16:39:34 -07001638 help='writable static directory')
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001639 parser.add_option('--port',
1640 default=8080, type='int',
Gilad Arnoldaf696d12014-02-14 13:13:28 -08001641 help=('port for the dev server to use; if zero, binds to '
1642 'an arbitrary available port (default: 8080)'))
Gilad Arnold9714d9b2012-10-04 10:09:42 -07001643 parser.add_option('-t', '--test_image',
1644 action='store_true',
joychen121fc9b2013-08-02 14:30:30 -07001645 help='Deprecated.')
joychen5260b9a2013-07-16 14:48:01 -07001646 parser.add_option('-x', '--xbuddy_manage_builds',
1647 action='store_true',
1648 default=False,
1649 help='If set, allow xbuddy to manage images in'
1650 'build/images.')
Dan Shi72b16132015-10-08 12:10:33 -07001651 parser.add_option('-a', '--android_build_credential',
1652 default=None,
1653 help='Path to a json file which contains the credential '
1654 'needed to access Android builds.')
Chris Sosa3ae4dc12013-03-29 11:47:00 -07001655 _AddProductionOptions(parser)
1656 _AddUpdateOptions(parser)
1657 _AddTestingOptions(parser)
Chris Sosa7c931362010-10-11 19:49:01 -07001658 (options, _) = parser.parse_args()
rtc@google.com21a5ca32009-11-04 18:23:23 +00001659
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001660 # Handle options that must be set globally in cherrypy. Do this
1661 # work up front, because calls to _Log() below depend on this
1662 # initialization.
1663 if options.production:
1664 cherrypy.config.update({'environment': 'production'})
1665 if not options.logfile:
1666 cherrypy.config.update({'log.screen': True})
1667 else:
1668 cherrypy.config.update({'log.error_file': '',
1669 'log.access_file': ''})
Paul Hobbsef4e0702016-06-27 17:01:42 -07001670 hdlr = MakeLogHandler(options.logfile)
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001671 # Pylint can't seem to process these two calls properly
1672 # pylint: disable=E1101
1673 cherrypy.log.access_log.addHandler(hdlr)
1674 cherrypy.log.error_log.addHandler(hdlr)
1675 # pylint: enable=E1101
1676
joychened64b222013-06-21 16:39:34 -07001677 # set static_dir, from which everything will be served
joychen84d13772013-08-06 09:17:23 -07001678 options.static_dir = os.path.realpath(options.static_dir)
Chris Sosa0356d3b2010-09-16 15:46:22 -07001679
joychened64b222013-06-21 16:39:34 -07001680 cache_dir = os.path.join(options.static_dir, 'cache')
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001681 # If our devserver is only supposed to serve payloads, we shouldn't be
1682 # mucking with the cache at all. If the devserver hadn't previously
1683 # generated a cache and is expected, the caller is using it wrong.
joychen7c2054a2013-07-25 11:14:07 -07001684 if os.path.exists(cache_dir):
Chris Sosadbc20082012-12-10 13:39:11 -08001685 _CleanCache(cache_dir, options.clear_cache)
Chris Sosa6b8c3742011-01-31 12:12:17 -08001686 else:
1687 os.makedirs(cache_dir)
Don Garrettf90edf02010-11-16 17:36:14 -08001688
Chris Sosadbc20082012-12-10 13:39:11 -08001689 _Log('Using cache directory %s' % cache_dir)
joychened64b222013-06-21 16:39:34 -07001690 _Log('Serving from %s' % options.static_dir)
rtc@google.com21a5ca32009-11-04 18:23:23 +00001691
Amin Hassanie9ffb862019-09-25 17:10:40 -07001692 _xbuddy = xbuddy.XBuddy(manage_builds=options.xbuddy_manage_builds,
joychen121fc9b2013-08-02 14:30:30 -07001693 static_dir=options.static_dir)
Chris Sosa75490802013-09-30 17:21:45 -07001694 if options.clear_cache and options.xbuddy_manage_builds:
1695 _xbuddy.CleanCache()
joychen121fc9b2013-08-02 14:30:30 -07001696
Chris Sosa6a3697f2013-01-29 16:44:43 -08001697 # We allow global use here to share with cherrypy classes.
1698 # pylint: disable=W0603
Chris Sosacde6bf42012-05-31 18:36:39 -07001699 global updater
Andrew de los Reyes52620802010-04-12 13:40:07 -07001700 updater = autoupdate.Autoupdate(
joychen121fc9b2013-08-02 14:30:30 -07001701 _xbuddy,
joychened64b222013-06-21 16:39:34 -07001702 static_dir=options.static_dir,
Gilad Arnold0c9c8602012-10-02 23:58:58 -07001703 payload_path=options.payload,
Don Garrett0ad09372010-12-06 16:20:30 -08001704 proxy_port=options.proxy_port,
Satoru Takabayashid733cbe2011-11-15 09:36:32 -08001705 critical_update=options.critical_update,
Gilad Arnolda564b4b2012-10-04 10:32:44 -07001706 max_updates=options.max_updates,
Gilad Arnold8318eac2012-10-04 12:52:23 -07001707 host_log=options.host_log,
Chris Sosa0f1ec842011-02-14 16:33:22 -08001708 )
Chris Sosa7c931362010-10-11 19:49:01 -07001709
J. Richard Barnette3d977b82013-04-23 11:05:19 -07001710 if options.exit:
1711 return
Chris Sosa2f1c41e2012-07-10 14:32:33 -07001712
joychen3cb228e2013-06-12 12:13:13 -07001713 dev_server = DevServerRoot(_xbuddy)
Congbin Guo3afae6c2019-08-13 16:29:42 -07001714 health_checker_app = health_checker.Root(dev_server, options.static_dir)
joychen3cb228e2013-06-12 12:13:13 -07001715
Gilad Arnold11fbef42014-02-10 11:04:13 -08001716 # Patch CherryPy to support binding to any available port (--port=0).
1717 cherrypy_ext.ZeroPortPatcher.DoPatch(cherrypy)
1718
Chris Sosa855b8932013-08-21 13:24:55 -07001719 if options.pidfile:
1720 plugins.PIDFile(cherrypy.engine, options.pidfile).subscribe()
1721
Gilad Arnold11fbef42014-02-10 11:04:13 -08001722 if options.portfile:
1723 cherrypy_ext.PortFile(cherrypy.engine, options.portfile).subscribe()
1724
Dan Shiafd5c6c2016-01-07 10:27:03 -08001725 if (options.android_build_credential and
1726 os.path.exists(options.android_build_credential)):
1727 try:
1728 with open(options.android_build_credential) as f:
1729 android_build.BuildAccessor.credential_info = json.load(f)
1730 except ValueError as e:
1731 _Log('Failed to load the android build credential: %s. Error: %s.' %
1732 (options.android_build_credential, e))
Congbin Guo3afae6c2019-08-13 16:29:42 -07001733
1734 cherrypy.tree.mount(health_checker_app, '/check_health',
1735 config=health_checker.get_config())
joychen3cb228e2013-06-12 12:13:13 -07001736 cherrypy.quickstart(dev_server, config=_GetConfig(options))
Chris Sosacde6bf42012-05-31 18:36:39 -07001737
1738
1739if __name__ == '__main__':
1740 main()