diff mbox

[Branch,~linaro-validation/lava-scheduler/trunk] Rev 42: Fixes two small but serious problems with targeting jobs to device_types:

Message ID 20110726050417.24392.2703.launchpad@loganberry.canonical.com
State Accepted
Headers show

Commit Message

Michael-Doyle Hudson July 26, 2011, 5:04 a.m. UTC
Merge authors:
  Michael Hudson-Doyle (mwhudson)
Related merge proposals:
  https://code.launchpad.net/~mwhudson/lava-scheduler/device-type-targeting-details/+merge/69197
  proposed by: Michael Hudson-Doyle (mwhudson)
  review: Approve - Paul Larson (pwlars)
------------------------------------------------------------
revno: 42 [merge]
committer: Michael-Doyle Hudson <michael.hudson@linaro.org>
branch nick: trunk
timestamp: Tue 2011-07-26 16:57:41 +1200
message:
  Fixes two small but serious problems with targeting jobs to device_types:
  
  1) A bug in job completed meant that such a job would never be marked
  completed.
  2) A job targeted to a specific device could run on another device of the same
  type.
added:
  lava_scheduler_app/migrations/0002_auto__chg_field_testjob_device_type.py
  lava_scheduler_app/migrations/0003_auto__add_field_testjob_requested_device__add_field_testjob_requested_.py
  lava_scheduler_app/migrations/0004_fill_out_device_fields.py
  lava_scheduler_app/migrations/0005_auto__del_field_testjob_device_type__del_field_testjob_target.py
modified:
  lava_scheduler_app/models.py
  lava_scheduler_app/templates/lava_scheduler_app/job.html
  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
diff mbox

Patch

=== added file 'lava_scheduler_app/migrations/0002_auto__chg_field_testjob_device_type.py'
--- lava_scheduler_app/migrations/0002_auto__chg_field_testjob_device_type.py	1970-01-01 00:00:00 +0000
+++ lava_scheduler_app/migrations/0002_auto__chg_field_testjob_device_type.py	2011-07-26 02:48:55 +0000
@@ -0,0 +1,83 @@ 
+# 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):
+        
+        # Changing field 'TestJob.device_type'
+        db.alter_column('lava_scheduler_app_testjob', 'device_type_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['lava_scheduler_app.DeviceType'], null=True))
+
+
+    def backwards(self, orm):
+        
+        # User chose to not deal with backwards NULL issues for 'TestJob.device_type'
+        raise RuntimeError("Cannot reverse this migration. 'TestJob.device_type' and its values cannot be restored.")
+
+
+    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', [], {'to': "orm['lava_scheduler_app.TestJob']", 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']"}),
+            'hostname': ('django.db.models.fields.CharField', [], {'max_length': '200', 'primary_key': 'True'}),
+            'status': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+        },
+        'lava_scheduler_app.devicetype': {
+            'Meta': {'object_name': 'DeviceType'},
+            'name': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True', 'db_index': 'True'})
+        },
+        'lava_scheduler_app.testjob': {
+            'Meta': {'object_name': 'TestJob'},
+            'definition': ('django.db.models.fields.TextField', [], {}),
+            'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']", 'null': 'True'}),
+            'end_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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'}),
+            'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.Device']", 'null': 'True'})
+        }
+    }
+
+    complete_apps = ['lava_scheduler_app']

