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
1=== modified file 'lava_scheduler_app/api.py'
2--- lava_scheduler_app/api.py 2013-05-02 07:35:44 +0000
3+++ lava_scheduler_app/api.py 2013-06-20 11:53:35 +0000
4@@ -36,7 +36,7 @@
5 raise xmlrpclib.Fault(404, "Specified device not found.")
6 except DeviceType.DoesNotExist:
7 raise xmlrpclib.Fault(404, "Specified device type not found.")
8- return job.id
9+ return job
10
11 def resubmit_job(self, job_id):
12 try:
13
14=== modified file 'lava_scheduler_app/management/commands/scheduler.py'
15--- lava_scheduler_app/management/commands/scheduler.py 2012-03-27 05:41:09 +0000
16+++ lava_scheduler_app/management/commands/scheduler.py 2013-06-20 11:53:35 +0000
17@@ -42,7 +42,7 @@
18
19 from twisted.internet import reactor
20
21- from lava_scheduler_daemon.service import BoardSet
22+ from lava_scheduler_daemon.service import BoardSet, JobQueue
23 from lava_scheduler_daemon.dbjobsource import DatabaseJobSource
24
25 daemon_options = self._configure(options)
26@@ -57,7 +57,7 @@
27 'fake-dispatcher')
28 else:
29 dispatcher = options['dispatcher']
30- service = BoardSet(
31+ service = JobQueue(
32 source, dispatcher, reactor, daemon_options=daemon_options)
33 reactor.callWhenRunning(service.startService)
34 reactor.run()
35
36=== added file 'lava_scheduler_app/migrations/0030_auto__add_field_testjob_sub_id.py'
37--- lava_scheduler_app/migrations/0030_auto__add_field_testjob_sub_id.py 1970-01-01 00:00:00 +0000
38+++ lava_scheduler_app/migrations/0030_auto__add_field_testjob_sub_id.py 2013-06-20 11:53:35 +0000
39@@ -0,0 +1,160 @@
40+# -*- coding: utf-8 -*-
41+import datetime
42+from south.db import db
43+from south.v2 import SchemaMigration
44+from django.db import models
45+
46+
47+class Migration(SchemaMigration):
48+
49+ def forwards(self, orm):
50+ # Adding field 'TestJob.sub_id'
51+ db.add_column('lava_scheduler_app_testjob', 'sub_id',
52+ self.gf('django.db.models.fields.CharField')(default='', max_length=200, blank=True),
53+ keep_default=False)
54+
55+
56+ def backwards(self, orm):
57+ # Deleting field 'TestJob.sub_id'
58+ db.delete_column('lava_scheduler_app_testjob', 'sub_id')
59+
60+
61+ models = {
62+ 'auth.group': {
63+ 'Meta': {'object_name': 'Group'},
64+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
65+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
66+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
67+ },
68+ 'auth.permission': {
69+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
70+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
71+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
72+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
73+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
74+ },
75+ 'auth.user': {
76+ 'Meta': {'object_name': 'User'},
77+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
78+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
79+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
80+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
81+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
82+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
83+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
84+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
85+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
86+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
87+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
88+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
89+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
90+ },
91+ 'contenttypes.contenttype': {
92+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
93+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
94+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
95+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
96+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
97+ },
98+ 'dashboard_app.bundle': {
99+ 'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'},
100+ '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}),
101+ '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}),
102+ 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}),
103+ 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
104+ 'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}),
105+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
106+ 'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
107+ 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}),
108+ 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
109+ },
110+ 'dashboard_app.bundlestream': {
111+ 'Meta': {'object_name': 'BundleStream'},
112+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
113+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
114+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
115+ 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
116+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
117+ 'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
118+ 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
119+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
120+ },
121+ 'lava_scheduler_app.device': {
122+ 'Meta': {'object_name': 'Device'},
123+ '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'}),
124+ 'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']"}),
125+ 'device_version': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'null': 'True', 'blank': 'True'}),
126+ 'health_status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
127+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '200', 'primary_key': 'True'}),
128+ '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'}),
129+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
130+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['lava_scheduler_app.Tag']", 'symmetrical': 'False', 'blank': 'True'})
131+ },
132+ 'lava_scheduler_app.devicestatetransition': {
133+ 'Meta': {'object_name': 'DeviceStateTransition'},
134+ 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
135+ 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
136+ 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['lava_scheduler_app.Device']"}),
137+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
138+ 'job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.TestJob']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
139+ 'message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
140+ 'new_state': ('django.db.models.fields.IntegerField', [], {}),
141+ 'old_state': ('django.db.models.fields.IntegerField', [], {})
142+ },
143+ 'lava_scheduler_app.devicetype': {
144+ 'Meta': {'object_name': 'DeviceType'},
145+ 'display': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
146+ 'health_check_job': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
147+ 'name': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True'})
148+ },
149+ 'lava_scheduler_app.jobfailuretag': {
150+ 'Meta': {'object_name': 'JobFailureTag'},
151+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
152+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
153+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'})
154+ },
155+ 'lava_scheduler_app.tag': {
156+ 'Meta': {'object_name': 'Tag'},
157+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
158+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
159+ 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'})
160+ },
161+ 'lava_scheduler_app.testjob': {
162+ 'Meta': {'object_name': 'TestJob'},
163+ '_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'}),
164+ '_results_link': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '400', 'null': 'True', 'db_column': "'results_link'", 'blank': 'True'}),
165+ 'actual_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.Device']"}),
166+ 'definition': ('django.db.models.fields.TextField', [], {}),
167+ 'description': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'null': 'True', 'blank': 'True'}),
168+ 'end_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
169+ 'failure_comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
170+ 'failure_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'failure_tags'", 'blank': 'True', 'to': "orm['lava_scheduler_app.JobFailureTag']"}),
171+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
172+ 'health_check': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
173+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
174+ 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
175+ 'log_file': ('django.db.models.fields.files.FileField', [], {'default': 'None', 'max_length': '100', 'null': 'True', 'blank': 'True'}),
176+ 'priority': ('django.db.models.fields.IntegerField', [], {'default': '50'}),
177+ 'requested_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.Device']"}),
178+ 'requested_device_type': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.DeviceType']"}),
179+ 'start_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
180+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
181+ 'sub_id': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
182+ 'submit_time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
183+ 'submit_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['linaro_django_xmlrpc.AuthToken']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
184+ 'submitter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}),
185+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['lava_scheduler_app.Tag']", 'symmetrical': 'False', 'blank': 'True'}),
186+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
187+ },
188+ 'linaro_django_xmlrpc.authtoken': {
189+ 'Meta': {'object_name': 'AuthToken'},
190+ 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
191+ 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
192+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
193+ 'last_used_on': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
194+ 'secret': ('django.db.models.fields.CharField', [], {'default': "'ynu6yihw337isktsrzd0ocr83r0huox1b5y4qs1c0ktat7s3089d2xgiz3ll0n68fr6q026mep0t5xwg1coxnl2aoknolgowx2779uluan8quez0row0jnk5j2qsxlle'", 'unique': 'True', 'max_length': '128'}),
195+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': "orm['auth.User']"})
196+ }
197+ }
198+
199+ complete_apps = ['lava_scheduler_app']
200\ No newline at end of file
201
202=== modified file 'lava_scheduler_app/models.py'
203--- lava_scheduler_app/models.py 2013-02-10 21:26:06 +0000
204+++ lava_scheduler_app/models.py 2013-06-20 11:53:35 +0000
205@@ -2,6 +2,7 @@
206 import os
207 import simplejson
208 import urlparse
209+import copy
210
211 from django.conf import settings
212 from django.contrib.auth.models import User
213@@ -249,6 +250,12 @@
214
215 id = models.AutoField(primary_key=True)
216
217+ sub_id = models.CharField(
218+ verbose_name = _(u"Sub ID"),
219+ blank = True,
220+ max_length = 200
221+ )
222+
223 submitter = models.ForeignKey(
224 User,
225 verbose_name = _(u"Submitter"),
226@@ -398,9 +405,13 @@
227 elif 'device_type' in job_data:
228 target = None
229 device_type = DeviceType.objects.get(name=job_data['device_type'])
230+ elif 'device_group' in job_data:
231+ target = None
232+ device_type = None
233 else:
234 raise JSONDataError(
235- "Neither 'target' nor 'device_type' found in job data.")
236+ "No 'target' or 'device_type' or 'device_group' are found "
237+ "in job data.")
238
239 priorities = dict([(j.upper(), i) for i, j in cls.PRIORITY_CHOICES])
240 priority = cls.MEDIUM
241@@ -463,15 +474,14 @@
242 tags.append(Tag.objects.get(name=tag_name))
243 except Tag.DoesNotExist:
244 raise JSONDataError("tag %r does not exist" % tag_name)
245+
246 job = TestJob(
247 definition=json_data, submitter=submitter,
248 requested_device=target, requested_device_type=device_type,
249 description=job_name, health_check=health_check, user=user,
250 group=group, is_public=is_public, priority=priority)
251 job.save()
252- for tag in tags:
253- job.tags.add(tag)
254- return job
255+ return job.id
256
257 def _can_admin(self, user):
258 """ used to check for things like if the user can cancel or annotate
259
260=== modified file 'lava_scheduler_app/views.py'
261--- lava_scheduler_app/views.py 2013-05-02 09:00:04 +0000
262+++ lava_scheduler_app/views.py 2013-06-20 11:53:35 +0000
263@@ -74,10 +74,16 @@
264
265
266 def pklink(record):
267+ job_id = record.pk
268+ try:
269+ if record.sub_id:
270+ job_id = record.sub_id
271+ except:
272+ pass
273 return mark_safe(
274 '<a href="%s">%s</a>' % (
275 record.get_absolute_url(),
276- escape(record.pk)))
277+ escape(job_id)))
278
279
280 class IDLinkColumn(Column):
281@@ -100,13 +106,13 @@
282
283
284 def all_jobs_with_device_sort():
285- return TestJob.objects.select_related(
286+ jobs = TestJob.objects.select_related(
287 "actual_device", "requested_device", "requested_device_type",
288 "submitter", "user", "group").extra(
289 select={
290 'device_sort': 'coalesce(actual_device_id, requested_device_id, requested_device_type_id)'
291 }).all()
292-
293+ return jobs.order_by('submit_time')
294
295
296 class JobTable(DataTablesTable):
297@@ -126,7 +132,7 @@
298 else:
299 return ''
300
301- id = RestrictedIDLinkColumn()
302+ sub_id = RestrictedIDLinkColumn()
303 status = Column()
304 priority = Column()
305 device = Column(accessor='device_sort')
306@@ -137,7 +143,7 @@
307 duration = Column()
308
309 datatable_opts = {
310- 'aaSorting': [[0, 'desc']],
311+ 'aaSorting': [[6, 'desc']],
312 }
313 searchable_columns=['description']
314
315
316=== modified file 'lava_scheduler_daemon/dbjobsource.py'
317--- lava_scheduler_daemon/dbjobsource.py 2013-06-13 14:53:25 +0000
318+++ lava_scheduler_daemon/dbjobsource.py 2013-06-20 11:53:35 +0000
319@@ -103,6 +103,68 @@
320 def getBoardList(self):
321 return self.deferForDB(self.getBoardList_impl)
322
323+ def _fix_device(self, device, job):
324+ """Associate an available/idle DEVICE to the given JOB.
325+
326+ Returns the job with actual_device set to DEVICE.
327+
328+ If we are unable to grab the DEVICE then we return None.
329+ """
330+ DeviceStateTransition.objects.create(
331+ created_by=None, device=device, old_state=device.status,
332+ new_state=Device.RUNNING, message=None, job=job).save()
333+ device.status = Device.RUNNING
334+ device.current_job = job
335+ try:
336+ # The unique constraint on current_job may cause this to
337+ # fail in the case of concurrent requests for different
338+ # boards grabbing the same job. If there are concurrent
339+ # requests for the *same* board they may both return the
340+ # same job -- this is an application level bug though.
341+ device.save()
342+ except IntegrityError:
343+ self.logger.info(
344+ "job %s has been assigned to another board -- rolling back",
345+ job.id)
346+ transaction.rollback()
347+ return None
348+ else:
349+ job.actual_device = device
350+ job.save()
351+ transaction.commit()
352+ return job
353+
354+ def getJobList_impl(self):
355+ jobs = TestJob.objects.all().filter(status=TestJob.SUBMITTED)
356+ job_list = []
357+ devices = None
358+
359+ for job in jobs:
360+ if job.actual_device:
361+ job_list.append(job)
362+ elif job.requested_device:
363+ self.logger.info("Checking Requested Device")
364+ devices = Device.objects.all().filter(
365+ hostname=job.requested_device.hostname,
366+ status=Device.IDLE)
367+ elif job.requested_device_type:
368+ self.logger.info("Checking Requested Device Type")
369+ devices = Device.objects.all().filter(
370+ device_type=job.requested_device_type,
371+ status=Device.IDLE)
372+ else:
373+ continue
374+ if devices:
375+ device = devices[0]
376+ job = self._fix_device(device, job)
377+ if job:
378+ job_list.append(job)
379+
380+ return job_list
381+
382+ def getJobList(self):
383+ return self.deferForDB(self.getJobList_impl)
384+
385 def _get_json_data(self, job):
386 json_data = simplejson.loads(job.definition)
387 json_data['target'] = job.actual_device.hostname
388@@ -229,6 +291,20 @@
389 def getJobForBoard(self, board_name):
390 return self.deferForDB(self.getJobForBoard_impl, board_name)
391
392+ def getJobDetails_impl(self, job):
393+ job.status = TestJob.RUNNING
394+ job.start_time = datetime.datetime.utcnow()
395+ shutil.rmtree(job.output_dir, ignore_errors=True)
396+ job.log_file.save('job-%s.log' % job.id, ContentFile(''), save=False)
397+ job.submit_token = AuthToken.objects.create(user=job.submitter)
398+ job.save()
399+ json_data = self._get_json_data(job)
400+ transaction.commit()
401+ return json_data
402+
403+ def getJobDetails(self, job):
404+ return self.deferForDB(self.getJobDetails_impl, job)
405+
406 def getOutputDirForJobOnBoard_impl(self, board_name):
407 device = Device.objects.get(hostname=board_name)
408 job = device.current_job
409
410=== added file 'lava_scheduler_daemon/job.py'
411--- lava_scheduler_daemon/job.py 1970-01-01 00:00:00 +0000
412+++ lava_scheduler_daemon/job.py 2013-06-20 11:53:35 +0000
413@@ -0,0 +1,79 @@
414+# Copyright (C) 2013 Linaro Limited
415+#
416+# Author: Senthil Kumaran <senthil.kumaran@linaro.org>
417+#
418+# This file is part of LAVA Scheduler.
419+#
420+# LAVA Scheduler is free software: you can redistribute it and/or modify it
421+# under the terms of the GNU Affero General Public License version 3 as
422+# published by the Free Software Foundation
423+#
424+# LAVA Scheduler is distributed in the hope that it will be useful, but
425+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
426+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
427+# more details.
428+#
429+# You should have received a copy of the GNU Affero General Public License
430+# along with LAVA Scheduler. If not, see <http://www.gnu.org/licenses/>.
431+
432+import logging
433+
434+from twisted.internet import defer
435+from lava_scheduler_daemon.board import MonitorJob, catchall_errback
436+
437+class NewJob(object):
438+ job_cls = MonitorJob
439+
440+ def __init__(self, source, job, dispatcher, reactor, daemon_options,
441+ job_cls=None):
442+ self.source = source
443+ self.dispatcher = dispatcher
444+ self.reactor = reactor
445+ self.daemon_options = daemon_options
446+ self.job = job
447+ self.board_name = job.actual_device.hostname
448+ if job_cls is not None:
449+ self.job_cls = job_cls
450+ self.running_job = None
451+ self.logger = logging.getLogger(__name__ + '.NewJob.' + str(job.id))
452+
453+ def start(self):
454+ self.logger.debug("processing job")
455+ if self.job is None:
456+ self.logger.debug("no job found for processing")
457+ return
458+ self.source.getJobDetails(self.job).addCallbacks(
459+ self._startJob, self._ebStartJob)
460+
461+ def _startJob(self, job_data):
462+ if job_data is None:
463+ self.logger.debug("no job found")
464+ return
465+ self.logger.info("starting job %r", job_data)
466+
467+ self.running_job = self.job_cls(
468+ job_data, self.dispatcher, self.source, self.board_name,
469+ self.reactor, self.daemon_options)
470+ d = self.running_job.run()
471+ d.addCallbacks(self._cbJobFinished, self._ebJobFinished)
472+
473+ def _ebStartJob(self, result):
474+ self.logger.error(
475+ '%s: %s\n%s', result.type.__name__, result.value,
476+ result.getTraceback())
477+ return
478+
479+ def stop(self):
480+ self.logger.debug("stopping")
481+
482+ if self.running_job is not None:
483+ self.logger.debug("job running; deferring stop")
484+ else:
485+ self.logger.debug("stopping immediately")
486+ return defer.succeed(None)
487+
488+ def _ebJobFinished(self, result):
489+ self.logger.exception(result.value)
490+
491+ def _cbJobFinished(self, result):
492+ self.running_job = None
493
494=== modified file 'lava_scheduler_daemon/service.py'
495--- lava_scheduler_daemon/service.py 2012-12-03 05:03:38 +0000
496+++ lava_scheduler_daemon/service.py 2013-06-20 11:53:35 +0000
497@@ -5,6 +5,7 @@
498 from twisted.internet.task import LoopingCall
499
500 from lava_scheduler_daemon.board import Board, catchall_errback
501+from lava_scheduler_daemon.job import NewJob
502
503
504 class BoardSet(Service):
505@@ -56,3 +57,35 @@
506 self.logger.info(
507 "waiting for %s boards", len(self.boards) - len(dead_boards))
508 return defer.gatherResults(ds)
509+
510+
511+class JobQueue(Service):
512+
513+ def __init__(self, source, dispatcher, reactor, daemon_options):
514+ self.logger = logging.getLogger(__name__ + '.JobQueue')
515+ self.source = source
516+ self.dispatcher = dispatcher
517+ self.reactor = reactor
518+ self.daemon_options = daemon_options
519+ self._check_job_call = LoopingCall(self._checkJobs)
520+ self._check_job_call.clock = reactor
521+
522+ def _checkJobs(self):
523+ self.logger.debug("Refreshing jobs")
524+ return self.source.getJobList().addCallback(
525+ self._cbCheckJobs).addErrback(catchall_errback(self.logger))
526+
527+ def _cbCheckJobs(self, job_list):
528+ for job in job_list:
529+ self.logger.debug("Found job: %d" % job.id)
530+ new_job = NewJob(self.source, job, self.dispatcher, self.reactor,
531+ self.daemon_options)
532+ self.logger.info("Starting Job: %d " % job.id)
533+ new_job.start()
534+
535+ def startService(self):
536+ self._check_job_call.start(20)
537+
538+ def stopService(self):
539+ self._check_job_call.stop()
540+ return None

Subscribers

People subscribed via source and target branches

to status/vote changes: