Merge lp:~stylesen/lava-scheduler/multinode into lp:lava-scheduler/multinode

Proposed by Senthil Kumaran S
Status: Merged
Approved by: Neil Williams
Approved revision: no longer in the source branch.
Merged at revision: 248
Proposed branch: lp:~stylesen/lava-scheduler/multinode
Merge into: lp:lava-scheduler/multinode
Diff against target: 540 lines (+376/-12)
8 files modified
lava_scheduler_app/api.py (+1/-1)
lava_scheduler_app/management/commands/scheduler.py (+2/-2)
lava_scheduler_app/migrations/0030_auto__add_field_testjob_sub_id.py (+160/-0)
lava_scheduler_app/models.py (+14/-4)
lava_scheduler_app/views.py (+11/-5)
lava_scheduler_daemon/dbjobsource.py (+76/-0)
lava_scheduler_daemon/job.py (+79/-0)
lava_scheduler_daemon/service.py (+33/-0)
To merge this branch: bzr merge lp:~stylesen/lava-scheduler/multinode
Reviewer Review Type Date Requested Status
Neil Williams Approve
Review via email: mp+170590@code.launchpad.net

Description of the change

Initial bits with a job based scheduler for multi-node support.

We do not accept multi-node jobs yet.

To post a comment you must log in.
Revision history for this message
Neil Williams (codehelp) wrote :

Approved

review: Approve
248. By Neil Williams

