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 31from webkitpy.common.net.networktransaction import NetworkTransaction 32from webkitpy.common.system.deprecated_logging import log 33from webkitpy.thirdparty.autoinstalled.mechanize import Browser 34from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup 35 36import logging 37import urllib2 38 39 40_log = logging.getLogger("webkitpy.common.net.statusserver") 41 42 43class 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