dev_server.py revision 2e7b2ea7850bdd3ca92a5ef47f11d91a08c2b3db
14f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 2e83b0cadc67882c1ba7f430d16dab80c9b3a0228Dan Handley# Use of this source code is governed by a BSD-style license that can be 34f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# found in the LICENSE file. 44f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 54f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptafrom distutils import version 64f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptaimport json 74f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptaimport logging 84f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptaimport os 94f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptaimport urllib2 104f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptaimport HTMLParser 114f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptaimport cStringIO 124f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptaimport re 134f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptaimport sys 144f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 154f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptafrom autotest_lib.client.common_lib import global_config 164f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptafrom autotest_lib.client.common_lib import utils 174f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptafrom autotest_lib.client.common_lib.cros import retry 184f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptafrom autotest_lib.client.bin import utils as site_utils 194f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptafrom autotest_lib.site_utils.graphite import stats 204f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# TODO(cmasone): redo this class using requests module; http://crosbug.com/30107 214f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 224f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 234f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin GuptaCONFIG = global_config.global_config 244f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# This file is generated at build time and specifies, per suite and per test, 254f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# the DEPENDENCIES list specified in each control file. It's a dict of dicts: 264f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# {'bvt': {'/path/to/autotest/control/site_tests/test1/control': ['dep1']} 274f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# 'suite': {'/path/to/autotest/control/site_tests/test2/control': ['dep2']} 284f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# 'power': {'/path/to/autotest/control/site_tests/test1/control': ['dep1'], 294f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# '/path/to/autotest/control/site_tests/test3/control': ['dep3']} 304f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# } 314f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin GuptaDEPENDENCIES_FILE = 'test_suites/dependency_info' 324f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# Number of seconds for caller to poll devserver's is_staged call to check if 334f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# artifacts are staged. 344f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta_ARTIFACT_STAGE_POLLING_INTERVAL = 5 354f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# Artifacts that should be staged when client calls devserver RPC to stage an 364f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# image. 374f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta_ARTIFACTS_TO_BE_STAGED_FOR_IMAGE = 'full_payload,test_suites,stateful' 384f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# Artifacts that should be staged when client calls devserver RPC to stage an 394f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta# image with autotest artifact. 408d69a03f6a7db3c437b7cfdd15402627277d8cb4Sandrine Bailleux_ARTIFACTS_TO_BE_STAGED_FOR_IMAGE_WITH_AUTOTEST = ('full_payload,test_suites,' 414f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 'autotest,stateful') 424f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 434f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 444f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptaclass MarkupStripper(HTMLParser.HTMLParser): 454f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """HTML parser that strips HTML tags, coded characters like & 464f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 474f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta Works by, basically, not doing anything for any tags, and only recording 484f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta the content of text nodes in an internal data structure. 494f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """ 504f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta def __init__(self): 514f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta self.reset() 524f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta self.fed = [] 534f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 544f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 554f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta def handle_data(self, d): 564f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """Consume content of text nodes, store it away.""" 574f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta self.fed.append(d) 584f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 594f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 604f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta def get_data(self): 614f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """Concatenate and return all stored data.""" 624f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return ''.join(self.fed) 634f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 644f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 654f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptadef _get_image_storage_server(): 664f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return CONFIG.get_config_value('CROS', 'image_storage_server', type=str) 674f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 684f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 694f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptadef _get_dev_server_list(): 704f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return CONFIG.get_config_value('CROS', 'dev_server', type=list, default=[]) 714f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 724f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 734f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptadef _get_crash_server_list(): 744f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return CONFIG.get_config_value('CROS', 'crash_server', type=list, 754f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta default=[]) 764f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 774f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 784f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptadef remote_devserver_call(timeout_min=30): 794f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """A decorator to use with remote devserver calls. 804f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 814f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta This decorator converts urllib2.HTTPErrors into DevServerExceptions with 824f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta any embedded error info converted into plain text. 834f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta The method retries on urllib2.URLError to avoid devserver flakiness. 844f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """ 854f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta #pylint: disable=C0111 864f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta def inner_decorator(method): 874f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 884f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @retry.retry(urllib2.URLError, timeout_min=timeout_min) 894f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta def wrapper(*args, **kwargs): 904f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """This wrapper actually catches the HTTPError.""" 914f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta try: 924f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return method(*args, **kwargs) 934f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta except urllib2.HTTPError as e: 944f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta error_markup = e.read() 954f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta strip = MarkupStripper() 964f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta try: 974f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta strip.feed(error_markup.decode('utf_32')) 984f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta except UnicodeDecodeError: 994f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta strip.feed(error_markup) 1004f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta raise DevServerException(strip.get_data()) 1014f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1024f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return wrapper 1034f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1044f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return inner_decorator 1054f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1064f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1074f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptaclass DevServerException(Exception): 1084f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """Raised when the dev server returns a non-200 HTTP response.""" 1094f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta pass 1104f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1114f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1124f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Guptaclass DevServer(object): 1134f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """Base class for all DevServer-like server stubs. 1144f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1154f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta This is the base class for interacting with all Dev Server-like servers. 1164f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta A caller should instantiate a sub-class of DevServer with: 1174f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1184f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta host = SubClassServer.resolve(build) 1194f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta server = SubClassServer(host) 1204f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """ 1214f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta _MIN_FREE_DISK_SPACE_GB = 20 1224f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1234f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta def __init__(self, devserver): 1244f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta self._devserver = devserver 1254f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1264f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1274f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta def url(self): 1284f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """Returns the url for this devserver.""" 1294f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return self._devserver 1304f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1314f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1324f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @staticmethod 1334f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta def devserver_healthy(devserver, timeout_min=0.1): 1344f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """Returns True if the |devserver| is healthy to stage build. 1354f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1364f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @param devserver: url of the devserver. 1374f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @param timeout_min: How long to wait in minutes before deciding the 1384f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta the devserver is not up (float). 1394f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """ 1404f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta server_name = re.sub(r':\d+$', '', devserver.lstrip('http://')) 1414f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta # statsd treats |.| as path separator. 1424f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta server_name = server_name.replace('.', '_') 1434f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta call = DevServer._build_call(devserver, 'check_health') 1444f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1454f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @remote_devserver_call(timeout_min=timeout_min) 1464f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta def make_call(): 1474f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """Inner method that makes the call.""" 1484f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return utils.urlopen_socket_timeout(call, 1494f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta timeout=timeout_min*60).read() 1504f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1514f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta try: 1524f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta result_dict = json.load(cStringIO.StringIO(make_call())) 1534f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta free_disk = result_dict['free_disk'] 1544f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta stats.Gauge(server_name).send('free_disk', free_disk) 1554f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1564f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta skip_devserver_health_check = CONFIG.get_config_value('CROS', 1574f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 'skip_devserver_health_check', 1584f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta type=bool) 1594f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta if skip_devserver_health_check: 1604f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta logging.debug('devserver health check is skipped.') 1614f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta elif (free_disk < DevServer._MIN_FREE_DISK_SPACE_GB): 1624f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta logging.error('Devserver check_health failed. Free disk space ' 1634f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 'is low. Only %dGB is available.', free_disk) 1644f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta stats.Counter(server_name +'.devserver_not_healthy').increment() 1654f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return False 1664f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1674f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta # This counter indicates the load of a devserver. By comparing the 1684f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta # value of this counter for all devservers, we can evaluate the 1694f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta # load balancing across all devservers. 1704f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta stats.Counter(server_name + '.devserver_healthy').increment() 1714f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return True 1724f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta except Exception as e: 1734f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta logging.error('Devserver call failed: "%s", timeout: %s seconds,' 1744f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta ' Error: %s', call, timeout_min*60, str(e)) 1754f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta stats.Counter(server_name + '.devserver_not_healthy').increment() 1764f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return False 1774f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1784f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1794f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @staticmethod 1804f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta def _build_call(host, method, **kwargs): 1814f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """Build a URL to |host| that calls |method|, passing |kwargs|. 1824f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1834f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta Builds a URL that calls |method| on the dev server defined by |host|, 1844f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta passing a set of key/value pairs built from the dict |kwargs|. 1854f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1864f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @param host: a string that is the host basename e.g. http://server:90. 1874f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @param method: the dev server method to call. 1884f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @param kwargs: a dict mapping arg names to arg values. 1894f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @return the URL string. 1904f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """ 1914f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta argstr = '&'.join(map(lambda x: "%s=%s" % x, kwargs.iteritems())) 1924f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return "%(host)s/%(method)s?%(argstr)s" % dict( 1934f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta host=host, method=method, argstr=argstr) 1944f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1954f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1964f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta def build_call(self, method, **kwargs): 1974f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """Builds a devserver RPC string that can be invoked using urllib.open. 1984f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 1994f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @param method: remote devserver method to call. 2004f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta """ 2014f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta return self._build_call(self._devserver, method, **kwargs) 2024f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 2034f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta 2044f6ad66ae9fcc8bcb3b0fcee10b7ab1ffcaf1a5Achin Gupta @classmethod 205 def build_all_calls(cls, method, **kwargs): 206 """Builds a list of URLs that makes RPC calls on all devservers. 207 208 Build a URL that calls |method| on the dev server, passing a set 209 of key/value pairs built from the dict |kwargs|. 210 211 @param method: the dev server method to call. 212 @param kwargs: a dict mapping arg names to arg values 213 @return the URL string 214 """ 215 calls = [] 216 # Note we use cls.servers as servers is class specific. 217 for server in cls.servers(): 218 if cls.devserver_healthy(server): 219 calls.append(cls._build_call(server, method, **kwargs)) 220 221 return calls 222 223 224 @staticmethod 225 def servers(): 226 """Returns a list of servers that can serve as this type of server.""" 227 raise NotImplementedError() 228 229 230 @classmethod 231 def resolve(cls, build): 232 """"Resolves a build to a devserver instance. 233 234 @param build: The build (e.g. x86-mario-release/R18-1586.0.0-a1-b1514). 235 """ 236 devservers = cls.servers() 237 while devservers: 238 hash_index = hash(build) % len(devservers) 239 devserver = devservers.pop(hash_index) 240 if cls.devserver_healthy(devserver): 241 return cls(devserver) 242 else: 243 logging.error('All devservers are currently down!!!') 244 raise DevServerException('All devservers are currently down!!!') 245 246 247class CrashServer(DevServer): 248 """Class of DevServer that symbolicates crash dumps.""" 249 @staticmethod 250 def servers(): 251 return _get_crash_server_list() 252 253 254 @remote_devserver_call() 255 def symbolicate_dump(self, minidump_path, build): 256 """Ask the devserver to symbolicate the dump at minidump_path. 257 258 Stage the debug symbols for |build| and, if that works, ask the 259 devserver to symbolicate the dump at |minidump_path|. 260 261 @param minidump_path: the on-disk path of the minidump. 262 @param build: The build (e.g. x86-mario-release/R18-1586.0.0-a1-b1514) 263 whose debug symbols are needed for symbolication. 264 @return The contents of the stack trace 265 @raise DevServerException upon any return code that's not HTTP OK. 266 """ 267 try: 268 import requests 269 except ImportError: 270 logging.warning("Can't 'import requests' to connect to dev server.") 271 return '' 272 273 stats.Counter('CrashServer.symbolicate_dump').increment() 274 timer = stats.Timer('CrashServer.symbolicate_dump') 275 timer.start() 276 # Symbolicate minidump. 277 call = self.build_call('symbolicate_dump', 278 archive_url=_get_image_storage_server() + build) 279 request = requests.post( 280 call, files={'minidump': open(minidump_path, 'rb')}) 281 if request.status_code == requests.codes.OK: 282 timer.stop() 283 return request.text 284 285 error_fd = cStringIO.StringIO(request.text) 286 raise urllib2.HTTPError( 287 call, request.status_code, request.text, request.headers, 288 error_fd) 289 290 291class ImageServer(DevServer): 292 """Class for DevServer that handles image-related RPCs. 293 294 The calls to devserver to stage artifacts, including stage and download, are 295 made in async mode. That is, when caller makes an RPC |stage| to request 296 devserver to stage certain artifacts, devserver handles the call and starts 297 staging artifacts in a new thread, and return |Success| without waiting for 298 staging being completed. When caller receives message |Success|, it polls 299 devserver's is_staged call until all artifacts are staged. 300 Such mechanism is designed to prevent cherrypy threads in devserver being 301 running out, as staging artifacts might take long time, and cherrypy starts 302 with a fixed number of threads that handle devserver rpc. 303 """ 304 @staticmethod 305 def servers(): 306 return _get_dev_server_list() 307 308 309 @classmethod 310 def devserver_url_for_servo(cls, board): 311 """Returns the devserver url for use with servo recovery. 312 313 @param board: The board (e.g. 'x86-mario'). 314 """ 315 # Ideally, for load balancing we'd select the server based 316 # on the board. For now, to simplify manual steps on the 317 # server side, we ignore the board type and hard-code the 318 # server as first in the list. 319 # 320 # TODO(jrbarnette) Once we have automated selection of the 321 # build for recovery, we should revisit this. 322 url_pattern = CONFIG.get_config_value('CROS', 323 'servo_url_pattern', 324 type=str) 325 return url_pattern % (cls.servers()[0], board) 326 327 328 class ArtifactUrls(object): 329 """A container for URLs of staged artifacts. 330 331 Attributes: 332 full_payload: URL for downloading a staged full release update 333 mton_payload: URL for downloading a staged M-to-N release update 334 nton_payload: URL for downloading a staged N-to-N release update 335 336 """ 337 def __init__(self, full_payload=None, mton_payload=None, 338 nton_payload=None): 339 self.full_payload = full_payload 340 self.mton_payload = mton_payload 341 self.nton_payload = nton_payload 342 343 344 def wait_for_artifacts_staged(self, archive_url, artifacts=''): 345 """Polling devserver.is_staged until all artifacts are staged. 346 347 @param archive_url: Google Storage URL for the build. 348 @param artifacts: Comma separated list of artifacts to download. 349 @return: True if all artifacts are staged in devserver. 350 """ 351 call = self.build_call('is_staged', 352 archive_url=archive_url, 353 artifacts=artifacts) 354 355 def all_staged(): 356 """Call devserver.is_staged rpc to check if all files are staged. 357 358 @return: True if all artifacts are staged in devserver. False 359 otherwise. 360 361 """ 362 try: 363 return urllib2.urlopen(call).read() == 'True' 364 except IOError: 365 return False 366 367 site_utils.poll_for_condition(all_staged, 368 exception=site_utils.TimeoutError(), 369 timeout=sys.maxint, 370 sleep_interval=_ARTIFACT_STAGE_POLLING_INTERVAL) 371 return True 372 373 374 def call_and_wait(self, call_name, archive_url, artifacts, 375 error_message, expected_response='Success'): 376 """Helper method to make a urlopen call, and wait for artifacts staged. 377 378 @param call_name: name of devserver rpc call. 379 @param archive_url: Google Storage URL for the build.. 380 @param artifacts: Comma separated list of artifacts to download. 381 @param expected_response: Expected response from rpc, default to 382 |Success|. If it's set to None, do not compare 383 the actual response. Any response is consider 384 to be good. 385 @param error_message: Error message to be thrown if response does not 386 match expected_response. 387 388 @return: The response from rpc. 389 @raise DevServerException upon any return code that's expected_response. 390 391 """ 392 call = self.build_call(call_name, 393 archive_url=archive_url, 394 artifacts=artifacts, 395 async=True) 396 response = urllib2.urlopen(call).read() 397 if expected_response and not response == expected_response: 398 raise DevServerException(error_message) 399 400 self.wait_for_artifacts_staged(archive_url, artifacts) 401 return response 402 403 404 @remote_devserver_call() 405 def stage_artifacts(self, image, artifacts): 406 """Tell the devserver to download and stage |artifacts| from |image|. 407 408 This is the main call point for staging any specific artifacts for a 409 given build. To see the list of artifacts one can stage see: 410 411 ~src/platfrom/dev/artifact_info.py. 412 413 This is maintained along with the actual devserver code. 414 415 @param image: the image to fetch and stage. 416 @param artifacts: A list of artifacts. 417 418 @raise DevServerException upon any return code that's not HTTP OK. 419 """ 420 archive_url = _get_image_storage_server() + image 421 artifacts = ','.join(artifacts) 422 error_message = ("staging artifacts %s for %s failed;" 423 "HTTP OK not accompanied by 'Success'." % 424 (' '.join(artifacts), image)) 425 self.call_and_wait(call_name='stage', 426 archive_url=archive_url, 427 artifacts=artifacts, 428 error_message=error_message) 429 430 431 @remote_devserver_call() 432 def trigger_download(self, image, synchronous=True): 433 """Tell the devserver to download and stage |image|. 434 435 Tells the devserver to fetch |image| from the image storage server 436 named by _get_image_storage_server(). 437 438 If |synchronous| is True, waits for the entire download to finish 439 staging before returning. Otherwise only the artifacts necessary 440 to start installing images onto DUT's will be staged before returning. 441 A caller can then call finish_download to guarantee the rest of the 442 artifacts have finished staging. 443 444 @param image: the image to fetch and stage. 445 @param synchronous: if True, waits until all components of the image are 446 staged before returning. 447 448 @raise DevServerException upon any return code that's not HTTP OK. 449 450 """ 451 archive_url=_get_image_storage_server() + image 452 artifacts = _ARTIFACTS_TO_BE_STAGED_FOR_IMAGE 453 error_message = ("trigger_download for %s failed;" 454 "HTTP OK not accompanied by 'Success'." % image) 455 response = self.call_and_wait(call_name='stage', 456 archive_url=archive_url, 457 artifacts=artifacts, 458 error_message=error_message) 459 was_successful = response == 'Success' 460 if was_successful and synchronous: 461 self.finish_download(image) 462 463 464 @remote_devserver_call() 465 def setup_telemetry(self, build): 466 """Tell the devserver to setup telemetry for this build. 467 468 The devserver will stage autotest and then extract the required files 469 for telemetry. 470 471 @param build: the build to setup telemetry for. 472 473 @returns path on the devserver that telemetry is installed to. 474 """ 475 archive_url=_get_image_storage_server() + build 476 artifacts = _ARTIFACTS_TO_BE_STAGED_FOR_TELEMETRY 477 response = self.call_and_wait(call_name='setup_telemetry', 478 archive_url=archive_url, 479 artifacts=artifacts, 480 expected_response=None) 481 return response 482 483 484 @remote_devserver_call() 485 def finish_download(self, image): 486 """Tell the devserver to finish staging |image|. 487 488 If trigger_download is called with synchronous=False, it will return 489 before all artifacts have been staged. This method contacts the 490 devserver and blocks until all staging is completed and should be 491 called after a call to trigger_download. 492 493 @param image: the image to fetch and stage. 494 @raise DevServerException upon any return code that's not HTTP OK. 495 """ 496 archive_url=_get_image_storage_server() + image 497 artifacts = _ARTIFACTS_TO_BE_STAGED_FOR_IMAGE_WITH_AUTOTEST 498 error_message = ("finish_download for %s failed;" 499 "HTTP OK not accompanied by 'Success'." % image) 500 self.call_and_wait(call_name='stage', 501 archive_url=archive_url, 502 artifacts=artifacts, 503 error_message=error_message) 504 505 506 def get_update_url(self, image): 507 """Returns the url that should be passed to the updater. 508 509 @param image: the image that was fetched. 510 """ 511 url_pattern = CONFIG.get_config_value('CROS', 'image_url_pattern', 512 type=str) 513 return (url_pattern % (self.url(), image)) 514 515 516 def _get_image_url(self, image): 517 """Returns the url of the directory for this image on the devserver. 518 519 @param image: the image that was fetched. 520 """ 521 url_pattern = CONFIG.get_config_value('CROS', 'image_url_pattern', 522 type=str) 523 return (url_pattern % (self.url(), image)).replace( 524 'update', 'static') 525 526 527 def get_delta_payload_url(self, payload_type, image): 528 """Returns a URL to a staged delta payload. 529 530 @param payload_type: either 'mton' or 'nton' 531 @param image: the image that was fetched. 532 533 @return A fully qualified URL that can be used for downloading the 534 payload. 535 536 @raise DevServerException if payload type argument is invalid. 537 538 """ 539 if payload_type not in ('mton', 'nton'): 540 raise DevServerException('invalid delta payload type: %s' % 541 payload_type) 542 version = os.path.basename(image) 543 base_url = self._get_image_url(image) 544 return base_url + '/au/%s_%s/update.gz' % (version, payload_type) 545 546 547 def get_full_payload_url(self, image): 548 """Returns a URL to a staged full payload. 549 550 @param image: the image that was fetched. 551 552 @return A fully qualified URL that can be used for downloading the 553 payload. 554 555 """ 556 return self._get_image_url(image) + '/update.gz' 557 558 559 def get_test_image_url(self, image): 560 """Returns a URL to a staged test image. 561 562 @param image: the image that was fetched. 563 564 @return A fully qualified URL that can be used for downloading the 565 image. 566 567 """ 568 return self._get_image_url(image) + '/chromiumos_test_image.bin' 569 570 571 @remote_devserver_call() 572 def list_control_files(self, build, suite_name=''): 573 """Ask the devserver to list all control files for |build|. 574 575 @param build: The build (e.g. x86-mario-release/R18-1586.0.0-a1-b1514) 576 whose control files the caller wants listed. 577 @param suite_name: The name of the suite for which we require control 578 files. 579 @return None on failure, or a list of control file paths 580 (e.g. server/site_tests/autoupdate/control) 581 @raise DevServerException upon any return code that's not HTTP OK. 582 """ 583 call = self.build_call('controlfiles', build=build, 584 suite_name=suite_name) 585 response = urllib2.urlopen(call) 586 return [line.rstrip() for line in response] 587 588 589 @remote_devserver_call() 590 def get_control_file(self, build, control_path): 591 """Ask the devserver for the contents of a control file. 592 593 @param build: The build (e.g. x86-mario-release/R18-1586.0.0-a1-b1514) 594 whose control file the caller wants to fetch. 595 @param control_path: The file to fetch 596 (e.g. server/site_tests/autoupdate/control) 597 @return The contents of the desired file. 598 @raise DevServerException upon any return code that's not HTTP OK. 599 """ 600 call = self.build_call('controlfiles', build=build, 601 control_path=control_path) 602 return urllib2.urlopen(call).read() 603 604 605 @remote_devserver_call() 606 def get_dependencies_file(self, build): 607 """Ask the dev server for the contents of the suite dependencies file. 608 609 Ask the dev server at |self._dev_server| for the contents of the 610 pre-processed suite dependencies file (at DEPENDENCIES_FILE) 611 for |build|. 612 613 @param build: The build (e.g. x86-mario-release/R21-2333.0.0) 614 whose dependencies the caller is interested in. 615 @return The contents of the dependencies file, which should eval to 616 a dict of dicts, as per site_utils/suite_preprocessor.py. 617 @raise DevServerException upon any return code that's not HTTP OK. 618 """ 619 call = self.build_call('controlfiles', 620 build=build, control_path=DEPENDENCIES_FILE) 621 return urllib2.urlopen(call).read() 622 623 624 @classmethod 625 @remote_devserver_call() 626 def get_latest_build(cls, target, milestone=''): 627 """Ask all the devservers for the latest build for a given target. 628 629 @param target: The build target, typically a combination of the board 630 and the type of build e.g. x86-mario-release. 631 @param milestone: For latest build set to '', for builds only in a 632 specific milestone set to a str of format Rxx 633 (e.g. R16). Default: ''. Since we are dealing with a 634 webserver sending an empty string, '', ensures that 635 the variable in the URL is ignored as if it was set 636 to None. 637 @return A string of the returned build e.g. R20-2226.0.0. 638 @raise DevServerException upon any return code that's not HTTP OK. 639 """ 640 calls = cls.build_all_calls('latestbuild', target=target, 641 milestone=milestone) 642 latest_builds = [] 643 for call in calls: 644 latest_builds.append(urllib2.urlopen(call).read()) 645 646 return max(latest_builds, key=version.LooseVersion) 647