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 29import datetime 30import logging 31import webapp2 32 33from google.appengine.ext import ndb 34from google.appengine.ext.webapp import template 35from google.appengine.ext.db import BadRequestError 36 37# A simple log server for rebaseline-o-matic. 38# 39# Accepts updates to the same log entry and shows a simple status page. 40# Has a special state for the case where there are no NeedsRebaseline 41# lines in TestExpectations to avoid cluttering the log with useless 42# entries every 30 seconds. 43# 44# Other than that, new updatelog calls append to the most recent log 45# entry until they have the newentry parameter, in which case, it 46# starts a new log entry. 47 48LOG_PARAM = "log" 49NEW_ENTRY_PARAM = "newentry" 50# FIXME: no_needs_rebaseline is never used anymore. Remove support for it. 51# Instead, add UI to logs.html to collapse short entries. 52NO_NEEDS_REBASELINE_PARAM = "noneedsrebaseline" 53NUM_LOGS_PARAM = "numlogs" 54BEFORE_PARAM = "before" 55 56 57class LogEntry(ndb.Model): 58 content = ndb.TextProperty() 59 date = ndb.DateTimeProperty(auto_now_add=True) 60 is_no_needs_rebaseline = ndb.BooleanProperty() 61 62 63def logs_query(): 64 return LogEntry.query().order(-LogEntry.date) 65 66 67class UpdateLog(webapp2.RequestHandler): 68 def post(self): 69 new_log_data = self.request.POST.get(LOG_PARAM) 70 # This entry is set to on whenever a new auto-rebaseline run is going to 71 # start logging entries. If this is not on, then the log will get appended 72 # to the most recent log entry. 73 new_entry = self.request.POST.get(NEW_ENTRY_PARAM) == "on" 74 # The case of no NeedsRebaseline lines in TestExpectations is special-cased 75 # to always overwrite the previous noneedsrebaseline entry in the log to 76 # avoid cluttering the log with useless empty posts. It just updates the 77 # date of the entry so that users can see that rebaseline-o-matic is still 78 # running. 79 # FIXME: no_needs_rebaseline is never used anymore. Remove support for it. 80 no_needs_rebaseline = self.request.POST.get(NO_NEEDS_REBASELINE_PARAM) == "on" 81 82 out = "Wrote new log entry." 83 if not new_entry or no_needs_rebaseline: 84 log_entries = logs_query().fetch(1) 85 if log_entries: 86 log_entry = log_entries[0] 87 log_entry.date = datetime.datetime.now() 88 if no_needs_rebaseline: 89 # Don't write out a new log entry for repeated no_needs_rebaseline cases. 90 # The repeated entries just add noise to the logs. 91 if log_entry.is_no_needs_rebaseline: 92 out = "Overwrote existing no needs rebaseline log." 93 else: 94 out = "Wrote new no needs rebaseline log." 95 new_entry = True 96 new_log_data = "" 97 elif log_entry.is_no_needs_rebaseline: 98 out = "Previous entry was a no need rebaseline log. Writing a new log." 99 new_entry = True 100 else: 101 out = "Added to existing log entry." 102 log_entry.content = log_entry.content + "\n" + new_log_data 103 104 if new_entry or not log_entries: 105 log_entry = LogEntry(content=new_log_data, is_no_needs_rebaseline=no_needs_rebaseline) 106 107 try: 108 log_entry.put() 109 except BadRequestError: 110 out = "Created new log entry because the previous one exceeded the max length." 111 LogEntry(content=new_log_data, is_no_needs_rebaseline=no_needs_rebaseline).put() 112 113 self.response.out.write(out) 114 115 116class UploadForm(webapp2.RequestHandler): 117 def get(self): 118 self.response.out.write(template.render("uploadform.html", { 119 "update_log_url": "/updatelog", 120 "set_no_needs_rebaseline_url": "/noneedsrebaselines", 121 "log_param": LOG_PARAM, 122 "new_entry_param": NEW_ENTRY_PARAM, 123 "no_needs_rebaseline_param": NO_NEEDS_REBASELINE_PARAM, 124 })) 125 126 127class ShowLatest(webapp2.RequestHandler): 128 def get(self): 129 query = logs_query() 130 131 before = self.request.get(BEFORE_PARAM) 132 if before: 133 date = datetime.datetime.strptime(before, "%Y-%m-%dT%H:%M:%SZ") 134 query = query.filter(LogEntry.date < date) 135 136 num_logs = self.request.get(NUM_LOGS_PARAM) 137 logs = query.fetch(int(num_logs) if num_logs else 3) 138 139 self.response.out.write(template.render("logs.html", { 140 "logs": logs, 141 "num_logs_param": NUM_LOGS_PARAM, 142 "before_param": BEFORE_PARAM, 143 })) 144 145 146routes = [ 147 ('/uploadform', UploadForm), 148 ('/updatelog', UpdateLog), 149 ('/', ShowLatest), 150] 151 152app = webapp2.WSGIApplication(routes, debug=True) 153