1a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
2a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
3a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# found in the LICENSE file.
4a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
56e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)"""This module contains functionality for starting build try jobs via HTTP.
66e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
76e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)This includes both sending a request to start a job, and also related code
86e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)for querying the status of the job.
96e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
106e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)This module can be either run as a stand-alone script to send a request to a
116e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)builder, or imported and used by calling the public functions below.
126e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)"""
13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import getpass
15cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)import json
16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import optparse
17a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import os
18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import sys
19a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import urllib
20a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import urllib2
21a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
226e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)# URL template for fetching JSON data about builds.
23cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)BUILDER_JSON_URL = ('%(server_url)s/json/builders/%(bot_name)s/builds/'
24cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                    '%(build_num)s?as_text=1&filter=0')
25cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
266e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)# URL template for displaying build steps.
27cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)BUILDER_HTML_URL = ('%(server_url)s/builders/%(bot_name)s/builds/%(build_num)s')
28cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
296e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)# Try server status page for the perf try server.
306e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)PERF_TRY_SERVER_URL = 'http://build.chromium.org/p/tryserver.chromium.perf'
31cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
326e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)# Hostname of the tryserver where perf bisect builders are hosted.
336e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)# This is used for posting build requests to the tryserver.
346e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)PERF_BISECT_BUILDER_HOST = 'master4.golo.chromium.org'
35cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
366e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)# The default 'try_job_port' on tryserver to post build request.
376e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)PERF_BISECT_BUILDER_PORT = 8341
38cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
396e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)# Status codes that can be returned by the GetBuildStatus method.
40cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# From buildbot.status.builder.
41cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# See: http://docs.buildbot.net/current/developer/results.html
42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY, TRYPENDING = range(7)
43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
446e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)OK = (SUCCESS, WARNINGS)  # These indicate build is complete.
456e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)FAILED = (FAILURE, EXCEPTION, SKIPPED)  # These indicate build failure.
466e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)PENDING = (RETRY, TRYPENDING)  # These indicate in progress or in pending queue.
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
48a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
49a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class ServerAccessError(Exception):
50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
51a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def __str__(self):
52a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return '%s\nSorry, cannot connect to server.' % self.args[0]
53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
54a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
556e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)def PostTryJob(host, port, params):
56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  """Sends a build request to the server using the HTTP protocol.
57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
586e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  The required parameters are:
591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    'revision': "src@rev", where rev is a git hash or SVN revision.
606e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    'bot': Name of builder bot to use, e.g. "win_perf_bisect_builder".
616e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  Args:
636e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    host: Hostname of the try server.
646e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    port: Port of the try server.
656e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    params: A dictionary of parameters to be sent in the POST request.
666e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
67a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  Returns:
686e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    True if the request is posted successfully.
696e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
706e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  Raises:
716e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    ServerAccessError: Failed to make a request to the try server.
726e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    ValueError: Not all of the expected inputs were given.
73a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  """
746e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  if not params.get('revision'):
756e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    raise ValueError('Missing revision number.')
766e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  if not params.get('bot'):
776e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    raise ValueError('Missing bot name.')
786e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
796e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  base_url = 'http://%s:%s/send_try_patch' % (host, port)
806e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  print 'Posting build request by HTTP.'
816e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  print 'URL: %s, params: %s' % (base_url, params)
82a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
83a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  connection = None
84a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  try:
85a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    print 'Opening connection...'
866e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    connection = urllib2.urlopen(base_url, urllib.urlencode(params))
87a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    print 'Done, request sent to server to produce build.'
886e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  except IOError as e:
896e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    raise ServerAccessError('%s is unaccessible. Reason: %s' % (base_url, e))
90a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if not connection:
916e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    raise ServerAccessError('%s is unaccessible.' % base_url)
926e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
93a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  response = connection.read()
946e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  print 'Received response from server: %s' % response
95a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if response != 'OK':
966e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    raise ServerAccessError('Response was not "OK".')
97a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return True
98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def _IsBuildRunning(build_data):
101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """Checks whether the build is in progress on buildbot.
102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Presence of currentStep element in build JSON indicates build is in progress.
104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Args:
106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    build_data: A dictionary with build data, loaded from buildbot JSON API.
107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Returns:
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    True if build is in progress, otherwise False.
110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """
111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  current_step = build_data.get('currentStep')
112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (current_step and current_step.get('isStarted') and
113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      current_step.get('results') is None):
114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return True
115cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return False
116cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
117cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
118cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def _IsBuildFailed(build_data):
119cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """Checks whether the build failed on buildbot.
120cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
121cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Sometime build status is marked as failed even though compile and packaging
122cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  steps are successful. This may happen due to some intermediate steps of less
123cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  importance such as gclient revert, generate_telemetry_profile are failed.
124cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Therefore we do an addition check to confirm if build was successful by
125cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  calling _IsBuildSuccessful.
126cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
127cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Args:
128cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    build_data: A dictionary with build data, loaded from buildbot JSON API.
129cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
130cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Returns:
131cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    True if revision is failed build, otherwise False.
132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """
133cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if (build_data.get('results') in FAILED and
134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      not _IsBuildSuccessful(build_data)):
135cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return True
136cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return False
137cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
138cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
139cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def _IsBuildSuccessful(build_data):
140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """Checks whether the build succeeded on buildbot.
141cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
142cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  We treat build as successful if the package_build step is completed without
143cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  any error i.e., when results attribute of the this step has value 0 or 1
144cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  in its first element.
145cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
146cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Args:
147cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    build_data: A dictionary with build data, loaded from buildbot JSON API.
148cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
149cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Returns:
150cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    True if revision is successfully build, otherwise False.
151cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """
152cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if build_data.get('steps'):
153cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for item in build_data.get('steps'):
154cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      # The 'results' attribute of each step consists of two elements,
155cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      # results[0]: This represents the status of build step.
156cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      # See: http://docs.buildbot.net/current/developer/results.html
157cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      # results[1]: List of items, contains text if step fails, otherwise empty.
158cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if (item.get('name') == 'package_build' and
159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          item.get('isFinished') and
160cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          item.get('results')[0] in OK):
161cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        return True
162cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return False
163cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
164cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
165cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def _FetchBuilderData(builder_url):
166cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """Fetches JSON data for the all the builds from the tryserver.
167cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
168cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Args:
169cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    builder_url: A tryserver URL to fetch builds information.
170cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
171cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Returns:
172cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    A dictionary with information of all build on the tryserver.
173cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """
174cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  data = None
175cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  try:
176cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    url = urllib2.urlopen(builder_url)
1776e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  except urllib2.URLError as e:
178cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    print ('urllib2.urlopen error %s, waterfall status page down.[%s]' % (
179cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        builder_url, str(e)))
180cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return None
181cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if url is not None:
182cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    try:
183cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      data = url.read()
1846e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    except IOError as e:
185cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      print 'urllib2 file object read error %s, [%s].' % (builder_url, str(e))
186cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return data
187cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
188cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
189cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def _GetBuildData(buildbot_url):
190cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """Gets build information for the given build id from the tryserver.
191cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
192cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Args:
193cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    buildbot_url: A tryserver URL to fetch build information.
194cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
195cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Returns:
196cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    A dictionary with build information if build exists, otherwise None.
197cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """
198cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  builds_json = _FetchBuilderData(buildbot_url)
199cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if builds_json:
200cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return json.loads(builds_json)
201cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return None
202cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
203cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
204cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def _GetBuildBotUrl(builder_host, builder_port):
2056e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  """Gets build bot URL for fetching build info.
206cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
2076e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  Bisect builder bots are hosted on tryserver.chromium.perf, though we cannot
2086e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  access this tryserver using host and port number directly, so we use another
2096e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  tryserver URL for the perf tryserver.
210cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
211cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Args:
212cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    builder_host: Hostname of the server where the builder is hosted.
213cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    builder_port: Port number of ther server where the builder is hosted.
214cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
215cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Returns:
216cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    URL of the buildbot as a string.
217cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """
2186e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  if (builder_host == PERF_BISECT_BUILDER_HOST and
2196e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      builder_port == PERF_BISECT_BUILDER_PORT):
2206e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    return PERF_TRY_SERVER_URL
221cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  else:
222cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return 'http://%s:%s' % (builder_host, builder_port)
223cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
224cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
225cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def GetBuildStatus(build_num, bot_name, builder_host, builder_port):
226cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """Gets build status from the buildbot status page for a given build number.
227cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
228cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Args:
229cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    build_num: A build number on tryserver to determine its status.
230cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bot_name: Name of the bot where the build information is scanned.
231cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    builder_host: Hostname of the server where the builder is hosted.
2326e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    builder_port: Port number of the server where the builder is hosted.
233cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
234cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Returns:
2356e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    A pair which consists of build status (SUCCESS, FAILED or PENDING) and a
2366e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    link to build status page on the waterfall.
237cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """
238cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  results_url = None
23946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if build_num:
2406e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    # Get the URL for requesting JSON data with status information.
24146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    server_url = _GetBuildBotUrl(builder_host, builder_port)
2426e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    buildbot_url = BUILDER_JSON_URL % {
2436e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        'server_url': server_url,
2446e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        'bot_name': bot_name,
2456e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        'build_num': build_num,
2466e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
24746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    build_data = _GetBuildData(buildbot_url)
24846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if build_data:
24946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      # Link to build on the buildbot showing status of build steps.
2506e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      results_url = BUILDER_HTML_URL % {
2516e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          'server_url': server_url,
2526e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          'bot_name': bot_name,
2536e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          'build_num': build_num,
2546e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      }
25546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if _IsBuildFailed(build_data):
25646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        return (FAILED, results_url)
25746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
25846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      elif _IsBuildSuccessful(build_data):
25946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        return (OK, results_url)
260cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return (PENDING, results_url)
261cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
262cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
263cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def GetBuildNumFromBuilder(build_reason, bot_name, builder_host, builder_port):
2646e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  """Gets build number on build status page for a given 'build reason'.
2656e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
2666e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  This function parses the JSON data from buildbot page and collects basic
2676e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  information about the all the builds, and then uniquely identifies the build
2686e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  based on the 'reason' attribute in builds's JSON data.
269cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
2706e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  The 'reason' attribute set is when a build request is posted, and it is used
271cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  to identify the build on status page.
272cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
273cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Args:
274cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    build_reason: A unique build name set to build on tryserver.
275cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    bot_name: Name of the bot where the build information is scanned.
276cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    builder_host: Hostname of the server where the builder is hosted.
277cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    builder_port: Port number of ther server where the builder is hosted.
278cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
279cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Returns:
280cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    A build number as a string if found, otherwise None.
281cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """
282cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # Gets the buildbot url for the given host and port.
283cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  server_url = _GetBuildBotUrl(builder_host, builder_port)
2846e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  buildbot_url = BUILDER_JSON_URL % {
2856e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      'server_url': server_url,
2866e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      'bot_name': bot_name,
2876e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      'build_num': '_all',
2886e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  }
289cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  builds_json = _FetchBuilderData(buildbot_url)
290cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  if builds_json:
291cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    builds_data = json.loads(builds_json)
292cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for current_build in builds_data:
293cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if builds_data[current_build].get('reason') == build_reason:
294cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        return builds_data[current_build].get('number')
295cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  return None
296cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
297cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
2986e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)def _GetRequestParams(options):
2996e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  """Extracts request parameters which will be passed to PostTryJob.
300a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
301a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  Args:
302a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    options: The options object parsed from the command line.
303a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
304a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  Returns:
3056e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    A dictionary with parameters to pass to PostTryJob.
306a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  """
3076e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  params = {
3086e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      'user': options.user,
3096e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      'name': options.name,
3106e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  }
3116e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  # Add other parameters if they're available in the options object.
3126e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  for key in ['email', 'revision', 'root', 'bot', 'patch']:
3136e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    option = getattr(options, key)
3146e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    if option:
3156e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      params[key] = option
3166e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  return params
317a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
318a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
319a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def _GenParser():
3206e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  """Returns a parser for getting command line arguments."""
321a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  usage = ('%prog [options]\n'
3226e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)           'Post a build request to the try server for the given revision.')
323a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  parser = optparse.OptionParser(usage=usage)
324a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  parser.add_option('-H', '--host',
3256e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    help='Host address of the try server (required).')
326a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  parser.add_option('-P', '--port', type='int',
3276e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    help='HTTP port of the try server (required).')
328a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  parser.add_option('-u', '--user', default=getpass.getuser(),
329a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                    dest='user',
330a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                    help='Owner user name [default: %default]')
331a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  parser.add_option('-e', '--email',
332a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                    default=os.environ.get('TRYBOT_RESULTS_EMAIL_ADDRESS',
333a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                           os.environ.get('EMAIL_ADDRESS')),
334cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                    help=('Email address where to send the results. Use either '
335cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                          'the TRYBOT_RESULTS_EMAIL_ADDRESS environment '
336cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                          'variable or EMAIL_ADDRESS to set the email address '
337cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                          'the try bots report results to [default: %default]'))
338a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  parser.add_option('-n', '--name',
339cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                    default='try_job_http',
340a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                    help='Descriptive name of the try job')
341a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  parser.add_option('-b', '--bot',
3426e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    help=('Only one builder per run may be specified; to post '
3436e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                          'jobs on on multiple builders, run script for each '
3446e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                          'builder separately.'))
345a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  parser.add_option('-r', '--revision',
3466e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                    help=('Revision to use for the try job. The revision may '
3476e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                          'be determined by the try server; see its waterfall '
3486e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                          'for more info.'))
349a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  parser.add_option('--root',
350cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                    help=('Root to use for the patch; base subdirectory for '
3516e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                          'patch created in a subdirectory.'))
352a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  parser.add_option('--patch',
353cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                    help='Patch information.')
354a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return parser
355a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
356a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
3576e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)def Main(_):
3586e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  """Posts a try job based on command line parameters."""
359a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  parser = _GenParser()
360cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  options, _ = parser.parse_args()
3616e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  if not options.host or not options.port:
3626e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    parser.print_help()
3636e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    return 1
3646e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  params = _GetRequestParams(options)
3656e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  PostTryJob(options.host, options.port, params)
366a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
367a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
368a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)if __name__ == '__main__':
369a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  sys.exit(Main(sys.argv))
370