diff options
Diffstat (limited to 'WebKitTools/QueueStatusServer/handlers')
10 files changed, 243 insertions, 104 deletions
diff --git a/WebKitTools/QueueStatusServer/handlers/dashboard.py b/WebKitTools/QueueStatusServer/handlers/dashboard.py index 26de263..660c595 100644 --- a/WebKitTools/QueueStatusServer/handlers/dashboard.py +++ b/WebKitTools/QueueStatusServer/handlers/dashboard.py @@ -32,31 +32,16 @@ from google.appengine.ext import webapp from google.appengine.ext.webapp import template from model.attachment import Attachment -from model.queues import queues +from model.queues import Queue class Dashboard(webapp.RequestHandler): + # We may want to sort these? + _ordered_queues = Queue.all() + _header_names = [queue.short_name() for queue in _ordered_queues] - # FIXME: This list probably belongs as part of a Queue object in queues.py - # Arrays are bubble_name, queue_name - # FIXME: Can this be unified with StatusBubble._queues_to_display? - _queues_to_display = [ - ["Style", "style-queue"], - ["Cr-Linux", "chromium-ews"], - ["Qt", "qt-ews"], - ["Gtk", "gtk-ews"], - ["Mac", "mac-ews"], - ["Win", "win-ews"], - ["Commit", "commit-queue"], - ] - # Split the zipped list into component parts - _header_names, _ordered_queue_names = zip(*_queues_to_display) - - # This asserts that all of the queues listed above are valid queue names. - assert(reduce(operator.and_, map(lambda name: name in queues, _ordered_queue_names))) - - def _build_bubble(self, attachment, queue_name): - queue_status = attachment.status_for_queue(queue_name) + def _build_bubble(self, attachment, queue): + queue_status = attachment.status_for_queue(queue) bubble = { "status_class": attachment.state_from_queue_status(queue_status) if queue_status else "none", "status_date": queue_status.date if queue_status else None, @@ -67,7 +52,7 @@ class Dashboard(webapp.RequestHandler): row = { "bug_id": attachment.bug_id(), "attachment_id": attachment.id, - "bubbles": [self._build_bubble(attachment, queue_name) for queue_name in self._ordered_queue_names], + "bubbles": [self._build_bubble(attachment, queue) for queue in self._ordered_queues], } return row diff --git a/WebKitTools/QueueStatusServer/handlers/nextpatch.py b/WebKitTools/QueueStatusServer/handlers/nextpatch.py index edb702a..5f6d71d 100644 --- a/WebKitTools/QueueStatusServer/handlers/nextpatch.py +++ b/WebKitTools/QueueStatusServer/handlers/nextpatch.py @@ -26,26 +26,24 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from datetime import datetime + from google.appengine.ext import db from google.appengine.ext import webapp -from model.workitems import WorkItems -from model.activeworkitems import ActiveWorkItems -from model import queuestatus - -from datetime import datetime, timedelta +from model.queues import Queue class NextPatch(webapp.RequestHandler): - def _get_next_patch_id(self, queue_name): - work_items = WorkItems.all().filter("queue_name =", queue_name).get() - if not work_items: - return None - active_work_items = ActiveWorkItems.get_or_insert(key_name=queue_name, queue_name=queue_name) - return db.run_in_transaction(self._assign_patch, active_work_items.key(), work_items.item_ids) - + # FIXME: This should probably be a post, or an explict lock_patch + # since GET requests shouldn't really modify the datastore. def get(self, queue_name): - patch_id = self._get_next_patch_id(queue_name) + queue = Queue.queue_with_name(queue_name) + if not queue: + self.error(404) + return + # FIXME: Patch assignment should probably move into Queue. + patch_id = db.run_in_transaction(self._assign_patch, queue.active_work_items().key(), queue.work_items().item_ids) if not patch_id: self.error(404) return diff --git a/WebKitTools/QueueStatusServer/handlers/queuestatus.py b/WebKitTools/QueueStatusServer/handlers/queuestatus.py index 0259c37..5c31537 100644 --- a/WebKitTools/QueueStatusServer/handlers/queuestatus.py +++ b/WebKitTools/QueueStatusServer/handlers/queuestatus.py @@ -29,15 +29,15 @@ from google.appengine.ext import webapp from google.appengine.ext.webapp import template -from model.queues import queues, display_name_for_queue -from model.workitems import WorkItems -from model.activeworkitems import ActiveWorkItems +from model.queues import Queue from model import queuestatus class QueueStatus(webapp.RequestHandler): - def _rows_for_work_items(self, queued_items, active_items): + def _rows_for_work_items(self, queue): + queued_items = queue.work_items() + active_items = queue.active_work_items() if not queued_items: return [] rows = [] @@ -50,14 +50,17 @@ class QueueStatus(webapp.RequestHandler): return rows def get(self, queue_name): - queued_items = WorkItems.all().filter("queue_name =", queue_name).get() - active_items = ActiveWorkItems.all().filter("queue_name =", queue_name).get() - statuses = queuestatus.QueueStatus.all().filter("queue_name =", queue_name).order("-date").fetch(15) + queue_name = queue_name.lower() + queue = Queue.queue_with_name(queue_name) + if not queue: + self.error(404) + return status_groups = [] last_patch_id = None synthetic_patch_id_counter = 0 + statuses = queuestatus.QueueStatus.all().filter("queue_name =", queue.name()).order("-date").fetch(15) for status in statuses: patch_id = status.active_patch_id if not patch_id or last_patch_id != patch_id: @@ -69,8 +72,8 @@ class QueueStatus(webapp.RequestHandler): last_patch_id = patch_id template_values = { - "display_queue_name": display_name_for_queue(queue_name), - "work_item_rows": self._rows_for_work_items(queued_items, active_items), + "display_queue_name": queue.display_name(), + "work_item_rows": self._rows_for_work_items(queue), "status_groups": status_groups, } self.response.out.write(template.render("templates/queuestatus.html", template_values)) diff --git a/WebKitTools/QueueStatusServer/handlers/recentstatus.py b/WebKitTools/QueueStatusServer/handlers/recentstatus.py index e2b8c2f..fddc93a 100644 --- a/WebKitTools/QueueStatusServer/handlers/recentstatus.py +++ b/WebKitTools/QueueStatusServer/handlers/recentstatus.py @@ -31,23 +31,24 @@ import datetime from google.appengine.ext import webapp from google.appengine.ext.webapp import template -from model.queues import queues, display_name_for_queue +from model.queues import Queue from model.queuestatus import QueueStatus from model.workitems import WorkItems class QueueBubble(object): """View support class for recentstatus.html""" - def __init__(self, queue_name): - self._queue_name = queue_name - self._work_items = WorkItems.all().filter("queue_name =", queue_name).get() - self._last_status = QueueStatus.all().filter("queue_name =", queue_name).order("-date").get() + def __init__(self, queue): + self._queue = queue + self._work_items = queue.work_items() + self._last_status = QueueStatus.all().filter("queue_name =", queue.name()).order("-date").get() + # FIXME: name and display_name should be replaced by a .queue() accessor. def name(self): - return self._queue_name + return self._queue.name() def display_name(self): - return display_name_for_queue(self._queue_name) + return self._queue.display_name() def _last_status_date(self): if not self._last_status: @@ -88,6 +89,6 @@ class QueuesOverview(webapp.RequestHandler): def get(self): template_values = { - "queues": [QueueBubble(queue_name) for queue_name in queues], + "queues": [QueueBubble(queue) for queue in Queue.all()], } self.response.out.write(template.render("templates/recentstatus.html", template_values)) diff --git a/WebKitTools/QueueStatusServer/handlers/releasepatch.py b/WebKitTools/QueueStatusServer/handlers/releasepatch.py new file mode 100644 index 0000000..0e46e69 --- /dev/null +++ b/WebKitTools/QueueStatusServer/handlers/releasepatch.py @@ -0,0 +1,62 @@ +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from google.appengine.ext import webapp, db +from google.appengine.ext.webapp import template + +from handlers.updatebase import UpdateBase +from model.attachment import Attachment +from model.queues import Queue + + +class ReleasePatch(UpdateBase): + def get(self): + self.response.out.write(template.render("templates/releasepatch.html", None)) + + def post(self): + queue_name = self.request.get("queue_name") + # FIXME: This queue lookup should be shared between handlers. + queue = Queue.queue_with_name(queue_name) + if not queue: + self.error(404) + return + + attachment_id = self._int_from_request("attachment_id") + attachment = Attachment(attachment_id) + last_status = attachment.status_for_queue(queue) + + # Ideally we should use a transaction for the calls to + # WorkItems and ActiveWorkItems. + + # Only remove it from the queue if the last message is not a retry request. + # Allow removing it from the queue even if there is no last_status for easier testing. + if not last_status or not last_status.is_retry_request(): + queue.work_items().remove_work_item(attachment_id) + + # Always release the lock on the item. + queue.active_work_items().expire_item(attachment_id) diff --git a/WebKitTools/QueueStatusServer/handlers/statusbubble.py b/WebKitTools/QueueStatusServer/handlers/statusbubble.py index bfbe958..5690484 100644 --- a/WebKitTools/QueueStatusServer/handlers/statusbubble.py +++ b/WebKitTools/QueueStatusServer/handlers/statusbubble.py @@ -33,33 +33,18 @@ from google.appengine.ext.webapp import template from model.attachment import Attachment from model.workitems import WorkItems -from model.queues import queues, name_with_underscores +from model.queues import Queue class StatusBubble(webapp.RequestHandler): - # FIXME: This list probably belongs as part of a Queue object in queues.py - # Arrays are bubble_name, queue_name - _queues_to_display = [ - ["style", "style-queue"], - ["cr-linux", "chromium-ews"], - ["gtk", "gtk-ews"], - ["qt", "qt-ews"], - ["mac", "mac-ews"], - ["win", "win-ews"], - ] + _queues_to_display = [queue for queue in Queue.all() if queue.is_ews()] - # This asserts that all of the queues listed above are valid queue names. - assert(reduce(operator.and_, map(lambda name_pair: name_pair[1] in queues, _queues_to_display))) - - def _build_bubble(self, queue_name_pair, attachment): - bubble_name = queue_name_pair[0] - queue_name = queue_name_pair[1] - - queue_status = attachment.status_for_queue(queue_name) + def _build_bubble(self, queue, attachment): + queue_status = attachment.status_for_queue(queue) bubble = { - "name": bubble_name, + "name": queue.short_name().lower(), "attachment_id": attachment.id, - "queue_position": attachment.position_in_queue(queue_name), + "queue_position": attachment.position_in_queue(queue), "state": attachment.state_from_queue_status(queue_status) if queue_status else "none", "status": queue_status, } @@ -67,7 +52,7 @@ class StatusBubble(webapp.RequestHandler): def get(self, attachment_id): attachment = Attachment(int(attachment_id)) - bubbles = [self._build_bubble(name_pair, attachment) for name_pair in self._queues_to_display] + bubbles = [self._build_bubble(queue, attachment) for queue in self._queues_to_display] template_values = { "bubbles": bubbles, } diff --git a/WebKitTools/QueueStatusServer/handlers/statusbubble_unittest.py b/WebKitTools/QueueStatusServer/handlers/statusbubble_unittest.py new file mode 100644 index 0000000..3ffbdaf --- /dev/null +++ b/WebKitTools/QueueStatusServer/handlers/statusbubble_unittest.py @@ -0,0 +1,62 @@ +# Copyright (C) 2010 Google, Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Research in Motion Ltd. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import unittest + + +from handlers.statusbubble import StatusBubble +from model.queues import Queue + + +class MockAttachment(object): + def __init__(self): + self.id = 1 + + def status_for_queue(self, queue): + return None + + def position_in_queue(self, queue): + return 1 + + +class StatusBubbleTest(unittest.TestCase): + def test_build_bubble(self): + bubble = StatusBubble() + queue = Queue("mac-ews") + attachment = MockAttachment() + bubble_dict = bubble._build_bubble(queue, attachment) + # FIXME: assertDictEqual (in Python 2.7) would be better to use here. + self.assertEqual(bubble_dict["name"], "mac") + self.assertEqual(bubble_dict["attachment_id"], 1) + self.assertEqual(bubble_dict["queue_position"], 1) + self.assertEqual(bubble_dict["state"], "none") + self.assertEqual(bubble_dict["status"], None) + + +if __name__ == '__main__': + unittest.main() diff --git a/WebKitTools/QueueStatusServer/handlers/submittoews.py b/WebKitTools/QueueStatusServer/handlers/submittoews.py new file mode 100644 index 0000000..3ba4373 --- /dev/null +++ b/WebKitTools/QueueStatusServer/handlers/submittoews.py @@ -0,0 +1,64 @@ +# Copyright (C) 2010 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from google.appengine.ext import webapp, db +from google.appengine.ext.webapp import template + +from handlers.updatebase import UpdateBase +from model.attachment import Attachment +from model.queues import Queue + + +class SubmitToEWS(UpdateBase): + def get(self): + self.response.out.write(template.render("templates/submittoews.html", None)) + + def _should_add_to_ews_queue(self, queue, attachment): + # This assert() is here to make sure we're not submitting to the commit-queue. + # The commit-queue clients check each patch anyway, but there is not sense + # in adding things to the commit-queue when they won't be processed by it. + assert(queue.is_ews()) + latest_status = attachment.status_for_queue(queue) + if not latest_status: + return True + # Only ever re-submit to the EWS if the EWS specifically requested a retry. + # This allows us to restart the EWS feeder queue, without all r? patches + # being retried as a result of that restart! + # In some future version we might add a "force" button to allow the user + # to override this restriction. + return latest_status.is_retry_request() + + def _add_attachment_to_ews_queues(self, attachment): + for queue in Queue.all_ews(): # all_ews() currently includes the style-queue + if self._should_add_to_ews_queue(queue, attachment): + queue.work_items().add_work_item(attachment.id) + + def post(self): + attachment_id = self._int_from_request("attachment_id") + attachment = Attachment(attachment_id) + self._add_attachment_to_ews_queues(attachment) diff --git a/WebKitTools/QueueStatusServer/handlers/updatestatus.py b/WebKitTools/QueueStatusServer/handlers/updatestatus.py index 89858b6..7301101 100644 --- a/WebKitTools/QueueStatusServer/handlers/updatestatus.py +++ b/WebKitTools/QueueStatusServer/handlers/updatestatus.py @@ -31,10 +31,10 @@ from google.appengine.ext import webapp, db from google.appengine.ext.webapp import template from handlers.updatebase import UpdateBase -from model.activeworkitems import ActiveWorkItems from model.attachment import Attachment from model.queuestatus import QueueStatus + class UpdateStatus(UpdateBase): def get(self): self.response.out.write(template.render("templates/updatestatus.html", None)) @@ -49,7 +49,9 @@ class UpdateStatus(UpdateBase): bug_id = self._int_from_request("bug_id") patch_id = self._int_from_request("patch_id") queue_name = self.request.get("queue_name") + bot_id = self.request.get("bot_id") queue_status.queue_name = queue_name + queue_status.bot_id = bot_id queue_status.active_bug_id = bug_id queue_status.active_patch_id = patch_id queue_status.message = self.request.get("status") @@ -57,24 +59,8 @@ class UpdateStatus(UpdateBase): queue_status.results_file = db.Blob(str(results_file)) return queue_status - @staticmethod - def _expire_item(key, item_id): - active_work_items = db.get(key) - active_work_items.expire_item(item_id) - active_work_items.put() - - # FIXME: An explicit lock_release request would be cleaner than this magical "Retry" status. - def _update_active_work_items(self, queue_status): - if queue_status.message != "Retry": # From AbstractQueue._retry_status - return - active_items = ActiveWorkItems.all().filter("queue_name =", queue_status.queue_name).get() - if not active_items: - return - return db.run_in_transaction(self._expire_item, active_items.key(), queue_status.active_patch_id) - def post(self): queue_status = self._queue_status_from_request() queue_status.put() - self._update_active_work_items(queue_status) Attachment.dirty(queue_status.active_patch_id) self.response.out.write(queue_status.key().id()) diff --git a/WebKitTools/QueueStatusServer/handlers/updateworkitems.py b/WebKitTools/QueueStatusServer/handlers/updateworkitems.py index f91beb4..16a9d49 100644 --- a/WebKitTools/QueueStatusServer/handlers/updateworkitems.py +++ b/WebKitTools/QueueStatusServer/handlers/updateworkitems.py @@ -30,7 +30,7 @@ from google.appengine.ext import webapp, db from google.appengine.ext.webapp import template from handlers.updatebase import UpdateBase -from model.queues import queues +from model.queues import Queue from model.workitems import WorkItems from datetime import datetime @@ -40,16 +40,6 @@ class UpdateWorkItems(UpdateBase): def get(self): self.response.out.write(template.render("templates/updateworkitems.html", None)) - def _work_items_for_queue(self, queue_name): - if queue_name not in queues: - self.response.out.write("\"%s\" is not in queues %s" % (queue_name, queues)) - return None - work_items = WorkItems.all().filter("queue_name =", queue_name).get() - if not work_items: - work_items = WorkItems() - work_items.queue_name = queue_name - return work_items - def _parse_work_items_string(self, items_string): # Our parsing could be much more robust. item_strings = items_string.split(" ") if items_string else [] @@ -57,10 +47,13 @@ class UpdateWorkItems(UpdateBase): def _work_items_from_request(self): queue_name = self.request.get("queue_name") - work_items = self._work_items_for_queue(queue_name) - if not work_items: + queue = Queue.queue_with_name(queue_name) + if not queue: + self.response.out.write("\"%s\" is not in queues %s" % (queue_name, Queue.all())) return None + items_string = self.request.get("work_items") + work_items = queue.work_items() work_items.item_ids = self._parse_work_items_string(items_string) work_items.date = datetime.now() return work_items |