=== added file 'lava_scheduler_app/migrations/0003_auto__add_field_testjob_requested_device__add_field_testjob_requested_.py'
--- lava_scheduler_app/migrations/0003_auto__add_field_testjob_requested_device__add_field_testjob_requested_.py	1970-01-01 00:00:00 +0000
+++ lava_scheduler_app/migrations/0003_auto__add_field_testjob_requested_device__add_field_testjob_requested_.py	2011-07-26 03:13:10 +0000
@@ -0,0 +1,98 @@ 
+# 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 'TestJob.requested_device'
+        db.add_column('lava_scheduler_app_testjob', 'requested_device', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='+', null=True, to=orm['lava_scheduler_app.Device']), keep_default=False)
+
+        # Adding field 'TestJob.requested_device_type'
+        db.add_column('lava_scheduler_app_testjob', 'requested_device_type', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='+', null=True, to=orm['lava_scheduler_app.DeviceType']), keep_default=False)
+
+        # Adding field 'TestJob.actual_device'
+        db.add_column('lava_scheduler_app_testjob', 'actual_device', self.gf('django.db.models.fields.related.ForeignKey')(default=None, related_name='+', null=True, to=orm['lava_scheduler_app.Device']), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'TestJob.requested_device'
+        db.delete_column('lava_scheduler_app_testjob', 'requested_device_id')
+
+        # Deleting field 'TestJob.requested_device_type'
+        db.delete_column('lava_scheduler_app_testjob', 'requested_device_type_id')
+
+        # Deleting field 'TestJob.actual_device'
+        db.delete_column('lava_scheduler_app_testjob', 'actual_device_id')
+
+
+    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', [], {'to': "orm['lava_scheduler_app.TestJob']", 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']"}),
+            'hostname': ('django.db.models.fields.CharField', [], {'max_length': '200', 'primary_key': 'True'}),
+            'status': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+        },
+        'lava_scheduler_app.devicetype': {
+            'Meta': {'object_name': 'DeviceType'},
+            'name': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True', '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', 'to': "orm['lava_scheduler_app.Device']"}),
+            'definition': ('django.db.models.fields.TextField', [], {}),
+            'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']", 'null': 'True'}),
+            'end_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'requested_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'to': "orm['lava_scheduler_app.Device']"}),
+            'requested_device_type': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'to': "orm['lava_scheduler_app.DeviceType']"}),
+            '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'}),
+            'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.Device']", 'null': 'True'})
+        }
+    }
+
+    complete_apps = ['lava_scheduler_app']

=== added file 'lava_scheduler_app/migrations/0004_fill_out_device_fields.py'
--- lava_scheduler_app/migrations/0004_fill_out_device_fields.py	1970-01-01 00:00:00 +0000
+++ lava_scheduler_app/migrations/0004_fill_out_device_fields.py	2011-07-26 04:08:41 +0000
@@ -0,0 +1,92 @@ 
+# 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):
+        "Write your forwards methods here."
+        for job in orm.TestJob.objects.all():
+            job.requested_device = job.target
+            if job.status != 0:
+                job.actual_device = job.target
+            if not job.target:
+                job.requested_device_type = job.device_type
+            job.save()
+
+    def backwards(self, orm):
+        "Write your backwards methods here."
+        for job in orm.TestJob.objects.all():
+            job.target = job.requested_device
+            job.device_type = job.requested_device.device_type
+            job.save()
+
+
+    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', [], {'to': "orm['lava_scheduler_app.TestJob']", 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']"}),
+            'hostname': ('django.db.models.fields.CharField', [], {'max_length': '200', 'primary_key': 'True'}),
+            'status': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+        },
+        'lava_scheduler_app.devicetype': {
+            'Meta': {'object_name': 'DeviceType'},
+            'name': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True', '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', 'to': "orm['lava_scheduler_app.Device']"}),
+            'definition': ('django.db.models.fields.TextField', [], {}),
+            'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']", 'null': 'True'}),
+            'end_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'requested_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'to': "orm['lava_scheduler_app.Device']"}),
+            'requested_device_type': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'to': "orm['lava_scheduler_app.DeviceType']"}),
+            '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'}),
+            'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.Device']", 'null': 'True'})
+        }
+    }
+
+    complete_apps = ['lava_scheduler_app']

