1 # Copyright (C) 2009 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 # This the client designed to talk to Tools/QueueStatusServer. 30 31 from webkitpy.common.net.networktransaction import NetworkTransaction 32 from webkitpy.common.system.deprecated_logging import log 33 from webkitpy.thirdparty.autoinstalled.mechanize import Browser 34 from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup 35 36 import logging 37 import urllib2 38 39 40 _log = logging.getLogger("webkitpy.common.net.statusserver") 41 42 43 class StatusServer: 44 # FIXME: This should probably move to common.config.urls. 45 default_host = "queues.webkit.org" 46 47 def __init__(self, host=default_host, browser=None, bot_id=None): 48 self.set_host(host) 49 self._browser = browser or Browser() 50 self.set_bot_id(bot_id) 51 52 def set_host(self, host): 53 self.host = host 54 self.url = "http://%s" % self.host 55 56 def set_bot_id(self, bot_id): 57 self.bot_id = bot_id 58 59 def results_url_for_status(self, status_id): 60 return "%s/results/%s" % (self.url, status_id) 61 62 def _add_patch(self, patch): 63 if not patch: 64 return 65 if patch.bug_id(): 66 self._browser["bug_id"] = unicode(patch.bug_id()) 67 if patch.id(): 68 self._browser["patch_id"] = unicode(patch.id()) 69 70 def _add_results_file(self, results_file): 71 if not results_file: 72 return 73 self._browser.add_file(results_file, "text/plain", "results.txt", 'results_file') 74 75 # 500 is the AppEngine limit for TEXT fields (which most of our fields are). 76 # Exceeding the limit will result in a 500 error from the server. 77 def _set_field(self, field_name, value, limit=500): 78 if len(value) > limit: 79 _log.warn("Attempted to set %s to value exceeding %s characters, truncating." % (field_name, limit)) 80 self._browser[field_name] = value[:limit] 81 82 def _post_status_to_server(self, queue_name, status, patch, results_file): 83 if results_file: 84 # We might need to re-wind the file if we've already tried to post it. 85 results_file.seek(0) 86 87 update_status_url = "%s/update-status" % self.url 88 self._browser.open(update_status_url) 89 self._browser.select_form(name="update_status") 90 self._browser["queue_name"] = queue_name 91 if self.bot_id: 92 self._browser["bot_id"] = self.bot_id 93 self._add_patch(patch) 94 self._set_field("status", status, limit=500) 95 self._add_results_file(results_file) 96 return self._browser.submit().read() # This is the id of the newly created status object. 97 98 def _post_svn_revision_to_server(self, svn_revision_number, broken_bot): 99 update_svn_revision_url = "%s/update-svn-revision" % self.url 100 self._browser.open(update_svn_revision_url) 101 self._browser.select_form(name="update_svn_revision") 102 self._browser["number"] = unicode(svn_revision_number) 103 self._browser["broken_bot"] = broken_bot 104 return self._browser.submit().read() 105 106 def _post_work_items_to_server(self, queue_name, work_items): 107 update_work_items_url = "%s/update-work-items" % self.url 108 self._browser.open(update_work_items_url) 109 self._browser.select_form(name="update_work_items") 110 self._browser["queue_name"] = queue_name 111 work_items = map(unicode, work_items) # .join expects strings 112 self._browser["work_items"] = " ".join(work_items) 113 return self._browser.submit().read() 114 115 def _post_work_item_to_ews(self, attachment_id): 116 submit_to_ews_url = "%s/submit-to-ews" % self.url 117 self._browser.open(submit_to_ews_url) 118 self._browser.select_form(name="submit_to_ews") 119 self._browser["attachment_id"] = unicode(attachment_id) 120 self._browser.submit() 121 122 def submit_to_ews(self, attachment_id): 123 _log.info("Submitting attachment %s to EWS queues" % attachment_id) 124 return NetworkTransaction().run(lambda: self._post_work_item_to_ews(attachment_id)) 125 126 def next_work_item(self, queue_name): 127 _log.debug("Fetching next work item for %s" % queue_name) 128 patch_status_url = "%s/next-patch/%s" % (self.url, queue_name) 129 return self._fetch_url(patch_status_url) 130 131 def _post_release_work_item(self, queue_name, patch): 132 release_patch_url = "%s/release-patch" % (self.url) 133 self._browser.open(release_patch_url) 134 self._browser.select_form(name="release_patch") 135 self._browser["queue_name"] = queue_name 136 self._browser["attachment_id"] = unicode(patch.id()) 137 self._browser.submit() 138 139 def release_work_item(self, queue_name, patch): 140 _log.info("Releasing work item %s from %s" % (patch.id(), queue_name)) 141 return NetworkTransaction(convert_404_to_None=True).run(lambda: self._post_release_work_item(queue_name, patch)) 142 143 def update_work_items(self, queue_name, work_items): 144 _log.debug("Recording work items: %s for %s" % (work_items, queue_name)) 145 return NetworkTransaction().run(lambda: self._post_work_items_to_server(queue_name, work_items)) 146 147 def update_status(self, queue_name, status, patch=None, results_file=None): 148 log(status) 149 return NetworkTransaction().run(lambda: self._post_status_to_server(queue_name, status, patch, results_file)) 150 151 def update_svn_revision(self, svn_revision_number, broken_bot): 152 log("SVN revision: %s broke %s" % (svn_revision_number, broken_bot)) 153 return NetworkTransaction().run(lambda: self._post_svn_revision_to_server(svn_revision_number, broken_bot)) 154 155 def _fetch_url(self, url): 156 # FIXME: This should use NetworkTransaction's 404 handling instead. 157 try: 158 return urllib2.urlopen(url).read() 159 except urllib2.HTTPError, e: 160 if e.code == 404: 161 return None 162 raise e 163 164 def patch_status(self, queue_name, patch_id): 165 patch_status_url = "%s/patch-status/%s/%s" % (self.url, queue_name, patch_id) 166 return self._fetch_url(patch_status_url) 167 168 def svn_revision(self, svn_revision_number): 169 svn_revision_url = "%s/svn-revision/%s" % (self.url, svn_revision_number) 170 return self._fetch_url(svn_revision_url) 171