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
3 # found in the LICENSE file.
5 """Post a try job request via HTTP to the Tryserver to produce build."""
15 # Link to get JSON data of builds
16 BUILDER_JSON_URL
= ('%(server_url)s/json/builders/%(bot_name)s/builds/'
17 '%(build_num)s?as_text=1&filter=0')
19 # Link to display build steps
20 BUILDER_HTML_URL
= ('%(server_url)s/builders/%(bot_name)s/builds/%(build_num)s')
22 # Tryserver buildbots status page
23 TRY_SERVER_URL
= 'http://build.chromium.org/p/tryserver.chromium'
25 # Hostname of the tryserver where perf bisect builders are hosted. This is used
26 # for posting build request to tryserver.
27 BISECT_BUILDER_HOST
= 'master4.golo.chromium.org'
28 # 'try_job_port' on tryserver to post build request.
29 BISECT_BUILDER_PORT
= 8328
32 # From buildbot.status.builder.
33 # See: http://docs.buildbot.net/current/developer/results.html
34 SUCCESS
, WARNINGS
, FAILURE
, SKIPPED
, EXCEPTION
, RETRY
, TRYPENDING
= range(7)
36 # Status codes that can be returned by the GetBuildStatus method.
37 OK
= (SUCCESS
, WARNINGS
)
38 # Indicates build failure.
39 FAILED
= (FAILURE
, EXCEPTION
, SKIPPED
)
40 # Inidcates build in progress or in pending queue.
41 PENDING
= (RETRY
, TRYPENDING
)
44 class ServerAccessError(Exception):
47 return '%s\nSorry, cannot connect to server.' % self
.args
[0]
50 def PostTryJob(url_params
):
51 """Sends a build request to the server using the HTTP protocol.
54 url_params: A dictionary of query parameters to be sent in the request.
55 In order to post build request to try server, this dictionary
56 should contain information for following keys:
57 'host': Hostname of the try server.
58 'port': Port of the try server.
59 'revision': SVN Revision to build.
60 'bot': Name of builder bot which would be used.
62 True if the request is posted successfully. Otherwise throws an exception.
64 # Parse url parameters to be sent to Try server.
65 if not url_params
.get('host'):
66 raise ValueError('Hostname of server to connect is missing.')
67 if not url_params
.get('port'):
68 raise ValueError('Port of server to connect is missing.')
69 if not url_params
.get('revision'):
70 raise ValueError('Missing revision details. Please specify revision'
72 if not url_params
.get('bot'):
73 raise ValueError('Missing bot details. Please specify bot information.')
75 # Pop 'host' and 'port' to avoid passing them as query params.
76 url
= 'http://%s:%s/send_try_patch' % (url_params
.pop('host'),
77 url_params
.pop('port'))
79 print 'Sending by HTTP'
80 query_params
= '&'.join('%s=%s' % (k
, v
) for k
, v
in url_params
.iteritems())
81 print 'url: %s?%s' % (url
, query_params
)
85 print 'Opening connection...'
86 connection
= urllib2
.urlopen(url
, urllib
.urlencode(url_params
))
87 print 'Done, request sent to server to produce build.'
89 raise ServerAccessError('%s is unaccessible. Reason: %s' % (url
, e
))
91 raise ServerAccessError('%s is unaccessible.' % url
)
92 response
= connection
.read()
93 print 'Received %s from server' % response
95 raise ServerAccessError('%s is unaccessible. Got:\n%s' % (url
, response
))
99 def _IsBuildRunning(build_data
):
100 """Checks whether the build is in progress on buildbot.
102 Presence of currentStep element in build JSON indicates build is in progress.
105 build_data: A dictionary with build data, loaded from buildbot JSON API.
108 True if build is in progress, otherwise False.
110 current_step
= build_data
.get('currentStep')
111 if (current_step
and current_step
.get('isStarted') and
112 current_step
.get('results') is None):
117 def _IsBuildFailed(build_data
):
118 """Checks whether the build failed on buildbot.
120 Sometime build status is marked as failed even though compile and packaging
121 steps are successful. This may happen due to some intermediate steps of less
122 importance such as gclient revert, generate_telemetry_profile are failed.
123 Therefore we do an addition check to confirm if build was successful by
124 calling _IsBuildSuccessful.
127 build_data: A dictionary with build data, loaded from buildbot JSON API.
130 True if revision is failed build, otherwise False.
132 if (build_data
.get('results') in FAILED
and
133 not _IsBuildSuccessful(build_data
)):
138 def _IsBuildSuccessful(build_data
):
139 """Checks whether the build succeeded on buildbot.
141 We treat build as successful if the package_build step is completed without
142 any error i.e., when results attribute of the this step has value 0 or 1
143 in its first element.
146 build_data: A dictionary with build data, loaded from buildbot JSON API.
149 True if revision is successfully build, otherwise False.
151 if build_data
.get('steps'):
152 for item
in build_data
.get('steps'):
153 # The 'results' attribute of each step consists of two elements,
154 # results[0]: This represents the status of build step.
155 # See: http://docs.buildbot.net/current/developer/results.html
156 # results[1]: List of items, contains text if step fails, otherwise empty.
157 if (item
.get('name') == 'package_build' and
158 item
.get('isFinished') and
159 item
.get('results')[0] in OK
):
164 def _FetchBuilderData(builder_url
):
165 """Fetches JSON data for the all the builds from the tryserver.
168 builder_url: A tryserver URL to fetch builds information.
171 A dictionary with information of all build on the tryserver.
175 url
= urllib2
.urlopen(builder_url
)
176 except urllib2
.URLError
, e
:
177 print ('urllib2.urlopen error %s, waterfall status page down.[%s]' % (
178 builder_url
, str(e
)))
184 print 'urllib2 file object read error %s, [%s].' % (builder_url
, str(e
))
188 def _GetBuildData(buildbot_url
):
189 """Gets build information for the given build id from the tryserver.
192 buildbot_url: A tryserver URL to fetch build information.
195 A dictionary with build information if build exists, otherwise None.
197 builds_json
= _FetchBuilderData(buildbot_url
)
199 return json
.loads(builds_json
)
203 def _GetBuildBotUrl(builder_host
, builder_port
):
204 """Gets build bot URL based on the host and port of the builders.
206 Note: All bisect builder bots are hosted on tryserver.chromium i.e.,
207 on master4:8328, since we cannot access tryserver using host and port
208 number directly, we use tryserver URL.
211 builder_host: Hostname of the server where the builder is hosted.
212 builder_port: Port number of ther server where the builder is hosted.
215 URL of the buildbot as a string.
217 if (builder_host
== BISECT_BUILDER_HOST
and
218 builder_port
== BISECT_BUILDER_PORT
):
219 return TRY_SERVER_URL
221 return 'http://%s:%s' % (builder_host
, builder_port
)
224 def GetBuildStatus(build_num
, bot_name
, builder_host
, builder_port
):
225 """Gets build status from the buildbot status page for a given build number.
228 build_num: A build number on tryserver to determine its status.
229 bot_name: Name of the bot where the build information is scanned.
230 builder_host: Hostname of the server where the builder is hosted.
231 builder_port: Port number of ther server where the builder is hosted.
234 A tuple consists of build status (SUCCESS, FAILED or PENDING) and a link
235 to build status page on the waterfall.
239 # Gets the buildbot url for the given host and port.
240 server_url
= _GetBuildBotUrl(builder_host
, builder_port
)
241 buildbot_url
= BUILDER_JSON_URL
% {'server_url': server_url
,
242 'bot_name': bot_name
,
243 'build_num': build_num
245 build_data
= _GetBuildData(buildbot_url
)
247 # Link to build on the buildbot showing status of build steps.
248 results_url
= BUILDER_HTML_URL
% {'server_url': server_url
,
249 'bot_name': bot_name
,
250 'build_num': build_num
252 if _IsBuildFailed(build_data
):
253 return (FAILED
, results_url
)
255 elif _IsBuildSuccessful(build_data
):
256 return (OK
, results_url
)
257 return (PENDING
, results_url
)
260 def GetBuildNumFromBuilder(build_reason
, bot_name
, builder_host
, builder_port
):
261 """Gets build number on build status page for a given build reason.
263 It parses the JSON data from buildbot page and collect basic information
264 about the all the builds and then this uniquely identifies the build based
265 on the 'reason' attribute in builds's JSON data.
266 The 'reason' attribute set while a build request is posted, and same is used
267 to identify the build on status page.
270 build_reason: A unique build name set to build on tryserver.
271 bot_name: Name of the bot where the build information is scanned.
272 builder_host: Hostname of the server where the builder is hosted.
273 builder_port: Port number of ther server where the builder is hosted.
276 A build number as a string if found, otherwise None.
278 # Gets the buildbot url for the given host and port.
279 server_url
= _GetBuildBotUrl(builder_host
, builder_port
)
280 buildbot_url
= BUILDER_JSON_URL
% {'server_url': server_url
,
281 'bot_name': bot_name
,
284 builds_json
= _FetchBuilderData(buildbot_url
)
286 builds_data
= json
.loads(builds_json
)
287 for current_build
in builds_data
:
288 if builds_data
[current_build
].get('reason') == build_reason
:
289 return builds_data
[current_build
].get('number')
293 def _GetQueryParams(options
):
294 """Parses common query parameters which will be passed to PostTryJob.
297 options: The options object parsed from the command line.
300 A dictionary consists of query parameters.
302 values
= {'host': options
.host
,
303 'port': options
.port
,
304 'user': options
.user
,
308 values
['email'] = options
.email
310 values
['revision'] = options
.revision
312 values
['root'] = options
.root
314 values
['bot'] = options
.bot
316 values
['patch'] = options
.patch
321 """Parses the command line for posting build request."""
322 usage
= ('%prog [options]\n'
323 'Post a build request to the try server for the given revision.\n')
324 parser
= optparse
.OptionParser(usage
=usage
)
325 parser
.add_option('-H', '--host',
326 help='Host address of the try server.')
327 parser
.add_option('-P', '--port', type='int',
328 help='HTTP port of the try server.')
329 parser
.add_option('-u', '--user', default
=getpass
.getuser(),
331 help='Owner user name [default: %default]')
332 parser
.add_option('-e', '--email',
333 default
=os
.environ
.get('TRYBOT_RESULTS_EMAIL_ADDRESS',
334 os
.environ
.get('EMAIL_ADDRESS')),
335 help=('Email address where to send the results. Use either '
336 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment '
337 'variable or EMAIL_ADDRESS to set the email address '
338 'the try bots report results to [default: %default]'))
339 parser
.add_option('-n', '--name',
340 default
='try_job_http',
341 help='Descriptive name of the try job')
342 parser
.add_option('-b', '--bot',
343 help=('IMPORTANT: specify ONE builder per run is supported.'
344 'Run script for each builders separately.'))
345 parser
.add_option('-r', '--revision',
346 help=('Revision to use for the try job; default: the '
347 'revision will be determined by the try server; see '
348 'its waterfall for more info'))
349 parser
.add_option('--root',
350 help=('Root to use for the patch; base subdirectory for '
351 'patch created in a subdirectory'))
352 parser
.add_option('--patch',
353 help='Patch information.')
358 parser
= _GenParser()
359 options
, _
= parser
.parse_args()
361 raise ServerAccessError('Please use the --host option to specify the try '
362 'server host to connect to.')
364 raise ServerAccessError('Please use the --port option to specify the try '
365 'server port to connect to.')
366 params
= _GetQueryParams(options
)
370 if __name__
== '__main__':
371 sys
.exit(Main(sys
.argv
))