Home | History | Annotate | Download | only in handlers
      1 # Copyright (C) 2013 Google Inc. All rights reserved.
      2 #
      3 # Redistribution and use in source and binary forms, with or without
      4 # modification, are permitted provided that the following conditions are
      5 # met:
      6 #
      7 #     * Redistributions of source code must retain the above copyright
      8 # notice, this list of conditions and the following disclaimer.
      9 #     * Redistributions in binary form must reproduce the above
     10 # copyright notice, this list of conditions and the following disclaimer
     11 # in the documentation and/or other materials provided with the
     12 # distribution.
     13 #     * Neither the name of Google Inc. nor the names of its
     14 # contributors may be used to endorse or promote products derived from
     15 # this software without specific prior written permission.
     16 #
     17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 
     29 import datetime
     30 import json
     31 import logging
     32 import sys
     33 import traceback
     34 import urllib2
     35 import webapp2
     36 
     37 from google.appengine.api import memcache
     38 
     39 MASTERS = [
     40     {'name': 'ChromiumWin', 'url': 'http://build.chromium.org/p/chromium.win', 'groups': ['@ToT Chromium']},
     41     {'name': 'ChromiumMac', 'url': 'http://build.chromium.org/p/chromium.mac', 'groups': ['@ToT Chromium']},
     42     {'name': 'ChromiumLinux', 'url': 'http://build.chromium.org/p/chromium.linux', 'groups': ['@ToT Chromium']},
     43     {'name': 'ChromiumChromiumOS', 'url': 'http://build.chromium.org/p/chromium.chromiumos', 'groups': ['@ToT ChromeOS']},
     44     {'name': 'ChromiumGPU', 'url': 'http://build.chromium.org/p/chromium.gpu', 'groups': ['@ToT Chromium']},
     45     {'name': 'ChromiumGPUFYI', 'url': 'http://build.chromium.org/p/chromium.gpu.fyi', 'groups': ['@ToT Chromium FYI']},
     46     {'name': 'ChromiumPerfAv', 'url': 'http://build.chromium.org/p/chromium.perf_av', 'groups': ['@ToT Chromium']},
     47     {'name': 'ChromiumWebkit', 'url': 'http://build.chromium.org/p/chromium.webkit', 'groups': ['@ToT Chromium', '@ToT Blink']},
     48     {'name': 'ChromiumFYI', 'url': 'http://build.chromium.org/p/chromium.fyi', 'groups': ['@ToT Chromium FYI']},
     49     {'name': 'V8', 'url': 'http://build.chromium.org/p/client.v8', 'groups': ['@ToT V8']},
     50 ]
     51 
     52 
     53 def master_json_url(master_url):
     54     return master_url + '/json/builders'
     55 
     56 
     57 def builder_json_url(master_url, builder):
     58     return master_json_url(master_url) + '/' + urllib2.quote(builder)
     59 
     60 
     61 def cached_build_json_url(master_url, builder, build_number):
     62     return builder_json_url(master_url, builder) + '/builds/' + str(build_number)
     63 
     64 
     65 def fetch_json(url):
     66     logging.debug('Fetching %s' % url)
     67     fetched_json = {}
     68     try:
     69         resp = urllib2.urlopen(url)
     70     except:
     71         exc_info = sys.exc_info()
     72         logging.warning('Error while fetching %s: %s', url, exc_info[1])
     73         return fetched_json
     74 
     75     try:
     76         fetched_json = json.load(resp)
     77     except:
     78         exc_info = sys.exc_info()
     79         logging.warning('Unable to parse JSON response from %s: %s', url, exc_info[1])
     80 
     81     return fetched_json
     82 
     83 
     84 def get_latest_build(build_data):
     85     cached_builds = []
     86     if 'cachedBuilds' in build_data:
     87         cached_builds = build_data['cachedBuilds']
     88 
     89     current_builds = build_data['currentBuilds']
     90 
     91     latest_cached_builds = set(cached_builds) - set(current_builds)
     92     if len(latest_cached_builds) != 0:
     93         latest_build = list(latest_cached_builds)[-1]
     94     elif len(current_builds) != 0:
     95         latest_build = current_builds[0]
     96     else:
     97         basedir = build_data['basedir'] if 'basedir' in build_data else 'current builder'
     98         logging.info('No cached or current builds for %s', basedir)
     99         return None
    100 
    101     return latest_build
    102 
    103 
    104 def dump_json(data):
    105     return json.dumps(data, separators=(',', ':'), sort_keys=True)
    106 
    107 
    108 def fetch_buildbot_data(masters):
    109     start_time = datetime.datetime.now()
    110     master_data = masters[:]
    111     for master in master_data:
    112         master_url = master['url']
    113         tests_object = master.setdefault('tests', {})
    114         master['tests'] = tests_object
    115 
    116         for builder in fetch_json(master_json_url(master_url)):
    117             build_data = fetch_json(builder_json_url(master_url, builder))
    118 
    119             latest_build = get_latest_build(build_data)
    120             if not latest_build:
    121                 logging.info('Skipping builder %s because it lacked cached or current builds.', builder)
    122                 continue
    123 
    124             build = fetch_json(cached_build_json_url(master_url, builder, latest_build))
    125             for step in build['steps']:
    126                 step_name = step['name']
    127                 is_test_step = 'test' in step_name and 'archive' not in step_name and 'Run tests' not in step_name
    128                 if not is_test_step:
    129                     continue
    130 
    131                 if step_name == 'webkit_tests':
    132                     step_name = 'layout-tests'
    133 
    134                 tests_object.setdefault(step_name, {'builders': []})
    135                 tests_object[step_name]['builders'].append(builder)
    136 
    137         for builders in tests_object.values():
    138             builders['builders'].sort()
    139 
    140     output_data = {'masters': master_data}
    141 
    142     delta = datetime.datetime.now() - start_time
    143 
    144     logging.info('Fetched buildbot data in %s seconds.', delta.seconds)
    145 
    146     return dump_json(output_data)
    147 
    148 
    149 class UpdateBuilders(webapp2.RequestHandler):
    150     """Fetch and update the cached buildbot data."""
    151     def get(self):
    152         buildbot_data = fetch_buildbot_data(MASTERS)
    153         memcache.set('buildbot_data', buildbot_data)
    154 
    155 
    156 class GetBuilders(webapp2.RequestHandler):
    157     """Fetch a list of masters mapped to their respective builders."""
    158     def get(self):
    159         callback = self.request.get('callback')
    160 
    161         buildbot_data = memcache.get('buildbot_data')
    162 
    163         if not buildbot_data:
    164             logging.warning('No buildbot data in memcache. If this message repeats, something is probably wrong with memcache.')
    165             buildbot_data = fetch_buildbot_data(MASTERS)
    166             try:
    167                 memcache.set('buildbot_data', buildbot_data)
    168             except ValueError, err:
    169                 logging.error(str(err))
    170 
    171         if callback:
    172             buildbot_data = callback + '(' + buildbot_data + ');'
    173 
    174         self.response.out.write(buildbot_data)
    175