=== added file 'lava_scheduler_app/migrations/0005_auto__del_field_testjob_device_type__del_field_testjob_target.py'
--- lava_scheduler_app/migrations/0005_auto__del_field_testjob_device_type__del_field_testjob_target.py	1970-01-01 00:00:00 +0000
+++ lava_scheduler_app/migrations/0005_auto__del_field_testjob_device_type__del_field_testjob_target.py	2011-07-26 03:15:21 +0000
@@ -0,0 +1,90 @@ 
+# 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):
+        
+        # Deleting field 'TestJob.device_type'
+        db.delete_column('lava_scheduler_app_testjob', 'device_type_id')
+
+        # Deleting field 'TestJob.target'
+        db.delete_column('lava_scheduler_app_testjob', 'target_id')
+
+
+    def backwards(self, orm):
+        
+        # Adding field 'TestJob.device_type'
+        db.add_column('lava_scheduler_app_testjob', 'device_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['lava_scheduler_app.DeviceType'], null=True), keep_default=False)
+
+        # Adding field 'TestJob.target'
+        db.add_column('lava_scheduler_app_testjob', 'target', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['lava_scheduler_app.Device'], null=True), keep_default=False)
+
+
+    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', [], {'to': "orm['lava_scheduler_app.TestJob']", 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']"}),
+            'hostname': ('django.db.models.fields.CharField', [], {'max_length': '200', 'primary_key': 'True'}),
+            'status': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+        },
+        'lava_scheduler_app.devicetype': {
+            'Meta': {'object_name': 'DeviceType'},
+            'name': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True', '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', 'to': "orm['lava_scheduler_app.Device']"}),
+            'definition': ('django.db.models.fields.TextField', [], {}),
+            'end_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'requested_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'to': "orm['lava_scheduler_app.Device']"}),
+            'requested_device_type': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'to': "orm['lava_scheduler_app.DeviceType']"}),
+            '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'}),
+            'submitter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        }
+    }
+
+    complete_apps = ['lava_scheduler_app']

=== modified file 'lava_scheduler_app/models.py'
--- lava_scheduler_app/models.py	2011-07-20 03:36:11 +0000
+++ lava_scheduler_app/models.py	2011-07-26 04:09:37 +0000
@@ -90,8 +90,15 @@ 
     #    max_length = 200
     #)
 
-    target = models.ForeignKey(Device, null=True)
-    device_type = models.ForeignKey(DeviceType)
+    # Only one of these two should be non-null.
+    requested_device = models.ForeignKey(
+        Device, null=True, default=None, related_name='+')
+    requested_device_type = models.ForeignKey(
+        DeviceType, null=True, default=None, related_name='+')
+
+    # This is set once the job starts.
+    actual_device = models.ForeignKey(
+        Device, null=True, default=None, related_name='+')
 
     #priority = models.IntegerField(
     #    verbose_name = _(u"Priority"),
@@ -128,8 +135,8 @@ 
 
     def __unicode__(self):
         r = "%s test job" % self.get_status_display()
-        if self.target:
-            r += " for %s" % (self.target.hostname,)
+        if self.requested_device:
+            r += " for %s" % (self.requested_device.hostname,)
         return r
 
     @classmethod
@@ -137,12 +144,12 @@ 
         job_data = json.loads(json_data)
         if 'target' in job_data:
             target = Device.objects.get(hostname=job_data['target'])
-            device_type = target.device_type
+            device_type = None
         else:
             target = None
             device_type = DeviceType.objects.get(name=job_data['device_type'])
         job = TestJob(
-            definition=json_data, submitter=user, device_type=device_type,
-            target=target)
+            definition=json_data, submitter=user, requested_device=target,
+            requested_device_type=device_type)
         job.save()
         return job

=== modified file 'lava_scheduler_app/templates/lava_scheduler_app/job.html'
--- lava_scheduler_app/templates/lava_scheduler_app/job.html	2011-07-24 22:53:00 +0000
+++ lava_scheduler_app/templates/lava_scheduler_app/job.html	2011-07-26 04:16:53 +0000
@@ -20,16 +20,25 @@ 
     <dt>Submitted by:</dt>
     <dd>{{ job.submitter }}</dd>
 
-    <dt>Targeted to:</dt>
-    <dd>{{ job.target }}</dd>
+    {% if job.requested_device %}
+    <dt>Requested device:</dt>
+    <dd>{{ job.requested_device }}</dd>
+    {% endif %}
 
+    {% if job.requested_device_type %}
     <dt>Requested type:</dt>
-    <dd>{{ job.device_type }}</dd>
+    <dd>{{ job.requested_device_type }}</dd>
+    {% endif %}
   </div>
 
   <div class="column">
     <dt>Status:</dt>
     <dd><b>{{ job.get_status_display }}</b></dd>
+
+    {% if job.actual_device %}
+    <dt>On device:</dt>
+    <dd>{{ job.actual_device }}</dd>
+    {% endif %}
   </div>
 
   <div class="column">

=== modified file 'lava_scheduler_app/tests.py'
--- lava_scheduler_app/tests.py	2011-07-21 00:45:08 +0000
+++ lava_scheduler_app/tests.py	2011-07-26 04:33:39 +0000
@@ -66,15 +66,12 @@ 
         device.save()
         return device
 
