blob: 65e16433afd0c76a03d9fb09ba817acfb18c5472 [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
158def get_lab_status():
159 """Grabs the current lab status and message.
160
161 @returns a dict with keys 'lab_is_up' and 'message'. lab_is_up points
162 to a boolean and message points to a string.
163 """
164 result = {'lab_is_up' : True, 'message' : ''}
165 status_url = global_config.global_config.get_config_value('CROS',
166 'lab_status_url')
167 max_attempts = 5
168 retry_waittime = 1
169 for _ in range(max_attempts):
170 try:
171 response = urllib2.urlopen(status_url)
172 except IOError as e:
173 logging.debug('Error occured when grabbing the lab status: %s.',
174 e)
175 time.sleep(retry_waittime)
176 continue
177 # Check for successful response code.
178 if response.getcode() == 200:
179 data = json.load(response)
180 result['lab_is_up'] = data['general_state'] in LAB_GOOD_STATES
181 result['message'] = data['message']
182 return result
183 time.sleep(retry_waittime)
184 # We go ahead and say the lab is open if we can't get the status.
185 logging.warn('Could not get a status from %s', status_url)
186 return result
187
188
189def check_lab_status(board=None):
190 """Check if the lab is up and if we can schedule suites to run.
191
192 Also checks if the lab is disabled for that particular board, and if so
193 will raise an error to prevent new suites from being scheduled for that
194 board.
195
196 @param board: board name that we want to check the status of.
197
198 @raises LabIsDownException if the lab is not up.
199 @raises BoardIsDisabledException if the desired board is currently
200 disabled.
201 """
202 # Ensure we are trying to schedule on the actual lab.
203 if not (global_config.global_config.get_config_value('SERVER',
204 'hostname').startswith('cautotest')):
205 return
206
207 # First check if the lab is up.
208 lab_status = get_lab_status()
209 if not lab_status['lab_is_up']:
210 raise LabIsDownException('Chromium OS Lab is currently not up: '
211 '%s.' % lab_status['message'])
212
213 # Check if the board we wish to use is disabled.
214 # Lab messages should be in the format of:
215 # Lab is 'status' [boards not to be ran] (comment). Example:
216 # Lab is Open [stumpy, kiev, x86-alex] (power_resume rtc causing duts to go
217 # down)
218 boards_are_disabled = re.search('\[(.*)\]', lab_status['message'])
219 if board and boards_are_disabled:
220 if board in boards_are_disabled.group(1):
221 raise BoardIsDisabledException('Chromium OS Lab is '
222 'currently not allowing suites to be scheduled on board '
223 '%s: %s' % (board, lab_status['message']))
224 return