2 # Copyright (C) 2012 Google Inc. All rights reserved.
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
14 # * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 from google.appengine.api import memcache
32 from google.appengine.ext import db
37 from datetime import datetime
39 from models import Builder
40 from models import Branch
41 from models import Build
42 from models import NumericIdHolder
43 from models import Platform
44 from models import ReportLog
45 from models import Test
46 from models import TestResult
47 from models import create_in_transaction_with_numeric_id_holder
50 class ReportHandler(webapp2.RequestHandler):
52 self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
54 headers = "\n".join([key + ': ' + value for key, value in self.request.headers.items()])
56 # Do as best as we can to remove the password
57 request_body_without_password = re.sub(r'"password"\s*:\s*".+?",', '', self.request.body)
58 log = ReportLog(timestamp=datetime.now(), headers=headers, payload=request_body_without_password)
62 self._body = json.loads(self.request.body)
64 return self._output('Failed to parse the payload as a json. Report key: %d' % log.key().id())
66 builder = self._model_by_key_name_in_body_or_error(Builder, 'builder-name')
67 branch = self._model_by_key_name_in_body_or_error(Branch, 'branch')
68 platform = self._model_by_key_name_in_body_or_error(Platform, 'platform')
69 build_number = self._integer_in_body('build-number')
70 revision = self._integer_in_body('revision')
71 timestamp = self._timestamp_in_body()
74 if builder and not (self.bypass_authentication() or builder.authenticate(self._body.get('password', ''))):
75 self._output('Authentication failed')
78 if not self._results_are_valid():
79 self._output("The payload doesn't contain results or results are malformed")
82 if not (builder and branch and platform and build_number and revision and timestamp) or failed:
85 build = self._create_build_if_possible(builder, build_number, branch, platform, revision, timestamp)
89 for test_name, result in self._body['results'].iteritems():
90 test = self._add_test_if_needed(test_name, branch, platform)
91 memcache.delete(Test.cache_key(test.id, branch.id, platform.id))
92 if isinstance(result, dict):
93 TestResult(name=test_name, build=build, value=float(result.get('avg', 0)), valueMedian=float(result.get('median', 0)),
94 valueStdev=float(result.get('stdev', 0)), valueMin=float(result.get('min', 0)), valueMax=float(result.get('max', 0))).put()
96 TestResult(name=test_name, build=build, value=float(result)).put()
98 log = ReportLog.get(log.key())
101 # We need to update dashboard and manifest because they are affected by the existance of test results
102 memcache.delete('dashboard')
103 memcache.delete('manifest')
105 return self._output('OK')
107 def _model_by_key_name_in_body_or_error(self, model, keyName):
108 key = self._body.get(keyName, '')
109 instance = key and model.get_by_key_name(key)
111 self._output('There are no %s named "%s"' % (model.__name__.lower(), key))
114 def _integer_in_body(self, key):
115 value = self._body.get(key, '')
119 return self._output('Invalid %s: "%s"' % (key.replace('-', ' '), value))
121 def _timestamp_in_body(self):
122 value = self._body.get('timestamp', '')
124 return datetime.fromtimestamp(int(value))
126 return self._output('Failed to parse the timestamp: %s' % value)
128 def _output(self, message):
129 self.response.out.write(message + '\n')
131 def bypass_authentication(self):
134 def _results_are_valid(self):
136 def _is_float_convertible(value):
143 if 'results' not in self._body or not isinstance(self._body['results'], dict):
146 for testResult in self._body['results'].values():
147 if isinstance(testResult, dict):
148 for value in testResult.values():
149 if not _is_float_convertible(value):
151 if 'avg' not in testResult:
154 if not _is_float_convertible(testResult):
159 def _create_build_if_possible(self, builder, build_number, branch, platform, revision, timestamp):
160 key_name = builder.name + ':' + str(int(time.mktime(timestamp.timetuple())))
163 build = Build.get_by_key_name(key_name)
165 return self._output('The build at %s already exists for %s' % (str(timestamp), builder.name))
167 return Build(branch=branch, platform=platform, builder=builder, buildNumber=build_number,
168 timestamp=timestamp, revision=revision, key_name=key_name).put()
169 return db.run_in_transaction(execute)
171 def _add_test_if_needed(self, test_name, branch, platform):
174 test = Test.get_by_key_name(test_name)
177 test = Test(id=id, name=test_name, key_name=test_name)
179 if branch.key() not in test.branches:
180 test.branches.append(branch.key())
181 if platform.key() not in test.platforms:
182 test.platforms.append(platform.key())
185 return create_in_transaction_with_numeric_id_holder(execute) or Test.get_by_key_name(test_name)
188 class AdminReportHandler(ReportHandler):
189 def bypass_authentication(self):