From patchwork Sun Feb 26 22:45:19 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: 6937 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 BBB8E23EA9 for ; Sun, 26 Feb 2012 22:45:23 +0000 (UTC) Received: from mail-iy0-f180.google.com (mail-iy0-f180.google.com [209.85.210.180]) by fiordland.canonical.com (Postfix) with ESMTP id 4CCADA1859B for ; Sun, 26 Feb 2012 22:45:23 +0000 (UTC) Received: by iabz7 with SMTP id z7so7456688iab.11 for ; Sun, 26 Feb 2012 14:45:22 -0800 (PST) Received: from mr.google.com ([10.43.52.74]) by 10.43.52.74 with SMTP id vl10mr11400632icb.55.1330296322721 (num_hops = 1); Sun, 26 Feb 2012 14:45:22 -0800 (PST) Received: by 10.43.52.74 with SMTP id vl10mr9214128icb.55.1330296322660; Sun, 26 Feb 2012 14:45:22 -0800 (PST) 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.11.10 with SMTP id r10csp53767ibr; Sun, 26 Feb 2012 14:45:20 -0800 (PST) Received: by 10.216.135.37 with SMTP id t37mr4516029wei.44.1330296319975; Sun, 26 Feb 2012 14:45:19 -0800 (PST) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id y84si9619577weq.110.2012.02.26.14.45.19 (version=TLSv1/SSLv3 cipher=OTHER); Sun, 26 Feb 2012 14:45:19 -0800 (PST) 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 1S1mqF-0005o5-Cq for ; Sun, 26 Feb 2012 22:45:19 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id 57028E01A0 for ; Sun, 26 Feb 2012 22:45:19 +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: 135 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-validation/lava-scheduler/trunk] Rev 135: automatically run a health check job (defined on the device type) Message-Id: <20120226224519.23249.53574.launchpad@ackee.canonical.com> Date: Sun, 26 Feb 2012 22:45:19 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="14860"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: f2fb1064d2c4727116cb4859681632c097ca2439 X-Gm-Message-State: ALoCoQmVfPr37W2ScD9Cju1J4xWrjmVOiIPRfMWskb2VAz5Oulcu9DAiysa9ODMCe8GEP4mXMLoj Merge authors: Michael Hudson-Doyle (mwhudson) Related merge proposals: https://code.launchpad.net/~mwhudson/lava-scheduler/auto-health-check-jobs/+merge/94310 proposed by: Michael Hudson-Doyle (mwhudson) review: Approve - Paul Larson (pwlars) ------------------------------------------------------------ revno: 135 [merge] committer: Michael Hudson-Doyle branch nick: trunk timestamp: Mon 2012-02-27 11:30:17 +1300 message: automatically run a health check job (defined on the device type) when the board has just come online or if no health check job has run for 24 hours added: lava_scheduler_app/migrations/0016_auto__add_field_devicetype_health_check_job.py lava_scheduler_app/migrations/0017_add_lava_health_user.py modified: lava_scheduler_app/models.py lava_scheduler_app/tests.py lava_scheduler_daemon/dbjobsource.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 === added file 'lava_scheduler_app/migrations/0016_auto__add_field_devicetype_health_check_job.py' --- lava_scheduler_app/migrations/0016_auto__add_field_devicetype_health_check_job.py 1970-01-01 00:00:00 +0000 +++ lava_scheduler_app/migrations/0016_auto__add_field_devicetype_health_check_job.py 2012-02-23 00:13:51 +0000 @@ -0,0 +1,120 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'DeviceType.health_check_job' + db.add_column('lava_scheduler_app_devicetype', 'health_check_job', self.gf('django.db.models.fields.TextField')(default=None, null=True, blank=True), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'DeviceType.health_check_job' + db.delete_column('lava_scheduler_app_devicetype', 'health_check_job') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'lava_scheduler_app.device': { + 'Meta': {'object_name': 'Device'}, + 'current_job': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'unique': 'True', 'null': 'True', 'to': "orm['lava_scheduler_app.TestJob']"}), + 'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']"}), + 'health_status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '200', 'primary_key': 'True'}), + 'last_health_report_job': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'unique': 'True', 'null': 'True', 'to': "orm['lava_scheduler_app.TestJob']"}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['lava_scheduler_app.Tag']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'lava_scheduler_app.devicestatetransition': { + 'Meta': {'object_name': 'DeviceStateTransition'}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['lava_scheduler_app.Device']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.TestJob']", 'null': 'True', 'blank': 'True'}), + 'message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'new_state': ('django.db.models.fields.IntegerField', [], {}), + 'old_state': ('django.db.models.fields.IntegerField', [], {}) + }, + 'lava_scheduler_app.devicetype': { + 'Meta': {'object_name': 'DeviceType'}, + 'health_check_job': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True', 'db_index': 'True'}) + }, + 'lava_scheduler_app.tag': { + 'Meta': {'object_name': 'Tag'}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}) + }, + 'lava_scheduler_app.testjob': { + 'Meta': {'object_name': 'TestJob'}, + 'actual_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.Device']"}), + 'definition': ('django.db.models.fields.TextField', [], {}), + 'description': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'end_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'health_check': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'log_file': ('django.db.models.fields.files.FileField', [], {'default': 'None', 'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'requested_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.Device']"}), + 'requested_device_type': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.DeviceType']"}), + 'results_link': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '400', 'null': 'True', 'blank': 'True'}), + 'start_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'submit_time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'submit_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['linaro_django_xmlrpc.AuthToken']", 'null': 'True', 'blank': 'True'}), + 'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['lava_scheduler_app.Tag']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'linaro_django_xmlrpc.authtoken': { + 'Meta': {'object_name': 'AuthToken'}, + 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_used_on': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'secret': ('django.db.models.fields.CharField', [], {'default': "'558rhcm45h7251wlnrp2aotn8x2a2ta8cq6qjkm8rzi8snjd702xs81jw7ocle3fik1cq11a3iwt10wkxg2l3w4i5mynzo7kq07d50hfpmkpx5kbn83l0esdh648n77k'", 'unique': 'True', 'max_length': '128'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['lava_scheduler_app'] === added file 'lava_scheduler_app/migrations/0017_add_lava_health_user.py' --- lava_scheduler_app/migrations/0017_add_lava_health_user.py 1970-01-01 00:00:00 +0000 +++ lava_scheduler_app/migrations/0017_add_lava_health_user.py 2012-02-23 01:42:36 +0000 @@ -0,0 +1,122 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + # because the 'frozen' auth.User doesn't have its usual custom manager + # or convenience methods, they are inlined here. + now = datetime.datetime.now() + new_user = orm['auth.User']( + username='lava-health', email='lava@lava.invalid', is_staff=False, + is_active=True, is_superuser=False, last_login=now, + date_joined=now) + new_user.password = '!' + new_user.save() + + def backwards(self, orm): + orm['auth.User'].objects.filter(username='lava-health').delete() + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'lava_scheduler_app.device': { + 'Meta': {'object_name': 'Device'}, + 'current_job': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'unique': 'True', 'null': 'True', 'to': "orm['lava_scheduler_app.TestJob']"}), + 'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']"}), + 'health_status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '200', 'primary_key': 'True'}), + 'last_health_report_job': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'unique': 'True', 'null': 'True', 'to': "orm['lava_scheduler_app.TestJob']"}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['lava_scheduler_app.Tag']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'lava_scheduler_app.devicestatetransition': { + 'Meta': {'object_name': 'DeviceStateTransition'}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['lava_scheduler_app.Device']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.TestJob']", 'null': 'True', 'blank': 'True'}), + 'message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'new_state': ('django.db.models.fields.IntegerField', [], {}), + 'old_state': ('django.db.models.fields.IntegerField', [], {}) + }, + 'lava_scheduler_app.devicetype': { + 'Meta': {'object_name': 'DeviceType'}, + 'health_check_job': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True', 'db_index': 'True'}) + }, + 'lava_scheduler_app.tag': { + 'Meta': {'object_name': 'Tag'}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}) + }, + 'lava_scheduler_app.testjob': { + 'Meta': {'object_name': 'TestJob'}, + 'actual_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.Device']"}), + 'definition': ('django.db.models.fields.TextField', [], {}), + 'description': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'end_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'health_check': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'log_file': ('django.db.models.fields.files.FileField', [], {'default': 'None', 'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'requested_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.Device']"}), + 'requested_device_type': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.DeviceType']"}), + 'results_link': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '400', 'null': 'True', 'blank': 'True'}), + 'start_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'submit_time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'submit_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['linaro_django_xmlrpc.AuthToken']", 'null': 'True', 'blank': 'True'}), + 'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['lava_scheduler_app.Tag']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'linaro_django_xmlrpc.authtoken': { + 'Meta': {'object_name': 'AuthToken'}, + 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_used_on': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'secret': ('django.db.models.fields.CharField', [], {'default': "'tna03mimg70u3tl70fb1t66pu3rp6m6kg3844s3ngwulis85chbt32foe2u49rau01rzw3dfydqc0ugsngubfjqz3zp0tze0zkncapi90bsybh84kls58572go0ggaz4'", 'unique': 'True', 'max_length': '128'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['lava_scheduler_app'] === modified file 'lava_scheduler_app/models.py' --- lava_scheduler_app/models.py 2012-02-19 15:21:55 +0000 +++ lava_scheduler_app/models.py 2012-02-23 04:19:36 +0000 @@ -1,6 +1,7 @@ import simplejson from django.contrib.auth.models import User +from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import ugettext as _ @@ -20,6 +21,17 @@ return self.name +def validate_job_json(data): + try: + ob = simplejson.loads(data) + except ValueError, e: + raise ValidationError(str(e)) + else: + if not isinstance(ob, dict): + raise ValidationError( + "job json must be an object, not %s" % type(ob).__name__) + + class DeviceType(models.Model): """ A class of device, for example a pandaboard or a snowball. @@ -30,7 +42,8 @@ def __unicode__(self): return self.name - # We will probably hang uboot command and such off here... + health_check_job = models.TextField( + null=True, blank=True, default=None, validators=[validate_job_json]) class Device(models.Model): === modified file 'lava_scheduler_app/tests.py' --- lava_scheduler_app/tests.py 2012-01-30 22:26:03 +0000 +++ lava_scheduler_app/tests.py 2012-02-23 04:12:07 +0000 @@ -64,15 +64,23 @@ name = self.getUniqueString('name') return DeviceType.objects.get_or_create(name=name)[0] + def make_device_type(self, name=None, health_check_job=None): + if name is None: + name = self.getUniqueString('name') + device_type = DeviceType.objects.create( + name=name, health_check_job=health_check_job) + device_type.save() + return device_type + def ensure_tag(self, name): return Tag.objects.get_or_create(name=name)[0] - def make_device(self, device_type=None, hostname=None): + def make_device(self, device_type=None, hostname=None, **kw): if device_type is None: device_type = self.ensure_device_type() if hostname is None: hostname = self.getUniqueString() - device = Device(device_type=device_type, hostname=hostname) + device = Device(device_type=device_type, hostname=hostname, **kw) device.save() return device @@ -274,6 +282,11 @@ def setUp(self): super(TestDBJobSource, self).setUp() self.source = NonthreadedDatabaseJobSource() + # The lava-health user is created by a migration in production + # databases, but removed from the test database by the django + # machinery. + User.objects.create_user( + username='lava-health', email='lava@lava.invalid') def test_getBoardList(self): self.factory.make_device(hostname='panda01') @@ -287,6 +300,67 @@ self.assertEqual( definition, self.source.getJobForBoard('panda01')) + health_job = json.dumps({'health_check': True}) + ordinary_job = json.dumps({'health_check': False}) + + def assertHealthJobAssigned(self, device): + job_data = self.source.getJobForBoard(device.hostname) + if job_data is None: + self.fail("no job assigned") + self.assertTrue( + job_data['health_check'], + 'getJobForBoard did not return health check job') + + def assertHealthJobNotAssigned(self, device): + job_data = self.source.getJobForBoard(device.hostname) + if job_data is None: + self.fail("no job assigned") + self.assertFalse( + job_data['health_check'], + 'getJobForBoard returned health check job') + + def test_getJobForBoard_returns_health_check_if_no_last_health_job(self): + device_type = self.factory.make_device_type( + health_check_job=self.health_job) + device = self.factory.make_device( + device_type=device_type, health_status=Device.HEALTH_PASS) + self.factory.make_testjob( + requested_device=device, definition=self.ordinary_job) + self.assertHealthJobAssigned(device) + + def test_getJobForBoard_returns_health_check_if_old_last_health_job(self): + device_type = self.factory.make_device_type( + health_check_job=self.health_job) + device = self.factory.make_device( + device_type=device_type, health_status=Device.HEALTH_PASS, + last_health_report_job=self.factory.make_testjob( + end_time=datetime.datetime.now() - datetime.timedelta(weeks=1))) + self.factory.make_testjob( + requested_device=device, definition=self.ordinary_job) + self.assertHealthJobAssigned(device) + + def test_getJobForBoard_returns_job_if_healthy_and_last_health_job_recent(self): + device_type = self.factory.make_device_type( + health_check_job=self.health_job) + device = self.factory.make_device( + device_type=device_type, health_status=Device.HEALTH_PASS, + last_health_report_job=self.factory.make_testjob( + end_time=datetime.datetime.now() - datetime.timedelta(hours=1))) + self.factory.make_testjob( + requested_device=device, definition=self.ordinary_job) + self.assertHealthJobNotAssigned(device) + + def test_getJobForBoard_returns_health_check_if_health_unknown(self): + device_type = self.factory.make_device_type( + health_check_job=self.health_job) + device = self.factory.make_device( + device_type=device_type, health_status=Device.HEALTH_UNKNOWN, + last_health_report_job=self.factory.make_testjob( + end_time=datetime.datetime.now() - datetime.timedelta(hours=1))) + self.factory.make_testjob( + requested_device=device, definition=self.ordinary_job) + self.assertHealthJobAssigned(device) + def test_getJobForBoard_returns_None_if_no_job(self): self.factory.make_device(hostname='panda01') self.assertEqual( === modified file 'lava_scheduler_daemon/dbjobsource.py' --- lava_scheduler_daemon/dbjobsource.py 2012-02-19 15:21:55 +0000 +++ lava_scheduler_daemon/dbjobsource.py 2012-02-23 02:51:18 +0000 @@ -3,6 +3,7 @@ import logging import urlparse +from django.contrib.auth.models import User from django.core.files.base import ContentFile from django.db import connection from django.db import IntegrityError, transaction @@ -113,33 +114,66 @@ params['server'] = urlparse.urlunsplit(parsed) return json_data + def _getHealthCheckJobForBoard(self, device): + job_json = device.device_type.health_check_job + if not job_json: + self.logger.error( + "no job_json in getHealthCheckJobForBoard for %r", device) + return None + else: + user = User.objects.get(username='lava-health') + job_data = json.loads(job_json) + job_name = job_data.get('description') + job = TestJob( + definition=job_json, submitter=user, description=job_name, + health_check=True) + job.save() + return job + + def _getJobFromQueue(self, device): + jobs_for_device = TestJob.objects.all().filter( + Q(requested_device=device) + | Q(requested_device_type=device.device_type), + status=TestJob.SUBMITTED) + jobs_for_device = jobs_for_device.extra( + select={ + 'is_targeted': 'requested_device_id is not NULL', + }, + where=[ + # In human language, this is saying "where the number of + # tags that are on the job but not on the device is 0" + '''(select count(*) from lava_scheduler_app_testjob_tags + where testjob_id = lava_scheduler_app_testjob.id + and tag_id not in (select tag_id + from lava_scheduler_app_device_tags + where device_id = '%s')) = 0''' + % device.hostname, + ], + order_by=['-is_targeted', 'submit_time']) + jobs = jobs_for_device[:1] + if jobs: + return jobs[0] + else: + return None + def getJobForBoard_impl(self, board_name): while True: device = Device.objects.get(hostname=board_name) if device.status != Device.IDLE: return None - jobs_for_device = TestJob.objects.all().filter( - Q(requested_device=device) - | Q(requested_device_type=device.device_type), - status=TestJob.SUBMITTED) - jobs_for_device = jobs_for_device.extra( - select={ - 'is_targeted': 'requested_device_id is not NULL', - }, - where=[ - # In human language, this is saying "where the number of - # tags that are on the job but not on the device is 0" - '''(select count(*) from lava_scheduler_app_testjob_tags - where testjob_id = lava_scheduler_app_testjob.id - and tag_id not in (select tag_id - from lava_scheduler_app_device_tags - where device_id = '%s')) = 0''' - % device.hostname, - ], - order_by=['-is_targeted', 'submit_time']) - jobs = jobs_for_device[:1] - if jobs: - job = jobs[0] + if not device.device_type.health_check_job: + run_health_check = False + elif device.health_status == Device.HEALTH_UNKNOWN: + run_health_check = True + elif not device.last_health_report_job: + run_health_check = True + else: + run_health_check = device.last_health_report_job.end_time < datetime.datetime.now() - datetime.timedelta(days=1) + if run_health_check: + job = self._getHealthCheckJobForBoard(device) + else: + job = self._getJobFromQueue(device) + if job: DeviceStateTransition.objects.create( created_by=None, device=device, old_state=device.status, new_state=Device.RUNNING, message=None, job=job).save()