blob: daa0a6b0101e42f9304c520c592160e15ee82742 [file] [log] [blame]
Dan Shia1ecd5c2013-06-06 11:21:31 -07001# Copyright (c) 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Dan Shia1ecd5c2013-06-06 11:21:31 -07005
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -08006import httplib
7import json
Alex Millerdadc2c22013-07-08 15:21:21 -07008import logging
9import re
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080010import time
Paul Drewsbef578d2013-09-24 15:10:36 -070011import urllib2
Alex Millerdadc2c22013-07-08 15:21:21 -070012
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080013import common
Alex Millerdadc2c22013-07-08 15:21:21 -070014from autotest_lib.client.common_lib import base_utils, global_config
Dan Shia1ecd5c2013-06-06 11:21:31 -070015from autotest_lib.server.cros.dynamic_suite import constants
16
17
Alex Millerdadc2c22013-07-08 15:21:21 -070018_SHERIFF_JS = global_config.global_config.get_config_value(
19 'NOTIFICATIONS', 'sheriffs', default='')
Fang Deng3197b392013-06-26 11:42:02 -070020_LAB_SHERIFF_JS = global_config.global_config.get_config_value(
21 'NOTIFICATIONS', 'lab_sheriffs', default='')
Alex Millerdadc2c22013-07-08 15:21:21 -070022_CHROMIUM_BUILD_URL = global_config.global_config.get_config_value(
23 'NOTIFICATIONS', 'chromium_build_url', default='')
24
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -080025LAB_GOOD_STATES = ('open', 'throttled')
26
27
28class LabIsDownException(Exception):
29 """Raised when the Lab is Down"""
30 pass
31
32
33class BoardIsDisabledException(Exception):
34 """Raised when a certain board is disabled in the Lab"""
35 pass
36
37
38class ParseBuildNameException(Exception):
39 """Raised when ParseBuildName() cannot parse a build name."""
40 pass
41
42
43def ParseBuildName(name):
44 """Format a build name, given board, type, milestone, and manifest num.
45
46 @param name: a build name, e.g. 'x86-alex-release/R20-2015.0.0'
47
48 @return board: board the manifest is for, e.g. x86-alex.
49 @return type: one of 'release', 'factory', or 'firmware'
50 @return milestone: (numeric) milestone the manifest was associated with.
51 @return manifest: manifest number, e.g. '2015.0.0'
52
53 """
54 match = re.match(r'([\w-]+)-(\w+)/R(\d+)-([\d.ab-]+)', name)
55 if match and len(match.groups()) == 4:
56 return match.groups()
57 raise ParseBuildNameException('%s is a malformed build name.' % name)
58
Alex Millerdadc2c22013-07-08 15:21:21 -070059
Dan Shia1ecd5c2013-06-06 11:21:31 -070060def get_label_from_afe(hostname, label_prefix, afe):
61 """Retrieve a host's specific label from the AFE.
62
63 Looks for a host label that has the form <label_prefix>:<value>
64 and returns the "<value>" part of the label. None is returned
65 if there is not a label matching the pattern
66
67 @param hostname: hostname of given DUT.
68 @param label_prefix: prefix of label to be matched, e.g., |board:|
69 @param afe: afe instance.
70 @returns the label that matches the prefix or 'None'
71
72 """
73 labels = afe.get_labels(name__startswith=label_prefix,
74 host__hostname__in=[hostname])
75 if labels and len(labels) == 1:
76 return labels[0].name.split(label_prefix, 1)[1]
77
78
79def get_board_from_afe(hostname, afe):
80 """Retrieve given host's board from its labels in the AFE.
81
82 Looks for a host label of the form "board:<board>", and
83 returns the "<board>" part of the label. `None` is returned
84 if there is not a single, unique label matching the pattern.
85
86 @param hostname: hostname of given DUT.
87 @param afe: afe instance.
88 @returns board from label, or `None`.
89
90 """
91 return get_label_from_afe(hostname, constants.BOARD_PREFIX, afe)
92
93
94def get_build_from_afe(hostname, afe):
95 """Retrieve the current build for given host from the AFE.
96
97 Looks through the host's labels in the AFE to determine its build.
98
99 @param hostname: hostname of given DUT.
100 @param afe: afe instance.
101 @returns The current build or None if it could not find it or if there
102 were multiple build labels assigned to this host.
103
104 """
105 return get_label_from_afe(hostname, constants.VERSION_PREFIX, afe)
106
107
Fang Deng3197b392013-06-26 11:42:02 -0700108def get_sheriffs(lab_only=False):
Alex Millerdadc2c22013-07-08 15:21:21 -0700109 """
110 Polls the javascript file that holds the identity of the sheriff and
111 parses it's output to return a list of chromium sheriff email addresses.
112 The javascript file can contain the ldap of more than one sheriff, eg:
113 document.write('sheriff_one, sheriff_two').
114
Fang Deng3197b392013-06-26 11:42:02 -0700115 @param lab_only: if True, only pulls lab sheriff.
116 @return: A list of chroium.org sheriff email addresses to cc on the bug.
117 An empty list if failed to parse the javascript.
Alex Millerdadc2c22013-07-08 15:21:21 -0700118 """
119 sheriff_ids = []
Fang Deng3197b392013-06-26 11:42:02 -0700120 sheriff_js_list = _LAB_SHERIFF_JS.split(',')
121 if not lab_only:
122 sheriff_js_list.extend(_SHERIFF_JS.split(','))
123
124 for sheriff_js in sheriff_js_list:
Alex Millerdadc2c22013-07-08 15:21:21 -0700125 try:
126 url_content = base_utils.urlopen('%s%s'% (
127 _CHROMIUM_BUILD_URL, sheriff_js)).read()
128 except (ValueError, IOError) as e:
beeps4efdf032013-09-17 11:27:14 -0700129 logging.warning('could not parse sheriff from url %s%s: %s',
130 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Paul Drewsbef578d2013-09-24 15:10:36 -0700131 except (urllib2.URLError, httplib.HTTPException) as e:
132 logging.warning('unexpected error reading from url "%s%s": %s',
133 _CHROMIUM_BUILD_URL, sheriff_js, str(e))
Alex Millerdadc2c22013-07-08 15:21:21 -0700134 else:
135 ldaps = re.search(r"document.write\('(.*)'\)", url_content)
136 if not ldaps:
beeps4efdf032013-09-17 11:27:14 -0700137 logging.warning('Could not retrieve sheriff ldaps for: %s',
138 url_content)
Alex Millerdadc2c22013-07-08 15:21:21 -0700139 continue
140 sheriff_ids += ['%s@chromium.org' % alias.replace(' ', '')
141 for alias in ldaps.group(1).split(',')]
142 return sheriff_ids
beeps46dadc92013-11-07 14:07:10 -0800143
144
145def remote_wget(source_url, dest_path, ssh_cmd):
146 """wget source_url from localhost to dest_path on remote host using ssh.
147
148 @param source_url: The complete url of the source of the package to send.
149 @param dest_path: The path on the remote host's file system where we would
150 like to store the package.
151 @param ssh_cmd: The ssh command to use in performing the remote wget.
152 """
153 wget_cmd = ("wget -O - %s | %s 'cat >%s'" %
154 (source_url, ssh_cmd, dest_path))
155 base_utils.run(wget_cmd)
156
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800157
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800158_MAX_LAB_STATUS_ATTEMPTS = 5
159def _get_lab_status(status_url):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800160 """Grabs the current lab status and message.
161
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800162 @returns The JSON object obtained from the given URL.
163
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800164 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800165 retry_waittime = 1
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800166 for _ in range(_MAX_LAB_STATUS_ATTEMPTS):
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800167 try:
168 response = urllib2.urlopen(status_url)
169 except IOError as e:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800170 logging.debug('Error occurred when grabbing the lab status: %s.',
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800171 e)
172 time.sleep(retry_waittime)
173 continue
174 # Check for successful response code.
175 if response.getcode() == 200:
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800176 return json.load(response)
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800177 time.sleep(retry_waittime)
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800178 return None
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800179
180
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800181def _decode_lab_status(lab_status, board):
182 """Decode lab status, and report exceptions as needed.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800183
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800184 Takes a deserialized JSON object from the lab status page, and
185 interprets it to determine the actual lab status. Raises
186 exceptions as required to report when the lab is down.
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800187
188 @param board: board name that we want to check the status of.
189
190 @raises LabIsDownException if the lab is not up.
191 @raises BoardIsDisabledException if the desired board is currently
192 disabled.
193 """
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800194 # First check if the lab is up.
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800195 if not lab_status['general_state'] in LAB_GOOD_STATES:
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800196 raise LabIsDownException('Chromium OS Lab is currently not up: '
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800197 '%s.' % lab_status['message'])
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800198
199 # Check if the board we wish to use is disabled.
200 # Lab messages should be in the format of:
201 # Lab is 'status' [boards not to be ran] (comment). Example:
202 # Lab is Open [stumpy, kiev, x86-alex] (power_resume rtc causing duts to go
203 # down)
204 boards_are_disabled = re.search('\[(.*)\]', lab_status['message'])
205 if board and boards_are_disabled:
206 if board in boards_are_disabled.group(1):
207 raise BoardIsDisabledException('Chromium OS Lab is '
208 'currently not allowing suites to be scheduled on board '
209 '%s: %s' % (board, lab_status['message']))
210 return
J. Richard Barnette266da2a2013-11-27 15:09:55 -0800211
212
213def check_lab_status(board=None):
214 """Check if the lab status allows us to schedule suites.
215
216 Also checks if the lab is disabled for that particular board, and if so
217 will raise an error to prevent new suites from being scheduled for that
218 board.
219
220 @param board: board name that we want to check the status of.
221
222 @raises LabIsDownException if the lab is not up.
223 @raises BoardIsDisabledException if the desired board is currently
224 disabled.
225
226 """
227 # Ensure we are trying to schedule on the actual lab.
228 test_server_name = global_config.global_config.get_config_value(
229 'SERVER', 'hostname')
230 if not test_server_name.startswith('cautotest'):
231 return
232
233 # Download the lab status from its home on the web.
234 status_url = global_config.global_config.get_config_value(
235 'CROS', 'lab_status_url')
236 json_status = _get_lab_status(status_url)
237 if json_status is None:
238 # We go ahead and say the lab is open if we can't get the status.
239 logging.warn('Could not get a status from %s', status_url)
240 return
241 _decode_lab_status(json_status, board)