Chromium Code Reviews| 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 for the perf try server. |
| 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 | 33 |
| 34 # Hostname of the tryserver where perf bisect builders are hosted. | 34 # 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. | 35 # From buildbot.status.builder. |
| 43 # See: http://docs.buildbot.net/current/developer/results.html | 36 # See: http://docs.buildbot.net/current/developer/results.html |
| 44 SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY, TRYPENDING = range(7) | 37 SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY, TRYPENDING = range(7) |
| 45 | |
| 46 OK = (SUCCESS, WARNINGS) # These indicate build is complete. | 38 OK = (SUCCESS, WARNINGS) # These indicate build is complete. |
| 47 FAILED = (FAILURE, EXCEPTION, SKIPPED) # These indicate build failure. | 39 FAILED = (FAILURE, EXCEPTION, SKIPPED) # These indicate build failure. |
| 48 PENDING = (RETRY, TRYPENDING) # These indicate in progress or in pending queue. | 40 PENDING = (RETRY, TRYPENDING) # These indicate in progress or in pending queue. |
| 49 | 41 |
| 50 | 42 |
| 51 class ServerAccessError(Exception): | 43 class ServerAccessError(Exception): |
| 52 | 44 |
| 53 def __str__(self): | 45 def __str__(self): |
| 54 return '%s\nSorry, cannot connect to server.' % self.args[0] | 46 return '%s\nSorry, cannot connect to server.' % self.args[0] |
| 55 | 47 |
| 56 | 48 |
| 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): | 49 def _IsBuildRunning(build_data): |
| 103 """Checks whether the build is in progress on buildbot. | 50 """Checks whether the build is in progress on buildbot. |
| 104 | 51 |
| 105 Presence of currentStep element in build JSON indicates build is in progress. | 52 Presence of currentStep element in build JSON indicates build is in progress. |
| 106 | 53 |
| 107 Args: | 54 Args: |
| 108 build_data: A dictionary with build data, loaded from buildbot JSON API. | 55 build_data: A dictionary with build data, loaded from buildbot JSON API. |
| 109 | 56 |
| 110 Returns: | 57 Returns: |
| 111 True if build is in progress, otherwise False. | 58 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. | 158 tryserver URL for the perf tryserver. |
| 212 | 159 |
| 213 Args: | 160 Args: |
| 214 builder_type: Determines what type of builder is used, e.g. "perf". | 161 builder_type: Determines what type of builder is used, e.g. "perf". |
| 215 | 162 |
| 216 Returns: | 163 Returns: |
| 217 URL of the buildbot as a string. | 164 URL of the buildbot as a string. |
| 218 """ | 165 """ |
| 219 if builder_type == fetch_build.PERF_BUILDER: | 166 if builder_type == fetch_build.PERF_BUILDER: |
| 220 return PERF_TRY_SERVER_URL | 167 return PERF_TRY_SERVER_URL |
| 221 else: | 168 if builder_type == fetch_build.FULL_BUILDER: |
| 222 raise NotImplementedError('Cannot yet get non-perf builds.') | 169 return 'http://build.chromium.org/p/tryserver.chromium.linux' |
|
prasadv
2015/01/27 22:54:31
May be using a constant for this will make it cons
| |
| 170 raise NotImplementedError('Unsupported builder type "%s".' % builder_type) | |
| 223 | 171 |
| 224 | 172 |
| 225 def GetBuildStatus(build_num, bot_name, builder_type): | 173 def GetBuildStatus(build_num, bot_name, builder_type): |
| 226 """Gets build status from the buildbot status page for a given build number. | 174 """Gets build status from the buildbot status page for a given build number. |
| 227 | 175 |
| 228 Args: | 176 Args: |
| 229 build_num: A build number on tryserver to determine its status. | 177 build_num: A build number on tryserver to determine its status. |
| 230 bot_name: Name of the bot where the build information is scanned. | 178 bot_name: Name of the bot where the build information is scanned. |
| 231 builder_type: Type of builder, e.g. "perf". | 179 builder_type: Type of builder, e.g. "perf". |
| 232 | 180 |
| 233 Returns: | 181 Returns: |
| 234 A pair which consists of build status (SUCCESS, FAILED or PENDING) and a | 182 A pair which consists of build status (SUCCESS, FAILED or PENDING) and a |
| 235 link to build status page on the waterfall. | 183 link to build status page on the waterfall. |
| 236 """ | 184 """ |
| 185 # TODO(prasadv, qyearsley): Make this a method of BuildArchive | |
| 186 # (which may be renamed to BuilderTryBot or Builder). | |
| 237 results_url = None | 187 results_url = None |
| 238 if build_num: | 188 if build_num: |
| 239 # Get the URL for requesting JSON data with status information. | 189 # Get the URL for requesting JSON data with status information. |
| 240 server_url = _GetBuildBotUrl(builder_type) | 190 server_url = _GetBuildBotUrl(builder_type) |
| 241 buildbot_url = BUILDER_JSON_URL % { | 191 buildbot_url = BUILDER_JSON_URL % { |
| 242 'server_url': server_url, | 192 'server_url': server_url, |
| 243 'bot_name': bot_name, | 193 'bot_name': bot_name, |
| 244 'build_num': build_num, | 194 'build_num': build_num, |
| 245 } | 195 } |
| 246 build_data = _GetBuildData(buildbot_url) | 196 build_data = _GetBuildData(buildbot_url) |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 270 to identify the build on status page. | 220 to identify the build on status page. |
| 271 | 221 |
| 272 Args: | 222 Args: |
| 273 build_reason: A unique build name set to build on tryserver. | 223 build_reason: A unique build name set to build on tryserver. |
| 274 bot_name: Name of the bot where the build information is scanned. | 224 bot_name: Name of the bot where the build information is scanned. |
| 275 builder_type: Type of builder, e.g. "perf". | 225 builder_type: Type of builder, e.g. "perf". |
| 276 | 226 |
| 277 Returns: | 227 Returns: |
| 278 A build number as a string if found, otherwise None. | 228 A build number as a string if found, otherwise None. |
| 279 """ | 229 """ |
| 230 # TODO(prasadv, qyearsley): Make this a method of BuildArchive | |
| 231 # (which may be renamed to BuilderTryBot or Builder). | |
| 280 # Gets the buildbot url for the given host and port. | 232 # Gets the buildbot url for the given host and port. |
| 281 server_url = _GetBuildBotUrl(builder_type) | 233 server_url = _GetBuildBotUrl(builder_type) |
| 282 buildbot_url = BUILDER_JSON_URL % { | 234 buildbot_url = BUILDER_JSON_URL % { |
| 283 'server_url': server_url, | 235 'server_url': server_url, |
| 284 'bot_name': bot_name, | 236 'bot_name': bot_name, |
| 285 'build_num': '_all', | 237 'build_num': '_all', |
| 286 } | 238 } |
| 287 builds_json = _FetchBuilderData(buildbot_url) | 239 builds_json = _FetchBuilderData(buildbot_url) |
| 288 if builds_json: | 240 if builds_json: |
| 289 builds_data = json.loads(builds_json) | 241 builds_data = json.loads(builds_json) |
| 290 for current_build in builds_data: | 242 for current_build in builds_data: |
| 291 if builds_data[current_build].get('reason') == build_reason: | 243 if builds_data[current_build].get('reason') == build_reason: |
| 292 return builds_data[current_build].get('number') | 244 return builds_data[current_build].get('number') |
| 293 return None | 245 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 |