| # Copyright (C) 2013, 2014 Apple 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: |
| # 1. Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # 2. 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. |
| # |
| # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 datetime |
| import itertools |
| |
| from google.appengine.ext import webapp |
| from google.appengine.ext.webapp import template |
| |
| from model.patchlog import PatchLog |
| from model.queues import Queue |
| from model.queuestatus import QueueStatus |
| |
| # Fall back to simplejson, because we are still on Python 2.5. |
| try: |
| import json |
| except ImportError: |
| import simplejson as json |
| |
| |
| class QueueStatusJSON(webapp.RequestHandler): |
| def _rows_for_work_items(self, queue): |
| queued_items = queue.work_items() |
| active_items = queue.active_work_items() |
| if not queued_items: |
| return [] |
| |
| rows = [] |
| for item_id in queued_items.item_ids: |
| patchStatusQuery = QueueStatus.all().filter('queue_name =', queue.name()).filter('active_patch_id =', item_id).order('-date') |
| statuses = patchStatusQuery.fetch(1) |
| message = None |
| message_time = None |
| bug_id = None |
| results_url = None |
| if statuses: |
| message = statuses[0].message |
| message_time = statuses[0].date |
| bug_id = statuses[0].active_bug_id |
| results_url = self.request.host_url + "/results/" + str(statuses[0].key().id()) if statuses[0].results_file else None |
| |
| row = { |
| "attachment_id": item_id, |
| "bug_id": bug_id, |
| "active": active_items and active_items.time_for_item(item_id) != None, |
| "active_since": active_items and active_items.time_for_item(item_id), |
| "latest_message": message, |
| "latest_message_time": message_time, |
| "status_page": self.request.host_url + "/patch/" + str(item_id), |
| "latest_results": results_url, |
| } |
| |
| patch_log = PatchLog.lookup_if_exists(item_id, queue.name()) |
| if patch_log and patch_log.retry_count: |
| row["retry_count"] = patch_log.retry_count |
| |
| rows.append(row) |
| return rows |
| |
| def _bots(self, queue): |
| # First, collect all bots that ever served this queue. |
| bot_id_statuses = QueueStatus.all(projection=['bot_id'], distinct=True).filter('queue_name =', queue.name()).order('-date').fetch(500) |
| bot_ids = list(entry.bot_id for entry in bot_id_statuses) |
| result = [] |
| for bot_id in bot_ids: |
| status = QueueStatus.all().filter('bot_id =', bot_id).order('-date').get() |
| |
| if status.queue_name != queue.name(): |
| # The bot got re-purposed, and is serving a different queue now. |
| continue |
| |
| result.append({ |
| "bot_id": bot_id, |
| "status_page": self.request.host_url + "/queue-status/" + queue.name() + "/bots/" + bot_id, |
| "latest_message": status.message, |
| "latest_message_time": status.date, |
| "latest_message_bug_id": status.active_bug_id, |
| "latest_message_patch_id": status.active_patch_id, |
| "latest_output": self.request.host_url + "/results/" + str(status.key().id()) if status.results_file else None, |
| }) |
| return result |
| |
| def get(self, queue_name): |
| self.response.headers["Access-Control-Allow-Origin"] = "*" |
| |
| queue_name = queue_name.lower() |
| queue = Queue.queue_with_name(queue_name) |
| if not queue: |
| self.error(404) |
| return |
| |
| self.response.headers['Content-Type'] = 'application/json' |
| |
| status = { |
| "status_page": self.request.host_url + "/queue-status/" + queue_name, |
| "charts_page": self.request.host_url + "/queue-charts/" + queue_name, |
| "queue": self._rows_for_work_items(queue), |
| "bots": self._bots(queue), |
| } |
| dthandler = lambda obj: obj.isoformat() + "Z" if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date) else None |
| self.response.out.write(json.dumps(status, default=dthandler)) |