-    def make_testjob(self, device_type=None, definition=None, **kwargs):
-        if device_type is None:
-            device_type = self.ensure_device_type()
+    def make_testjob(self, definition=None, **kwargs):
         if definition is None:
             definition = json.dumps({})
         submitter = self.make_user()
         testjob = TestJob(
-            device_type=device_type, definition=definition,
-            submitter=submitter, **kwargs)
+            definition=definition, submitter=submitter, **kwargs)
         testjob.save()
         return testjob
 
@@ -105,20 +102,20 @@ 
         panda_type = self.factory.ensure_device_type(name='panda')
         job = TestJob.from_json_and_user(
             json.dumps({'device_type':'panda'}), self.factory.make_user())
-        self.assertEqual(panda_type, job.device_type)
+        self.assertEqual(panda_type, job.requested_device_type)
 
     def test_from_json_and_user_sets_target(self):
         panda_board = self.factory.make_device(hostname='panda01')
         job = TestJob.from_json_and_user(
             json.dumps({'target':'panda01'}), self.factory.make_user())
-        self.assertEqual(panda_board, job.target)
+        self.assertEqual(panda_board, job.requested_device)
 
-    def test_from_json_and_user_sets_device_type_from_target(self):
+    def test_from_json_and_user_does_not_set_device_type_from_target(self):
         panda_type = self.factory.ensure_device_type(name='panda')
         self.factory.make_device(device_type=panda_type, hostname='panda01')
         job = TestJob.from_json_and_user(
             json.dumps({'target':'panda01'}), self.factory.make_user())
-        self.assertEqual(panda_type, job.device_type)
+        self.assertEqual(None, job.requested_device_type)
 
     def test_from_json_and_user_sets_date_submitted(self):
         self.factory.ensure_device_type(name='panda')
@@ -195,7 +192,7 @@ 
         device = self.factory.make_device(hostname='panda01')
         definition = {'foo': 'bar'}
         self.factory.make_testjob(
-            target=device, definition=json.dumps(definition))
+            requested_device=device, definition=json.dumps(definition))
         transaction.commit()
         self.assertEqual(
             definition, DatabaseJobSource().getJobForBoard_impl('panda01'))
@@ -211,7 +208,8 @@ 
         self.factory.make_device(hostname='panda01', device_type=panda_type)
         definition = {'foo': 'bar'}
         self.factory.make_testjob(
-            device_type=panda_type, definition=json.dumps(definition))
+            requested_device_type=panda_type,
+            definition=json.dumps(definition))
         transaction.commit()
         self.assertEqual(
             definition, DatabaseJobSource().getJobForBoard_impl('panda01'))
@@ -223,10 +221,10 @@ 
         first_definition = {'foo': 'bar'}
         second_definition = {'foo': 'baz'}
         self.factory.make_testjob(
-            target=panda01, definition=json.dumps(first_definition),
+            requested_device=panda01, definition=json.dumps(first_definition),
             submit_time=datetime.datetime.now() - datetime.timedelta(days=1))
         self.factory.make_testjob(
-            target=panda01, definition=json.dumps(second_definition),
+            requested_device=panda01, definition=json.dumps(second_definition),
             submit_time=datetime.datetime.now())
         transaction.commit()
         self.assertEqual(
@@ -240,18 +238,34 @@ 
         type_definition = {'foo': 'bar'}
         device_definition = {'foo': 'baz'}
         self.factory.make_testjob(
-            device_type=panda_type, definition=json.dumps(type_definition),
+            requested_device_type=panda_type,
+            definition=json.dumps(type_definition),
             submit_time=datetime.datetime.now() - datetime.timedelta(days=1))
         self.factory.make_testjob(
-            target=panda01, definition=json.dumps(device_definition))
+            requested_device=panda01,
+            definition=json.dumps(device_definition))
         transaction.commit()
         self.assertEqual(
             device_definition,
             DatabaseJobSource().getJobForBoard_impl('panda01'))
 