Merge Senthil's initial MultiNode changes for lava-scheduler.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lava_scheduler_app/api.py'
--- lava_scheduler_app/api.py 2013-05-02 07:35:44 +0000
+++ lava_scheduler_app/api.py 2013-06-20 11:53:35 +0000
@@ -36,7 +36,7 @@
36 raise xmlrpclib.Fault(404, "Specified device not found.")36 raise xmlrpclib.Fault(404, "Specified device not found.")
37 except DeviceType.DoesNotExist:37 except DeviceType.DoesNotExist:
38 raise xmlrpclib.Fault(404, "Specified device type not found.")38 raise xmlrpclib.Fault(404, "Specified device type not found.")
39 return job.id39 return job
4040
41 def resubmit_job(self, job_id):41 def resubmit_job(self, job_id):
42 try:42 try:
4343
=== modified file 'lava_scheduler_app/management/commands/scheduler.py'
--- lava_scheduler_app/management/commands/scheduler.py 2012-03-27 05:41:09 +0000
+++ lava_scheduler_app/management/commands/scheduler.py 2013-06-20 11:53:35 +0000
@@ -42,7 +42,7 @@
4242
43 from twisted.internet import reactor43 from twisted.internet import reactor
4444
45 from lava_scheduler_daemon.service import BoardSet45 from lava_scheduler_daemon.service import BoardSet, JobQueue
46 from lava_scheduler_daemon.dbjobsource import DatabaseJobSource46 from lava_scheduler_daemon.dbjobsource import DatabaseJobSource
4747
48 daemon_options = self._configure(options)48 daemon_options = self._configure(options)
@@ -57,7 +57,7 @@
57 'fake-dispatcher')57 'fake-dispatcher')
58 else:58 else:
59 dispatcher = options['dispatcher']59 dispatcher = options['dispatcher']
60 service = BoardSet(60 service = JobQueue(
61 source, dispatcher, reactor, daemon_options=daemon_options)61 source, dispatcher, reactor, daemon_options=daemon_options)
62 reactor.callWhenRunning(service.startService)62 reactor.callWhenRunning(service.startService)
63 reactor.run()63 reactor.run()
6464
=== added file 'lava_scheduler_app/migrations/0030_auto__add_field_testjob_sub_id.py'
--- lava_scheduler_app/migrations/0030_auto__add_field_testjob_sub_id.py 1970-01-01 00:00:00 +0000
+++ lava_scheduler_app/migrations/0030_auto__add_field_testjob_sub_id.py 2013-06-20 11:53:35 +0000
@@ -0,0 +1,160 @@
1# -*- coding: utf-8 -*-
2import datetime
3from south.db import db
4from south.v2 import SchemaMigration
5from django.db import models
6
7
8class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding field 'TestJob.sub_id'
12 db.add_column('lava_scheduler_app_testjob', 'sub_id',
13 self.gf('django.db.models.fields.CharField')(default='', max_length=200, blank=True),
14 keep_default=False)
15
16
17 def backwards(self, orm):
18 # Deleting field 'TestJob.sub_id'
19 db.delete_column('lava_scheduler_app_testjob', 'sub_id')
20
21
22 models = {
23 'auth.group': {
24 'Meta': {'object_name': 'Group'},
25 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
26 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
27 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
28 },
29 'auth.permission': {
30 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
31 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
32 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
33 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
34 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
35 },
36 'auth.user': {
37 'Meta': {'object_name': 'User'},
38 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
39 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
40 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
41 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
42 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
43 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
44 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
45 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
46 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
47 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
48 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
49 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
50 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
51 },
52 'contenttypes.contenttype': {
53 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
54 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
55 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
56 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
57 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
58 },
59 'dashboard_app.bundle': {
60 'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'},
61 '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}),
62 '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}),
63 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
64 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
65 'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}),
66 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67 'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
68 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}),
69 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
70 },
71 'dashboard_app.bundlestream': {
72 'Meta': {'object_name': 'BundleStream'},
73 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
74 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
75 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
76 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
77 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
78 'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
79 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
80 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
81 },
82 'lava_scheduler_app.device': {
83 'Meta': {'object_name': 'Device'},
84 'current_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['lava_scheduler_app.TestJob']", 'blank': 'True', 'unique': 'True'}),
85 'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']"}),
86 'device_version': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'null': 'True', 'blank': 'True'}),
87 'health_status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
88 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '200', 'primary_key': 'True'}),
89 'last_health_report_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['lava_scheduler_app.TestJob']", 'blank': 'True', 'unique': 'True'}),
90 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
91 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['lava_scheduler_app.Tag']", 'symmetrical': 'False', 'blank': 'True'})
92 },
93 'lava_scheduler_app.devicestatetransition': {
94 'Meta': {'object_name': 'DeviceStateTransition'},
95 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
96 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
97 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['lava_scheduler_app.Device']"}),
98 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
99 'job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.TestJob']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
100 'message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
101 'new_state': ('django.db.models.fields.IntegerField', [], {}),
102 'old_state': ('django.db.models.fields.IntegerField', [], {})
103 },
104 'lava_scheduler_app.devicetype': {
105 'Meta': {'object_name': 'DeviceType'},
106 'display': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
107 'health_check_job': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
108 'name': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True'})
109 },
110 'lava_scheduler_app.jobfailuretag': {
111 'Meta': {'object_name': 'JobFailureTag'},
112 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
113 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
114 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'})
115 },
116 'lava_scheduler_app.tag': {
117 'Meta': {'object_name': 'Tag'},
118 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
119 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
120 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'})
121 },
122 'lava_scheduler_app.testjob': {
123 'Meta': {'object_name': 'TestJob'},
124 '_results_bundle': ('django.db.models.fields.related.OneToOneField', [], {'null': 'True', 'db_column': "'results_bundle_id'", 'on_delete': 'models.SET_NULL', 'to': "orm['dashboard_app.Bundle']", 'blank': 'True', 'unique': 'True'}),
125 '_results_link': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '400', 'null': 'True', 'db_column': "'results_link'", 'blank': 'True'}),
126 'actual_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.Device']"}),
127 'definition': ('django.db.models.fields.TextField', [], {}),
128 'description': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'null': 'True', 'blank': 'True'}),
129 'end_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
130 'failure_comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
131 'failure_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'failure_tags'", 'blank': 'True', 'to': "orm['lava_scheduler_app.JobFailureTag']"}),
132 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
133 'health_check': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
134 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
135 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
136 'log_file': ('django.db.models.fields.files.FileField', [], {'default': 'None', 'max_length': '100', 'null': 'True', 'blank': 'True'}),
137 'priority': ('django.db.models.fields.IntegerField', [], {'default': '50'}),
138 'requested_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.Device']"}),
139 'requested_device_type': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.DeviceType']"}),
140 'start_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
141 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
142 'sub_id': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
143 'submit_time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
144 'submit_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['linaro_django_xmlrpc.AuthToken']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
145 'submitter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}),
146 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['lava_scheduler_app.Tag']", 'symmetrical': 'False', 'blank': 'True'}),
147 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
148 },
149 'linaro_django_xmlrpc.authtoken': {
150 'Meta': {'object_name': 'AuthToken'},
151 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
152 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
153 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
154 'last_used_on': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
155 'secret': ('django.db.models.fields.CharField', [], {'default': "'ynu6yihw337isktsrzd0ocr83r0huox1b5y4qs1c0ktat7s3089d2xgiz3ll0n68fr6q026mep0t5xwg1coxnl2aoknolgowx2779uluan8quez0row0jnk5j2qsxlle'", 'unique': 'True', 'max_length': '128'}),
156 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': "orm['auth.User']"})
157 }
158 }
159
160 complete_apps = ['lava_scheduler_app']
0\ No newline at end of file161\ No newline at end of file
1162
=== modified file 'lava_scheduler_app/models.py'
--- lava_scheduler_app/models.py 2013-02-10 21:26:06 +0000
+++ lava_scheduler_app/models.py 2013-06-20 11:53:35 +0000
@@ -2,6 +2,7 @@
2import os2import os
3import simplejson3import simplejson
4import urlparse4import urlparse
5import copy
56
6from django.conf import settings7from django.conf import settings
7from django.contrib.auth.models import User8from django.contrib.auth.models import User
@@ -249,6 +250,12 @@
249250
250 id = models.AutoField(primary_key=True)251 id = models.AutoField(primary_key=True)
251252
253 sub_id = models.CharField(
254 verbose_name = _(u"Sub ID"),
255 blank = True,
256 max_length = 200
257 )
258
252 submitter = models.ForeignKey(259 submitter = models.ForeignKey(
253 User,260 User,
254 verbose_name = _(u"Submitter"),261 verbose_name = _(u"Submitter"),
@@ -398,9 +405,13 @@
398 elif 'device_type' in job_data:405 elif 'device_type' in job_data:
399 target = None406 target = None
400 device_type = DeviceType.objects.get(name=job_data['device_type'])407 device_type = DeviceType.objects.get(name=job_data['device_type'])
408 elif 'device_group' in job_data:
409 target = None
410 device_type = None
401 else:411 else:
402 raise JSONDataError(412 raise JSONDataError(
403 "Neither 'target' nor 'device_type' found in job data.")413 "No 'target' or 'device_type' or 'device_group' are found "
414 "in job data.")
404415
405 priorities = dict([(j.upper(), i) for i, j in cls.PRIORITY_CHOICES])416 priorities = dict([(j.upper(), i) for i, j in cls.PRIORITY_CHOICES])
406 priority = cls.MEDIUM417 priority = cls.MEDIUM
@@ -463,15 +474,14 @@
463 tags.append(Tag.objects.get(name=tag_name))474 tags.append(Tag.objects.get(name=tag_name))
464 except Tag.DoesNotExist:475 except Tag.DoesNotExist:
465 raise JSONDataError("tag %r does not exist" % tag_name)476 raise JSONDataError("tag %r does not exist" % tag_name)
477
466 job = TestJob(478 job = TestJob(
467 definition=json_data, submitter=submitter,479 definition=json_data, submitter=submitter,
468 requested_device=target, requested_device_type=device_type,480 requested_device=target, requested_device_type=device_type,
469 description=job_name, health_check=health_check, user=user,481 description=job_name, health_check=health_check, user=user,
470 group=group, is_public=is_public, priority=priority)482 group=group, is_public=is_public, priority=priority)
471 job.save()483 job.save()
472 for tag in tags:484 return job.id
473 job.tags.add(tag)
474 return job
475485
476 def _can_admin(self, user):486 def _can_admin(self, user):
477 """ used to check for things like if the user can cancel or annotate487 """ used to check for things like if the user can cancel or annotate
478488
=== modified file 'lava_scheduler_app/views.py'
--- lava_scheduler_app/views.py 2013-05-02 09:00:04 +0000
+++ lava_scheduler_app/views.py 2013-06-20 11:53:35 +0000
@@ -74,10 +74,16 @@
7474
7575
76def pklink(record):76def pklink(record):
77 job_id = record.pk
78 try:
79 if record.sub_id:
80 job_id = record.sub_id
81 except:
82 pass
77 return mark_safe(83 return mark_safe(
78 '<a href="%s">%s</a>' % (84 '<a href="%s">%s</a>' % (
79 record.get_absolute_url(),85 record.get_absolute_url(),
80 escape(record.pk)))86 escape(job_id)))
8187
8288
83class IDLinkColumn(Column):89class IDLinkColumn(Column):
@@ -100,13 +106,13 @@
100106
101107
102def all_jobs_with_device_sort():108def all_jobs_with_device_sort():
103 return TestJob.objects.select_related(109 jobs = TestJob.objects.select_related(
104 "actual_device", "requested_device", "requested_device_type",110 "actual_device", "requested_device", "requested_device_type",
105 "submitter", "user", "group").extra(111 "submitter", "user", "group").extra(
106 select={112 select={
107 'device_sort': 'coalesce(actual_device_id, requested_device_id, requested_device_type_id)'113 'device_sort': 'coalesce(actual_device_id, requested_device_id, requested_device_type_id)'
108 }).all()114 }).all()
109115 return jobs.order_by('submit_time')
110116
111117
112class JobTable(DataTablesTable):118class JobTable(DataTablesTable):
@@ -126,7 +132,7 @@
126 else:132 else:
127 return ''133 return ''
128134
129 id = RestrictedIDLinkColumn()135 sub_id = RestrictedIDLinkColumn()
130 status = Column()136 status = Column()
131 priority = Column()137 priority = Column()
132 device = Column(accessor='device_sort')138 device = Column(accessor='device_sort')
@@ -137,7 +143,7 @@
137 duration = Column()143 duration = Column()
138144
139 datatable_opts = {145 datatable_opts = {
140 'aaSorting': [[0, 'desc']],146 'aaSorting': [[6, 'desc']],
141 }147 }
142 searchable_columns=['description']148 searchable_columns=['description']
143149
144150
=== modified file 'lava_scheduler_daemon/dbjobsource.py'
--- lava_scheduler_daemon/dbjobsource.py 2013-06-13 14:53:25 +0000
+++ lava_scheduler_daemon/dbjobsource.py 2013-06-20 11:53:35 +0000
@@ -103,6 +103,68 @@
103 def getBoardList(self):103 def getBoardList(self):
104 return self.deferForDB(self.getBoardList_impl)104 return self.deferForDB(self.getBoardList_impl)
105105
106 def _fix_device(self, device, job):
107 """Associate an available/idle DEVICE to the given JOB.
108
109 Returns the job with actual_device set to DEVICE.
110
111 If we are unable to grab the DEVICE then we return None.
112 """
113 DeviceStateTransition.objects.create(
114 created_by=None, device=device, old_state=device.status,
115 new_state=Device.RUNNING, message=None, job=job).save()
116 device.status = Device.RUNNING
117 device.current_job = job
118 try:
119 # The unique constraint on current_job may cause this to
120 # fail in the case of concurrent requests for different
121 # boards grabbing the same job. If there are concurrent
122 # requests for the *same* board they may both return the
123 # same job -- this is an application level bug though.
124 device.save()
125 except IntegrityError:
126 self.logger.info(
127 "job %s has been assigned to another board -- rolling back",
128 job.id)
129 transaction.rollback()
130 return None
131 else:
132 job.actual_device = device
133 job.save()
134 transaction.commit()
135 return job
136
137 def getJobList_impl(self):
138 jobs = TestJob.objects.all().filter(status=TestJob.SUBMITTED)
139 job_list = []
140 devices = None
141
142 for job in jobs:
143 if job.actual_device:
144 job_list.append(job)
145 elif job.requested_device:
146 self.logger.info("Checking Requested Device")
147 devices = Device.objects.all().filter(
148 hostname=job.requested_device.hostname,
149 status=Device.IDLE)
150 elif job.requested_device_type:
151 self.logger.info("Checking Requested Device Type")
152 devices = Device.objects.all().filter(
153 device_type=job.requested_device_type,
154 status=Device.IDLE)
155 else:
156 continue
157 if devices:
158 device = devices[0]
159 job = self._fix_device(device, job)
160 if job:
161 job_list.append(job)
162
163 return job_list
164
165 def getJobList(self):
166 return self.deferForDB(self.getJobList_impl)
167
106 def _get_json_data(self, job):168 def _get_json_data(self, job):
107 json_data = simplejson.loads(job.definition)169 json_data = simplejson.loads(job.definition)
108 json_data['target'] = job.actual_device.hostname170 json_data['target'] = job.actual_device.hostname
@@ -229,6 +291,20 @@
229 def getJobForBoard(self, board_name):291 def getJobForBoard(self, board_name):
230 return self.deferForDB(self.getJobForBoard_impl, board_name)292 return self.deferForDB(self.getJobForBoard_impl, board_name)
231293
294 def getJobDetails_impl(self, job):
295 job.status = TestJob.RUNNING
296 job.start_time = datetime.datetime.utcnow()
297 shutil.rmtree(job.output_dir, ignore_errors=True)
298 job.log_file.save('job-%s.log' % job.id, ContentFile(''), save=False)
299 job.submit_token = AuthToken.objects.create(user=job.submitter)
300 job.save()
301 json_data = self._get_json_data(job)
302 transaction.commit()
303 return json_data
304
305 def getJobDetails(self, job):
306 return self.deferForDB(self.getJobDetails_impl, job)
307
232 def getOutputDirForJobOnBoard_impl(self, board_name):308 def getOutputDirForJobOnBoard_impl(self, board_name):
233 device = Device.objects.get(hostname=board_name)309 device = Device.objects.get(hostname=board_name)
234 job = device.current_job310 job = device.current_job
235311
=== added file 'lava_scheduler_daemon/job.py'
--- lava_scheduler_daemon/job.py 1970-01-01 00:00:00 +0000
+++ lava_scheduler_daemon/job.py 2013-06-20 11:53:35 +0000
@@ -0,0 +1,79 @@
1# Copyright (C) 2013 Linaro Limited
2#
3# Author: Senthil Kumaran <senthil.kumaran@linaro.org>
4#
5# This file is part of LAVA Scheduler.
6#
7# LAVA Scheduler is free software: you can redistribute it and/or modify it
8# under the terms of the GNU Affero General Public License version 3 as
9# published by the Free Software Foundation
10#
11# LAVA Scheduler is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14# more details.
15#
16# You should have received a copy of the GNU Affero General Public License
17# along with LAVA Scheduler. If not, see <http://www.gnu.org/licenses/>.
18
19import logging
20
21from twisted.internet import defer
22from lava_scheduler_daemon.board import MonitorJob, catchall_errback
23
24class NewJob(object):
25 job_cls = MonitorJob
26
27 def __init__(self, source, job, dispatcher, reactor, daemon_options,
28 job_cls=None):
29 self.source = source
30 self.dispatcher = dispatcher
31 self.reactor = reactor
32 self.daemon_options = daemon_options
33 self.job = job
34 self.board_name = job.actual_device.hostname
35 if job_cls is not None:
36 self.job_cls = job_cls
37 self.running_job = None
38 self.logger = logging.getLogger(__name__ + '.NewJob.' + str(job.id))
39
40 def start(self):
41 self.logger.debug("processing job")
42 if self.job is None:
43 self.logger.debug("no job found for processing")
44 return
45 self.source.getJobDetails(self.job).addCallbacks(
46 self._startJob, self._ebStartJob)
47
48 def _startJob(self, job_data):
49 if job_data is None:
50 self.logger.debug("no job found")
51 return
52 self.logger.info("starting job %r", job_data)
53
54 self.running_job = self.job_cls(
55 job_data, self.dispatcher, self.source, self.board_name,
56 self.reactor, self.daemon_options)
57 d = self.running_job.run()
58 d.addCallbacks(self._cbJobFinished, self._ebJobFinished)
59
60 def _ebStartJob(self, result):
61 self.logger.error(
62 '%s: %s\n%s', result.type.__name__, result.value,
63 result.getTraceback())
64 return
65
66 def stop(self):
67 self.logger.debug("stopping")
68
69 if self.running_job is not None:
70 self.logger.debug("job running; deferring stop")
71 else:
72 self.logger.debug("stopping immediately")
73 return defer.succeed(None)
74
75 def _ebJobFinished(self, result):
76 self.logger.exception(result.value)
77
78 def _cbJobFinished(self, result):
79 self.running_job = None
080
=== modified file 'lava_scheduler_daemon/service.py'
--- lava_scheduler_daemon/service.py 2012-12-03 05:03:38 +0000
+++ lava_scheduler_daemon/service.py 2013-06-20 11:53:35 +0000
@@ -5,6 +5,7 @@
5from twisted.internet.task import LoopingCall5from twisted.internet.task import LoopingCall
66
7from lava_scheduler_daemon.board import Board, catchall_errback7from lava_scheduler_daemon.board import Board, catchall_errback
8from lava_scheduler_daemon.job import NewJob
89
910
10class BoardSet(Service):11class BoardSet(Service):
@@ -56,3 +57,35 @@
56 self.logger.info(57 self.logger.info(
57 "waiting for %s boards", len(self.boards) - len(dead_boards))58 "waiting for %s boards", len(self.boards) - len(dead_boards))
58 return defer.gatherResults(ds)59 return defer.gatherResults(ds)
60
61
62class JobQueue(Service):
63
64 def __init__(self, source, dispatcher, reactor, daemon_options):
65 self.logger = logging.getLogger(__name__ + '.JobQueue')
66 self.source = source
67 self.dispatcher = dispatcher
68 self.reactor = reactor
69 self.daemon_options = daemon_options
70 self._check_job_call = LoopingCall(self._checkJobs)
71 self._check_job_call.clock = reactor
72
73 def _checkJobs(self):
74 self.logger.debug("Refreshing jobs")
75 return self.source.getJobList().addCallback(
76 self._cbCheckJobs).addErrback(catchall_errback(self.logger))
77
78 def _cbCheckJobs(self, job_list):
79 for job in job_list:
80 self.logger.debug("Found job: %d" % job.id)
81 new_job = NewJob(self.source, job, self.dispatcher, self.reactor,
82 self.daemon_options)
83 self.logger.info("Starting Job: %d " % job.id)
84 new_job.start()
85
86 def startService(self):
87 self._check_job_call.start(20)
88
89 def stopService(self):
90 self._check_job_call.stop()
91 return None

Subscribers

People subscribed via source and target branches

to status/vote changes: