From patchwork Tue May 29 02:51:09 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael-Doyle Hudson X-Patchwork-Id: 9016 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 636C423EB4 for ; Tue, 29 May 2012 02:51:13 +0000 (UTC) Received: from mail-yx0-f180.google.com (mail-yx0-f180.google.com [209.85.213.180]) by fiordland.canonical.com (Postfix) with ESMTP id 0C93FA1811C for ; Tue, 29 May 2012 02:51:12 +0000 (UTC) Received: by yenq6 with SMTP id q6so2072305yen.11 for ; Mon, 28 May 2012 19:51:12 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-forwarded-to:x-forwarded-for:delivered-to:received-spf :content-type:mime-version:x-launchpad-project:x-launchpad-branch :x-launchpad-message-rationale:x-launchpad-branch-revision-number :x-launchpad-notification-type:to:from:subject:message-id:date :reply-to:sender:errors-to:precedence:x-generated-by :x-launchpad-hash:x-gm-message-state; bh=2i9vHRtyxO6OqeSc6B48GL7PWUedySsNRCP7HqXjvyI=; b=QmTDhc76ZykG+femwtfniAUyxslO5a7om8/u2Ska81IopoPGmlc+eIX1VF578Dn7af GOMRe0TI7InU1R45qR+NJfxdk57Klyr5U8E2ofsZFPcurShdhk+3xllu6FeV2OqJOYmO 3id+r/rZfUL8Kr7zPSNF3nbl8e4XcHPH3yQl8pXCPrYgHcF3hh472JZ4t+JQ4i5mf+70 +mGMKmJGPnX7xX7+6rhUo40dHiJ5QrAm8dOUp3LP8TTqyTf7mlQSZ952KRgQ53sXVUes s0ZaKw/zlA+42oGyqBTx/IF4Tw0XjiUjJoWkDahR2wrAZiu02IGz4R7wOD477AEs+adP s3zQ== Received: by 10.50.95.135 with SMTP id dk7mr5678218igb.53.1338259872339; Mon, 28 May 2012 19:51:12 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.231.24.148 with SMTP id v20csp100130ibb; Mon, 28 May 2012 19:51:10 -0700 (PDT) Received: by 10.14.99.79 with SMTP id w55mr2899296eef.59.1338259870195; Mon, 28 May 2012 19:51:10 -0700 (PDT) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id w3si11888011wiz.27.2012.05.28.19.51.09 (version=TLSv1/SSLv3 cipher=OTHER); Mon, 28 May 2012 19:51:10 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) client-ip=91.189.90.7; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) smtp.mail=bounces@canonical.com Received: from ackee.canonical.com ([91.189.89.26]) by indium.canonical.com with esmtp (Exim 4.71 #1 (Debian)) id 1SZCWb-0006rX-K2 for ; Tue, 29 May 2012 02:51:09 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id 89AE9E088E for ; Tue, 29 May 2012 02:51:09 +0000 (UTC) MIME-Version: 1.0 X-Launchpad-Project: lava-scheduler X-Launchpad-Branch: ~linaro-validation/lava-scheduler/trunk X-Launchpad-Message-Rationale: Subscriber X-Launchpad-Branch-Revision-Number: 173 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-validation/lava-scheduler/trunk] Rev 173: create views summarizing the state of each device type, link to those from scheduler summary page... Message-Id: <20120529025109.31338.81607.launchpad@ackee.canonical.com> Date: Tue, 29 May 2012 02:51:09 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="15316"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: 90904f17aa3556a914e1b6c3c16d7ae87304b405 X-Gm-Message-State: ALoCoQloWPSpfFvoskvQEJm5eNCQikGHPdLURKp56g58TA356r95L6XWFntHVxg8e3okGZzOZETn Merge authors: Spring Zhang (qzhang) Related merge proposals: https://code.launchpad.net/~qzhang/lava-scheduler/dt-page-revised/+merge/107598 proposed by: Spring Zhang (qzhang) https://code.launchpad.net/~qzhang/lava-scheduler/device-type-page/+merge/106577 proposed by: Spring Zhang (qzhang) ------------------------------------------------------------ revno: 173 [merge] committer: Michael Hudson-Doyle branch nick: trunk timestamp: Tue 2012-05-29 14:49:17 +1200 message: create views summarizing the state of each device type, link to those from scheduler summary page (Spring Zhang) added: lava_scheduler_app/templates/lava_scheduler_app/alldevices.html lava_scheduler_app/templates/lava_scheduler_app/device_type.html modified: lava_scheduler_app/models.py lava_scheduler_app/templates/lava_scheduler_app/index.html lava_scheduler_app/urls.py lava_scheduler_app/views.py --- lp:lava-scheduler https://code.launchpad.net/~linaro-validation/lava-scheduler/trunk You are subscribed to branch lp:lava-scheduler. To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-scheduler/trunk/+edit-subscription === modified file 'lava_scheduler_app/models.py' --- lava_scheduler_app/models.py 2012-05-23 11:42:47 +0000 +++ lava_scheduler_app/models.py 2012-05-28 07:21:03 +0000 @@ -55,6 +55,10 @@ health_check_job = models.TextField( null=True, blank=True, default=None, validators=[validate_job_json]) + @models.permalink + def get_absolute_url(self): + return ("lava.scheduler.device_type.detail", [self.pk]) + class Device(models.Model): """ === added file 'lava_scheduler_app/templates/lava_scheduler_app/alldevices.html' --- lava_scheduler_app/templates/lava_scheduler_app/alldevices.html 1970-01-01 00:00:00 +0000 +++ lava_scheduler_app/templates/lava_scheduler_app/alldevices.html 2012-05-28 09:40:20 +0000 @@ -0,0 +1,11 @@ +{% extends "lava_scheduler_app/_content.html" %} + +{% load django_tables2 %} + +{% block content %} + +

All Devices

+ +{% render_table devices_table %} + +{% endblock %} === added file 'lava_scheduler_app/templates/lava_scheduler_app/device_type.html' --- lava_scheduler_app/templates/lava_scheduler_app/device_type.html 1970-01-01 00:00:00 +0000 +++ lava_scheduler_app/templates/lava_scheduler_app/device_type.html 2012-05-24 09:12:26 +0000 @@ -0,0 +1,16 @@ +{% extends "lava_scheduler_app/_content.html" %} + +{% load django_tables2 %} + +{% block content %} +

{{device_type}} Status

+ +{{running_jobs_num}} jobs running / {{queued_jobs_num}} jobs queued + +

Health Job Summary

+{% render_table health_job_summary_table %} + +

Devices Overview

+{% render_table devices_table_no_dt %} + +{% endblock %} === modified file 'lava_scheduler_app/templates/lava_scheduler_app/index.html' --- lava_scheduler_app/templates/lava_scheduler_app/index.html 2012-03-05 00:23:16 +0000 +++ lava_scheduler_app/templates/lava_scheduler_app/index.html 2012-05-28 09:40:20 +0000 @@ -3,11 +3,16 @@ {% load django_tables2 %} {% block content %} -

Devices

- -{% render_table devices_table %} - -All Devices Health + +

Device Type Overview

+

{{device_status}} devices online

+

{{health_check_status}} health check jobs passed in 24 hours

+ +{% render_table device_type_table %} + +

All Devices

+ +

All Devices Health

Active Jobs

=== modified file 'lava_scheduler_app/urls.py' --- lava_scheduler_app/urls.py 2012-05-23 11:42:47 +0000 +++ lava_scheduler_app/urls.py 2012-05-28 09:40:20 +0000 @@ -18,6 +18,18 @@ url(r'^alljobs_json$', 'alljobs_json', name='lava.scheduler.job.list_json'), + url(r'^device_type/(?P[-_a-zA-Z0-9]+)$', + 'device_type_detail', + name='lava.scheduler.device_type.detail'), + url(r'^device_type_json$', + 'device_type_json', + name='lava.scheduler.device_type.device_type_json'), + url(r'^device_type/(?P[-_a-zA-Z0-9]+)/index_nodt_devices_json$', + 'index_nodt_devices_json', + name='lava.scheduler.device_type.index_nodt_devices_json'), + url(r'^alldevices$', + 'device_list', + name='lava.scheduler.alldevices'), url(r'^device/(?P[-_a-zA-Z0-9]+)$', 'device_detail', name='lava.scheduler.device.detail'), === modified file 'lava_scheduler_app/views.py' --- lava_scheduler_app/views.py 2012-05-23 11:42:47 +0000 +++ lava_scheduler_app/views.py 2012-05-28 09:40:20 +0000 @@ -3,6 +3,8 @@ import os import simplejson import StringIO +import datetime +from dateutil.relativedelta import relativedelta from django.conf import settings from django.core.urlresolvers import reverse @@ -38,13 +40,14 @@ getDispatcherLogMessages ) from lava_scheduler_app.models import ( + Tag, Device, + DeviceType, DeviceStateTransition, TestJob, ) - def post_only(func): def decorated(request, *args, **kwargs): if request.method != 'POST': @@ -69,6 +72,7 @@ record.get_absolute_url(), escape(record.pk))) + class IDLinkColumn(Column): def __init__(self, verbose_name="ID", **kw): @@ -97,6 +101,7 @@ }).all() + class JobTable(DataTablesTable): def render_device(self, record): @@ -157,12 +162,23 @@ def index_devices_json(request): return DeviceTable.json(request) +def health_jobs_in_hr(hr=-24): + return TestJob.objects.filter(health_check=True, + start_time__gte=(datetime.datetime.now() + relativedelta(hours=hr))) @BreadCrumb("Scheduler", parent=lava_index) def index(request): return render_to_response( "lava_scheduler_app/index.html", { + 'device_status': "%s/%s" % ( + Device.objects.filter( + status__in=[Device.IDLE, Device.RUNNING]).count(), + Device.objects.count()), + 'health_check_status': "%s/%s" % ( + health_jobs_in_hr().filter(status=TestJob.COMPLETE).count(), + health_jobs_in_hr().count()), + 'device_type_table': DeviceTypeTable('devicetype', reverse(device_type_json)), 'devices_table': DeviceTable('devices', reverse(index_devices_json)), 'active_jobs_table': IndexJobTable( 'active_jobs', reverse(index_active_jobs_json)), @@ -170,11 +186,107 @@ }, RequestContext(request)) +@BreadCrumb("All Devices", parent=index) +def device_list(request): + return render_to_response( + "lava_scheduler_app/alldevices.html", + { + 'devices_table': DeviceTable('devices', reverse(index_devices_json)), + 'bread_crumb_trail': BreadCrumbTrail.leading_to(device_list), + }, + RequestContext(request)) def get_restricted_job(user, pk): return get_object_or_404( TestJob.objects.accessible_by_principal(user), pk=pk) +class DeviceTypeTable(DataTablesTable): + + def get_queryset(self): + return DeviceType.objects.all() + + def render_status(self, record): + idle_num = Device.objects.filter(device_type=record.name, + status=Device.IDLE).count() + offline_num = Device.objects.filter(device_type=record.name, + status__in=[Device.OFFLINE, Device.OFFLINING]).count() + running_num = Device.objects.filter(device_type=record.name, + status=Device.RUNNING).count() + return "%s idle, %s offline, %s busy" % (idle_num, offline_num, + running_num) + + name = IDLinkColumn("name") + status = Column() + + searchable_columns = ['name'] + + +class HealthJobSummaryTable(DataTablesTable): + """ + The Table will return 1 day, 1 week, 1 month offset health job count. + The value is defined when table instance is created in device_type_detail() + """ + + def render_name(self, record): + matrix = {-24:"24hours", -24*7:"Week", -24*7*30:"Month"} + return matrix[record] + + def render_Complete(self, record): + device_type = self.params[0] + num = health_jobs_in_hr(record).filter( + actual_device__in=Device.objects.filter( + device_type=device_type), status=TestJob.COMPLETE).count() + return num + + def render_Failed(self, record): + device_type = self.params[0] + num = health_jobs_in_hr(record).filter( + actual_device__in=Device.objects.filter( + device_type=device_type), status=TestJob.INCOMPLETE).count() + return num + + name = Column() + Complete = Column() + Failed = Column() + +def device_type_json(request): + return DeviceTypeTable.json(request) + +class NoDTDeviceTable(DeviceTable): + def get_queryset(self, device_type): + return Device.objects.filter(device_type=device_type) + + class Meta: + exclude = ('device_type',) + +def index_nodt_devices_json(request, pk): + device_type = get_object_or_404(DeviceType, pk=pk) + return NoDTDeviceTable.json(request, params=(device_type,)) + +@BreadCrumb("Device Type {pk}", parent=index, needs=['pk']) +def device_type_detail(request, pk): + dt = get_object_or_404(DeviceType, pk=pk) + return render_to_response( + "lava_scheduler_app/device_type.html", + { + 'device_type': dt, + 'running_jobs_num': TestJob.objects.filter( + actual_device__in=Device.objects.filter(device_type=dt), + status=TestJob.RUNNING).count(), + # Fix me: doesn't count actual_device not set but requested + # device type jobs. + 'queued_jobs_num': TestJob.objects.filter( + actual_device__in=Device.objects.filter(device_type=dt), + status=TestJob.SUBMITTED).count(), + # data return 1 day, 1 week, 1 month offset + 'health_job_summary_table': HealthJobSummaryTable( + 'device_type', params=(dt,), data=[-24, -24*7, -24*7*30]), + 'devices_table_no_dt': NoDTDeviceTable('devices', + reverse(index_nodt_devices_json, kwargs=dict(pk=pk)), params=(dt,)), + 'bread_crumb_trail': BreadCrumbTrail.leading_to(device_type_detail, pk=pk), + }, + RequestContext(request)) + class DeviceHealthTable(DataTablesTable):