OLD | NEW |
1 # Copyright (C) 2013 Google Inc. All rights reserved. | 1 # Copyright (C) 2013 Google Inc. All rights reserved. |
2 # | 2 # |
3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
5 # met: | 5 # met: |
6 # | 6 # |
7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
10 # copyright notice, this list of conditions and the following disclaimer | 10 # copyright notice, this list of conditions and the following disclaimer |
(...skipping 20 matching lines...) Expand all Loading... |
31 import logging | 31 import logging |
32 import re | 32 import re |
33 import sys | 33 import sys |
34 import traceback | 34 import traceback |
35 import urllib2 | 35 import urllib2 |
36 import webapp2 | 36 import webapp2 |
37 | 37 |
38 from google.appengine.api import memcache | 38 from google.appengine.api import memcache |
39 | 39 |
40 MASTERS = [ | 40 MASTERS = [ |
41 {'name': 'ChromiumWin', 'url': 'http://build.chromium.org/p/chromium.win', '
groups': ['@ToT Chromium']}, | 41 {'name': 'ChromiumWin', 'url_name': 'chromium.win', 'groups': ['@ToT Chromiu
m']}, |
42 {'name': 'ChromiumMac', 'url': 'http://build.chromium.org/p/chromium.mac', '
groups': ['@ToT Chromium']}, | 42 {'name': 'ChromiumMac', 'url_name': 'chromium.mac', 'groups': ['@ToT Chromiu
m']}, |
43 {'name': 'ChromiumLinux', 'url': 'http://build.chromium.org/p/chromium.linux
', 'groups': ['@ToT Chromium']}, | 43 {'name': 'ChromiumLinux', 'url_name': 'chromium.linux', 'groups': ['@ToT Chr
omium']}, |
44 {'name': 'ChromiumChromiumOS', 'url': 'http://build.chromium.org/p/chromium.
chromiumos', 'groups': ['@ToT ChromeOS']}, | 44 {'name': 'ChromiumChromiumOS', 'url_name': 'chromium.chromiumos', 'groups':
['@ToT ChromeOS']}, |
45 {'name': 'ChromiumGPU', 'url': 'http://build.chromium.org/p/chromium.gpu', '
groups': ['@ToT Chromium']}, | 45 {'name': 'ChromiumGPU', 'url_name': 'chromium.gpu', 'groups': ['@ToT Chromiu
m']}, |
46 {'name': 'ChromiumGPUFYI', 'url': 'http://build.chromium.org/p/chromium.gpu.
fyi', 'groups': ['@ToT Chromium FYI']}, | 46 {'name': 'ChromiumGPUFYI', 'url_name': 'chromium.gpu.fyi', 'groups': ['@ToT
Chromium FYI']}, |
47 {'name': 'ChromiumWebkit', 'url': 'http://build.chromium.org/p/chromium.webk
it', 'groups': ['@ToT Chromium', '@ToT Blink']}, | 47 {'name': 'ChromiumWebkit', 'url_name': 'chromium.webkit', 'groups': ['@ToT C
hromium', '@ToT Blink']}, |
48 {'name': 'ChromiumFYI', 'url': 'http://build.chromium.org/p/chromium.fyi', '
groups': ['@ToT Chromium FYI']}, | 48 {'name': 'ChromiumFYI', 'url_name': 'chromium.fyi', 'groups': ['@ToT Chromiu
m FYI']}, |
49 {'name': 'V8', 'url': 'http://build.chromium.org/p/client.v8', 'groups': ['@
ToT V8']}, | 49 {'name': 'V8', 'url_name': 'client.v8', 'groups': ['@ToT V8']}, |
50 ] | 50 ] |
51 | 51 |
52 # Buildbot steps that have test in the name, but don't run tests. | 52 # Buildbot steps that have test in the name, but don't run tests. |
53 NON_TEST_STEP_NAMES = [ | 53 NON_TEST_STEP_NAMES = [ |
54 'archive', | 54 'archive', |
55 'Run tests', | 55 'Run tests', |
56 'find isolated tests', | 56 'find isolated tests', |
57 'read test spec', | 57 'read test spec', |
58 'Download latest chromedriver', | 58 'Download latest chromedriver', |
59 'compile tests', | 59 'compile tests', |
(...skipping 10 matching lines...) Expand all Loading... |
70 'python_tests(chrome', | 70 'python_tests(chrome', |
71 'run_all_tests.py', | 71 'run_all_tests.py', |
72 'test_report', | 72 'test_report', |
73 'test CronetSample', | 73 'test CronetSample', |
74 'test_mini_installer', | 74 'test_mini_installer', |
75 'telemetry_unittests', | 75 'telemetry_unittests', |
76 'webkit_python_tests', | 76 'webkit_python_tests', |
77 'webkit_unit_tests', | 77 'webkit_unit_tests', |
78 ] | 78 ] |
79 | 79 |
80 class FetchBuildersException(Exception): pass | 80 BUILDS_URL = 'http://chrome-build-extract.appspot.com/get_builds?builder=%s&mast
er=%s&num_builds=1' |
| 81 MASTER_URL = 'http://chrome-build-extract.appspot.com/get_master/%s' |
81 | 82 |
82 | 83 |
83 def master_json_url(master_url): | 84 class FetchBuildersException(Exception): |
84 return master_url + '/json/builders' | 85 pass |
85 | |
86 | |
87 def builder_json_url(master_url, builder): | |
88 return master_json_url(master_url) + '/' + urllib2.quote(builder) | |
89 | |
90 | |
91 def cached_build_json_url(master_url, builder, build_number): | |
92 return builder_json_url(master_url, builder) + '/builds/' + str(build_number
) | |
93 | 86 |
94 | 87 |
95 def fetch_json(url): | 88 def fetch_json(url): |
96 logging.debug('Fetching %s' % url) | 89 logging.debug('Fetching %s' % url) |
97 fetched_json = {} | 90 fetched_json = {} |
98 try: | 91 try: |
99 resp = urllib2.urlopen(url) | 92 resp = urllib2.urlopen(url) |
100 except: | 93 except: |
101 exc_info = sys.exc_info() | 94 exc_info = sys.exc_info() |
102 logging.warning('Error while fetching %s: %s', url, exc_info[1]) | 95 logging.warning('Error while fetching %s: %s', url, exc_info[1]) |
103 return fetched_json | 96 return fetched_json |
104 | 97 |
105 try: | 98 try: |
106 fetched_json = json.load(resp) | 99 fetched_json = json.load(resp) |
107 except: | 100 except: |
108 exc_info = sys.exc_info() | 101 exc_info = sys.exc_info() |
109 logging.warning('Unable to parse JSON response from %s: %s', url, exc_in
fo[1]) | 102 logging.warning('Unable to parse JSON response from %s: %s', url, exc_in
fo[1]) |
110 | 103 |
111 return fetched_json | 104 return fetched_json |
112 | 105 |
113 | 106 |
114 def get_latest_build(build_data): | |
115 cached_builds = [] | |
116 if 'cachedBuilds' in build_data: | |
117 cached_builds = build_data['cachedBuilds'] | |
118 | |
119 current_builds = build_data['currentBuilds'] | |
120 | |
121 latest_cached_builds = set(cached_builds) - set(current_builds) | |
122 if len(latest_cached_builds) != 0: | |
123 latest_cached_builds = sorted(list(latest_cached_builds)) | |
124 latest_build = latest_cached_builds[-1] | |
125 elif len(current_builds) != 0: | |
126 latest_build = current_builds[0] | |
127 else: | |
128 basedir = build_data['basedir'] if 'basedir' in build_data else 'current
builder' | |
129 logging.info('No cached or current builds for %s', basedir) | |
130 return None | |
131 | |
132 return latest_build | |
133 | |
134 | |
135 def dump_json(data): | 107 def dump_json(data): |
136 return json.dumps(data, separators=(',', ':'), sort_keys=True) | 108 return json.dumps(data, separators=(',', ':'), sort_keys=True) |
137 | 109 |
138 | 110 |
139 def fetch_buildbot_data(masters, force_update=False): | 111 def fetch_buildbot_data(masters): |
140 if force_update: | |
141 logging.info('Starting a forced buildbot update. Failure to fetch a mast
er\'s data will not abort the fetch.') | |
142 | |
143 start_time = datetime.datetime.now() | 112 start_time = datetime.datetime.now() |
144 master_data = masters[:] | 113 master_data = masters[:] |
145 for master in master_data: | 114 for master in master_data: |
146 master_url = master['url'] | 115 master_url = MASTER_URL % master['url_name'] |
| 116 builders = fetch_json(master_url) |
| 117 if not builders: |
| 118 msg = 'Aborting fetch. Could not fetch builders from master "%s": %s
.' % (master['name'], master_url) |
| 119 logging.warning(msg) |
| 120 raise FetchBuildersException(msg) |
| 121 |
147 tests_object = master.setdefault('tests', {}) | 122 tests_object = master.setdefault('tests', {}) |
148 master['tests'] = tests_object | |
149 | 123 |
150 builders = fetch_json(master_json_url(master_url)) | 124 for builder in builders['builders'].keys(): |
151 if not builders: | 125 build = fetch_json(BUILDS_URL % (urllib2.quote(builder), master['url
_name'])) |
152 msg = 'Could not fetch builders from master "%s": %s.' % (master['na
me'], master_url) | 126 if not build: |
153 logging.warning(msg) | 127 logging.info('Skipping builder %s on master %s due to empty data
.', builder, master['url_name']) |
154 if force_update: | |
155 continue | |
156 else: | |
157 logging.warning('Aborting fetch.') | |
158 raise FetchBuildersException(msg) | |
159 | |
160 for builder in builders: | |
161 build_data = fetch_json(builder_json_url(master_url, builder)) | |
162 | |
163 latest_build = get_latest_build(build_data) | |
164 if not latest_build: | |
165 logging.info('Skipping builder %s because it lacked cached or cu
rrent builds.', builder) | |
166 continue | 128 continue |
167 | 129 |
168 build = fetch_json(cached_build_json_url(master_url, builder, latest
_build)) | 130 if not build['builds']: |
169 if not build: | 131 logging.info('Skipping builder %s on master %s due to empty buil
ds list.', builder, master['url_name']) |
170 logging.info('Skipping build %s on builder %s due to empty data'
, latest_build, builder) | 132 continue |
171 for step in build['steps']: | 133 |
| 134 for step in build['builds'][0]['steps']: |
172 step_name = step['name'] | 135 step_name = step['name'] |
173 | 136 |
174 if not 'test' in step_name: | 137 if not 'test' in step_name: |
175 continue | 138 continue |
176 | 139 |
177 if any(name in step_name for name in NON_TEST_STEP_NAMES): | 140 if any(name in step_name for name in NON_TEST_STEP_NAMES): |
178 continue | 141 continue |
179 | 142 |
180 if re.search('_only|_ignore|_perf$', step_name): | 143 if re.search('_only|_ignore|_perf$', step_name): |
181 continue | 144 continue |
(...skipping 12 matching lines...) Expand all Loading... |
194 delta = datetime.datetime.now() - start_time | 157 delta = datetime.datetime.now() - start_time |
195 | 158 |
196 logging.info('Fetched buildbot data in %s seconds.', delta.seconds) | 159 logging.info('Fetched buildbot data in %s seconds.', delta.seconds) |
197 | 160 |
198 return dump_json(output_data) | 161 return dump_json(output_data) |
199 | 162 |
200 | 163 |
201 class UpdateBuilders(webapp2.RequestHandler): | 164 class UpdateBuilders(webapp2.RequestHandler): |
202 """Fetch and update the cached buildbot data.""" | 165 """Fetch and update the cached buildbot data.""" |
203 def get(self): | 166 def get(self): |
204 force_update = True if self.request.get('force') else False | |
205 try: | 167 try: |
206 buildbot_data = fetch_buildbot_data(MASTERS, force_update) | 168 buildbot_data = fetch_buildbot_data(MASTERS) |
207 memcache.set('buildbot_data', buildbot_data) | 169 memcache.set('buildbot_data', buildbot_data) |
208 self.response.set_status(200) | 170 self.response.set_status(200) |
209 self.response.out.write("ok") | 171 self.response.out.write("ok") |
210 except FetchBuildersException, ex: | 172 except FetchBuildersException, ex: |
211 logging.error('Not updating builders because fetch failed: %s', str(
ex)) | 173 logging.error('Not updating builders because fetch failed: %s', str(
ex)) |
212 self.response.set_status(500) | 174 self.response.set_status(500) |
213 self.response.out.write(ex.message) | 175 self.response.out.write(ex.message) |
214 | 176 |
215 | 177 |
216 | |
217 class GetBuilders(webapp2.RequestHandler): | 178 class GetBuilders(webapp2.RequestHandler): |
218 """Return a list of masters mapped to their respective builders, possibly us
ing cached data.""" | 179 """Return a list of masters mapped to their respective builders, possibly us
ing cached data.""" |
219 def get(self): | 180 def get(self): |
220 callback = self.request.get('callback') | |
221 | |
222 buildbot_data = memcache.get('buildbot_data') | 181 buildbot_data = memcache.get('buildbot_data') |
223 | 182 |
224 if not buildbot_data: | 183 if not buildbot_data: |
225 logging.warning('No buildbot data in memcache. If this message repea
ts, something is probably wrong with memcache.') | 184 logging.warning('No buildbot data in memcache. If this message repea
ts, something is probably wrong with memcache.') |
| 185 try: |
| 186 buildbot_data = fetch_buildbot_data(MASTERS) |
| 187 memcache.set('buildbot_data', buildbot_data) |
| 188 except FetchBuildersException, ex: |
| 189 logging.error('Builders fetch failed: %s', str(ex)) |
| 190 self.response.set_status(500) |
| 191 self.response.out.write(ex.message) |
| 192 return |
226 | 193 |
227 # Since we have no cached buildbot data, we would rather have missin
g masters than no data at all. | 194 callback = self.request.get('callback') |
228 buildbot_data = fetch_buildbot_data(MASTERS, True) | |
229 try: | |
230 memcache.set('buildbot_data', buildbot_data) | |
231 except ValueError, err: | |
232 logging.error(str(err)) | |
233 | |
234 if callback: | 195 if callback: |
235 buildbot_data = callback + '(' + buildbot_data + ');' | 196 buildbot_data = callback + '(' + buildbot_data + ');' |
236 | 197 |
237 self.response.out.write(buildbot_data) | 198 self.response.out.write(buildbot_data) |
OLD | NEW |