+    def test_getJobForBoard_avoids_targeted_to_other_board_of_same_type(self):
+        panda_type = self.factory.ensure_device_type(name='panda')
+        panda01 = self.factory.make_device(
+            hostname='panda01', device_type=panda_type)
+        self.factory.make_device(hostname='panda02', device_type=panda_type)
+        definition = {'foo': 'bar'}
+        self.factory.make_testjob(
+            requested_device=panda01,
+            definition=json.dumps(definition))
+        transaction.commit()
+        self.assertEqual(
+            None,
+            DatabaseJobSource().getJobForBoard_impl('panda02'))
+
     def test_getJobForBoard_sets_start_time(self):
         device = self.factory.make_device(hostname='panda01')
-        job = self.factory.make_testjob(target=device)
+        job = self.factory.make_testjob(requested_device=device)
         before = datetime.datetime.now()
         transaction.commit()
         DatabaseJobSource().getJobForBoard_impl('panda01')
@@ -262,7 +276,7 @@ 
 
     def test_getJobForBoard_set_statuses(self):
         device = self.factory.make_device(hostname='panda01')
-        job = self.factory.make_testjob(target=device)
+        job = self.factory.make_testjob(requested_device=device)
         transaction.commit()
         DatabaseJobSource().getJobForBoard_impl('panda01')
         # reload from the database
@@ -274,7 +288,7 @@ 
 
     def test_getJobForBoard_sets_running_job(self):
         device = self.factory.make_device(hostname='panda01')
-        job = self.factory.make_testjob(target=device)
+        job = self.factory.make_testjob(requested_device=device)
         transaction.commit()
         DatabaseJobSource().getJobForBoard_impl('panda01')
         # reload from the database
@@ -284,7 +298,7 @@ 
 
     def get_device_and_running_job(self):
         device = self.factory.make_device(hostname='panda01')
-        job = self.factory.make_testjob(target=device)
+        job = self.factory.make_testjob(requested_device=device)
         transaction.commit()
         DatabaseJobSource().getJobForBoard_impl('panda01')
         return device, job
@@ -299,6 +313,19 @@ 
             (Device.IDLE, TestJob.COMPLETE),
             (device.status, job.status))
 
+    def test_jobCompleted_works_on_device_type_targeted(self):
+        device = self.factory.make_device(hostname='panda01')
+        job = self.factory.make_testjob(
+            requested_device_type=device.device_type)
+        transaction.commit()
+        DatabaseJobSource().getJobForBoard_impl('panda01')
+        DatabaseJobSource().jobCompleted_impl('panda01', None)
+        job = TestJob.objects.get(pk=job.pk)
+        device = Device.objects.get(pk=device.pk)
+        self.assertEqual(
+            (Device.IDLE, TestJob.COMPLETE),
+            (device.status, job.status))
+
     def test_jobCompleted_sets_end_time(self):
         device, job = self.get_device_and_running_job()
         before = datetime.datetime.now()

=== modified file 'lava_scheduler_daemon/dbjobsource.py'
--- lava_scheduler_daemon/dbjobsource.py	2011-07-21 00:40:39 +0000
+++ lava_scheduler_daemon/dbjobsource.py	2011-07-26 02:58:20 +0000
@@ -33,16 +33,18 @@ 
             if device.status != Device.IDLE:
                 return None
             jobs_for_device = TestJob.objects.all().filter(
-                Q(target=device) | Q(device_type=device.device_type),
+                Q(requested_device=device)
+                | Q(requested_device_type=device.device_type),
                 status=TestJob.SUBMITTED)
             jobs_for_device = jobs_for_device.extra(
-                select={'is_targeted': 'target_id is not NULL'},
+                select={'is_targeted': 'requested_device_id is not NULL'},
                 order_by=['-is_targeted', 'submit_time'])
             jobs = jobs_for_device[:1]
             if jobs:
                 job = jobs[0]
                 job.status = TestJob.RUNNING
                 job.start_time = datetime.datetime.utcnow()
+                job.actual_device = device
                 device.status = Device.RUNNING
                 device.current_job = job
                 try:
@@ -76,8 +78,8 @@ 
         self.logger.debug('marking job as complete on %s', board_name)
         device = Device.objects.get(hostname=board_name)
         device.status = Device.IDLE
+        job = device.current_job
         device.current_job = None
-        job = TestJob.objects.get(target=device, status=TestJob.RUNNING)
         job.status = TestJob.COMPLETE
         job.end_time = datetime.datetime.utcnow()
         device.save()