OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """This module contains functionality for starting build try jobs via HTTP. | 5 """This module contains functionality for starting build try jobs via HTTP. |
6 | 6 |
7 This includes both sending a request to start a job, and also related code | 7 This includes both sending a request to start a job, and also related code |
8 for querying the status of the job. | 8 for querying the status of the job. |
9 | 9 |
10 This module can be either run as a stand-alone script to send a request to a | 10 This module can be either run as a stand-alone script to send a request to a |
11 builder, or imported and used by calling the public functions below. | 11 builder, or imported and used by calling the public functions below. |
12 """ | 12 """ |
13 | 13 |
14 import getpass | 14 import getpass |
15 import json | 15 import json |
16 import optparse | 16 import optparse |
17 import os | 17 import os |
18 import sys | 18 import sys |
19 import urllib | 19 import urllib |
20 import urllib2 | 20 import urllib2 |
21 | 21 |
22 import fetch_build | 22 import fetch_build |
23 | 23 |
24 # URL template for fetching JSON data about builds. | 24 # URL template for fetching JSON data about builds. |
25 BUILDER_JSON_URL = ('%(server_url)s/json/builders/%(bot_name)s/builds/' | 25 BUILDER_JSON_URL = ('%(server_url)s/json/builders/%(bot_name)s/builds/' |
26 '%(build_num)s?as_text=1&filter=0') | 26 '%(build_num)s?as_text=1&filter=0') |
27 | 27 |
28 # URL template for displaying build steps. | 28 # URL template for displaying build steps. |
29 BUILDER_HTML_URL = ('%(server_url)s/builders/%(bot_name)s/builds/%(build_num)s') | 29 BUILDER_HTML_URL = '%(server_url)s/builders/%(bot_name)s/builds/%(build_num)s' |
30 | 30 |
31 # Try server status page for the perf try server. | 31 # Try server status page URLs, used to get build status. |
32 PERF_TRY_SERVER_URL = 'http://build.chromium.org/p/tryserver.chromium.perf' | 32 PERF_TRY_SERVER_URL = 'http://build.chromium.org/p/tryserver.chromium.perf' |
| 33 LINUX_TRY_SERVER_URL = 'http://build.chromium.org/p/tryserver.chromium.linux' |
33 | 34 |
34 # Hostname of the tryserver where perf bisect builders are hosted. | 35 # Status codes that can be returned by the GetBuildStatus method |
35 # This is used for posting build requests to the tryserver. | |
36 PERF_BISECT_BUILDER_HOST = 'master4.golo.chromium.org' | |
37 | |
38 # The default 'try_job_port' on tryserver to post build request. | |
39 PERF_BISECT_BUILDER_PORT = 8341 | |
40 | |
41 # Status codes that can be returned by the GetBuildStatus method. | |
42 # From buildbot.status.builder. | 36 # From buildbot.status.builder. |
43 # See: http://docs.buildbot.net/current/developer/results.html | 37 # See: http://docs.buildbot.net/current/developer/results.html |
44 SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY, TRYPENDING = range(7) | 38 SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY, TRYPENDING = range(7) |
45 | |
46 OK = (SUCCESS, WARNINGS) # These indicate build is complete. | 39 OK = (SUCCESS, WARNINGS) # These indicate build is complete. |
47 FAILED = (FAILURE, EXCEPTION, SKIPPED) # These indicate build failure. | 40 FAILED = (FAILURE, EXCEPTION, SKIPPED) # These indicate build failure. |
48 PENDING = (RETRY, TRYPENDING) # These indicate in progress or in pending queue. | 41 PENDING = (RETRY, TRYPENDING) # These indicate in progress or in pending queue. |
49 | 42 |
50 | 43 |
51 class ServerAccessError(Exception): | 44 class ServerAccessError(Exception): |
52 | 45 |
53 def __str__(self): | 46 def __str__(self): |
54 return '%s\nSorry, cannot connect to server.' % self.args[0] | 47 return '%s\nSorry, cannot connect to server.' % self.args[0] |
55 | 48 |
56 | 49 |
57 def PostTryJob(host, port, params): | |
58 """Sends a build request to the server using the HTTP protocol. | |
59 | |
60 The required parameters are: | |
61 'revision': "src@rev", where rev is a git hash or SVN revision. | |
62 'bot': Name of builder bot to use, e.g. "win_perf_bisect_builder". | |
63 | |
64 Args: | |
65 host: Hostname of the try server. | |
66 port: Port of the try server. | |
67 params: A dictionary of parameters to be sent in the POST request. | |
68 | |
69 Returns: | |
70 True if the request is posted successfully. | |
71 | |
72 Raises: | |
73 ServerAccessError: Failed to make a request to the try server. | |
74 ValueError: Not all of the expected inputs were given. | |
75 """ | |
76 if not params.get('revision'): | |
77 raise ValueError('Missing revision number.') | |
78 if not params.get('bot'): | |
79 raise ValueError('Missing bot name.') | |
80 | |
81 base_url = 'http://%s:%s/send_try_patch' % (host, port) | |
82 print 'Posting build request by HTTP.' | |
83 print 'URL: %s, params: %s' % (base_url, params) | |
84 | |
85 connection = None | |
86 try: | |
87 print 'Opening connection...' | |
88 connection = urllib2.urlopen(base_url, urllib.urlencode(params)) | |
89 print 'Done, request sent to server to produce build.' | |
90 except IOError as e: | |
91 raise ServerAccessError('%s is unaccessible. Reason: %s' % (base_url, e)) | |
92 if not connection: | |
93 raise ServerAccessError('%s is unaccessible.' % base_url) | |
94 | |
95 response = connection.read() | |
96 print 'Received response from server: %s' % response | |
97 if response != 'OK': | |
98 raise ServerAccessError('Response was not "OK".') | |
99 return True | |
100 | |
101 | |
102 def _IsBuildRunning(build_data): | 50 def _IsBuildRunning(build_data): |
103 """Checks whether the build is in progress on buildbot. | 51 """Checks whether the build is in progress on buildbot. |
104 | 52 |
105 Presence of currentStep element in build JSON indicates build is in progress. | 53 Presence of currentStep element in build JSON indicates build is in progress. |
106 | 54 |
107 Args: | 55 Args: |
108 build_data: A dictionary with build data, loaded from buildbot JSON API. | 56 build_data: A dictionary with build data, loaded from buildbot JSON API. |
109 | 57 |
110 Returns: | 58 Returns: |
111 True if build is in progress, otherwise False. | 59 True if build is in progress, otherwise False. |
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
211 tryserver URL for the perf tryserver. | 159 tryserver URL for the perf tryserver. |
212 | 160 |
213 Args: | 161 Args: |
214 builder_type: Determines what type of builder is used, e.g. "perf". | 162 builder_type: Determines what type of builder is used, e.g. "perf". |
215 | 163 |
216 Returns: | 164 Returns: |
217 URL of the buildbot as a string. | 165 URL of the buildbot as a string. |
218 """ | 166 """ |
219 if builder_type == fetch_build.PERF_BUILDER: | 167 if builder_type == fetch_build.PERF_BUILDER: |
220 return PERF_TRY_SERVER_URL | 168 return PERF_TRY_SERVER_URL |
221 else: | 169 if builder_type == fetch_build.FULL_BUILDER: |
222 raise NotImplementedError('Cannot yet get non-perf builds.') | 170 return LINUX_TRY_SERVER_URL |
| 171 raise NotImplementedError('Unsupported builder type "%s".' % builder_type) |
223 | 172 |
224 | 173 |
225 def GetBuildStatus(build_num, bot_name, builder_type): | 174 def GetBuildStatus(build_num, bot_name, builder_type): |
226 """Gets build status from the buildbot status page for a given build number. | 175 """Gets build status from the buildbot status page for a given build number. |
227 | 176 |
228 Args: | 177 Args: |
229 build_num: A build number on tryserver to determine its status. | 178 build_num: A build number on tryserver to determine its status. |
230 bot_name: Name of the bot where the build information is scanned. | 179 bot_name: Name of the bot where the build information is scanned. |
231 builder_type: Type of builder, e.g. "perf". | 180 builder_type: Type of builder, e.g. "perf". |
232 | 181 |
233 Returns: | 182 Returns: |
234 A pair which consists of build status (SUCCESS, FAILED or PENDING) and a | 183 A pair which consists of build status (SUCCESS, FAILED or PENDING) and a |
235 link to build status page on the waterfall. | 184 link to build status page on the waterfall. |
236 """ | 185 """ |
| 186 # TODO(prasadv, qyearsley): Make this a method of BuildArchive |
| 187 # (which may be renamed to BuilderTryBot or Builder). |
237 results_url = None | 188 results_url = None |
238 if build_num: | 189 if build_num: |
239 # Get the URL for requesting JSON data with status information. | 190 # Get the URL for requesting JSON data with status information. |
240 server_url = _GetBuildBotUrl(builder_type) | 191 server_url = _GetBuildBotUrl(builder_type) |
241 buildbot_url = BUILDER_JSON_URL % { | 192 buildbot_url = BUILDER_JSON_URL % { |
242 'server_url': server_url, | 193 'server_url': server_url, |
243 'bot_name': bot_name, | 194 'bot_name': bot_name, |
244 'build_num': build_num, | 195 'build_num': build_num, |
245 } | 196 } |
246 build_data = _GetBuildData(buildbot_url) | 197 build_data = _GetBuildData(buildbot_url) |
(...skipping 23 matching lines...) Expand all Loading... |
270 to identify the build on status page. | 221 to identify the build on status page. |
271 | 222 |
272 Args: | 223 Args: |
273 build_reason: A unique build name set to build on tryserver. | 224 build_reason: A unique build name set to build on tryserver. |
274 bot_name: Name of the bot where the build information is scanned. | 225 bot_name: Name of the bot where the build information is scanned. |
275 builder_type: Type of builder, e.g. "perf". | 226 builder_type: Type of builder, e.g. "perf". |
276 | 227 |
277 Returns: | 228 Returns: |
278 A build number as a string if found, otherwise None. | 229 A build number as a string if found, otherwise None. |
279 """ | 230 """ |
| 231 # TODO(prasadv, qyearsley): Make this a method of BuildArchive |
| 232 # (which may be renamed to BuilderTryBot or Builder). |
280 # Gets the buildbot url for the given host and port. | 233 # Gets the buildbot url for the given host and port. |
281 server_url = _GetBuildBotUrl(builder_type) | 234 server_url = _GetBuildBotUrl(builder_type) |
282 buildbot_url = BUILDER_JSON_URL % { | 235 buildbot_url = BUILDER_JSON_URL % { |
283 'server_url': server_url, | 236 'server_url': server_url, |
284 'bot_name': bot_name, | 237 'bot_name': bot_name, |
285 'build_num': '_all', | 238 'build_num': '_all', |
286 } | 239 } |
287 builds_json = _FetchBuilderData(buildbot_url) | 240 builds_json = _FetchBuilderData(buildbot_url) |
288 if builds_json: | 241 if builds_json: |
289 builds_data = json.loads(builds_json) | 242 builds_data = json.loads(builds_json) |
290 for current_build in builds_data: | 243 for current_build in builds_data: |
291 if builds_data[current_build].get('reason') == build_reason: | 244 if builds_data[current_build].get('reason') == build_reason: |
292 return builds_data[current_build].get('number') | 245 return builds_data[current_build].get('number') |
293 return None | 246 return None |
294 | |
295 | |
296 def _GetRequestParams(options): | |
297 """Extracts request parameters which will be passed to PostTryJob. | |
298 | |
299 Args: | |
300 options: The options object parsed from the command line. | |
301 | |
302 Returns: | |
303 A dictionary with parameters to pass to PostTryJob. | |
304 """ | |
305 params = { | |
306 'user': options.user, | |
307 'name': options.name, | |
308 } | |
309 # Add other parameters if they're available in the options object. | |
310 for key in ['email', 'revision', 'root', 'bot', 'patch']: | |
311 option = getattr(options, key) | |
312 if option: | |
313 params[key] = option | |
314 return params | |
315 | |
316 | |
317 def _GenParser(): | |
318 """Returns a parser for getting command line arguments.""" | |
319 usage = ('%prog [options]\n' | |
320 'Post a build request to the try server for the given revision.') | |
321 parser = optparse.OptionParser(usage=usage) | |
322 parser.add_option('-H', '--host', | |
323 help='Host address of the try server (required).') | |
324 parser.add_option('-P', '--port', type='int', | |
325 help='HTTP port of the try server (required).') | |
326 parser.add_option('-u', '--user', default=getpass.getuser(), | |
327 dest='user', | |
328 help='Owner user name [default: %default]') | |
329 parser.add_option('-e', '--email', | |
330 default=os.environ.get('TRYBOT_RESULTS_EMAIL_ADDRESS', | |
331 os.environ.get('EMAIL_ADDRESS')), | |
332 help=('Email address where to send the results. Use either ' | |
333 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment ' | |
334 'variable or EMAIL_ADDRESS to set the email address ' | |
335 'the try bots report results to [default: %default]')) | |
336 parser.add_option('-n', '--name', | |
337 default='try_job_http', | |
338 help='Descriptive name of the try job') | |
339 parser.add_option('-b', '--bot', | |
340 help=('Only one builder per run may be specified; to post ' | |
341 'jobs on on multiple builders, run script for each ' | |
342 'builder separately.')) | |
343 parser.add_option('-r', '--revision', | |
344 help=('Revision to use for the try job. The revision may ' | |
345 'be determined by the try server; see its waterfall ' | |
346 'for more info.')) | |
347 parser.add_option('--root', | |
348 help=('Root to use for the patch; base subdirectory for ' | |
349 'patch created in a subdirectory.')) | |
350 parser.add_option('--patch', | |
351 help='Patch information.') | |
352 return parser | |
353 | |
354 | |
355 def Main(_): | |
356 """Posts a try job based on command line parameters.""" | |
357 parser = _GenParser() | |
358 options, _ = parser.parse_args() | |
359 if not options.host or not options.port: | |
360 parser.print_help() | |
361 return 1 | |
362 params = _GetRequestParams(options) | |
363 PostTryJob(options.host, options.port, params) | |
364 | |
365 | |
366 if __name__ == '__main__': | |
367 sys.exit(Main(sys.argv)) | |
OLD | NEW |