Merge lp:~mwhudson/lava-scheduler/create-private-job into lp:lava-scheduler
- create-private-job
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~mwhudson/lava-scheduler/create-private-job |
Merge into: | lp:lava-scheduler |
Diff against target: |
433 lines (+148/-63) 4 files modified
lava_scheduler_app/models.py (+30/-11) lava_scheduler_app/tests.py (+112/-34) lava_scheduler_daemon/dbjobsource.py (+4/-18) setup.py (+2/-0) |
To merge this branch: | bzr merge lp:~mwhudson/lava-scheduler/create-private-job |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Zygmunt Krynicki (community) | Approve | ||
Review via email: mp+96703@code.launchpad.net |
Commit message
Description of the change
This branch enables the creation of private testjobs. It does so by looking for the bundle stream named by the submit_results action and copying the access data over to the new job. Because this means poking around in the JSON way more than before, I also finally implemented validation of the json at submit_job time -- this branch depends on https:/
So, what do you think? I think I more or less like this approach -- it avoids repetition of access information and the potential for inconsistency there. I do find grubbing through the actions for the submit_results action to be a bit strange still, both for the UX and the implementation -- I think I would be happier if the bundle_stream name and (ideally, optional, at least for jobs that run through the scheduler) xml-rpc endpoint were top level properties of the job description. But that can wait for another day, perhaps.
Cheers,
mwh
Zygmunt Krynicki (zyga) wrote : | # |
Zygmunt Krynicki (zyga) wrote : | # |
Otherwise this looks good :-) +1
Unmerged revisions
Preview Diff
1 | === modified file 'lava_scheduler_app/models.py' | |||
2 | --- lava_scheduler_app/models.py 2012-03-07 01:56:57 +0000 | |||
3 | +++ lava_scheduler_app/models.py 2012-03-09 04:16:33 +0000 | |||
4 | @@ -3,16 +3,17 @@ | |||
5 | 3 | from django.contrib.auth.models import User | 3 | from django.contrib.auth.models import User |
6 | 4 | from django.core.exceptions import ValidationError | 4 | from django.core.exceptions import ValidationError |
7 | 5 | from django.db import models | 5 | from django.db import models |
8 | 6 | from django.db.models.query import QuerySet | ||
9 | 7 | from django.utils.translation import ugettext as _ | 6 | from django.utils.translation import ugettext as _ |
10 | 8 | 7 | ||
11 | 9 | |||
12 | 10 | from django_restricted_resource.managers import RestrictedResourceManager | ||
13 | 11 | from django_restricted_resource.models import RestrictedResource | 8 | from django_restricted_resource.models import RestrictedResource |
15 | 12 | from django_restricted_resource.utils import filter_bogus_users | 9 | |
16 | 10 | from dashboard_app.models import BundleStream | ||
17 | 11 | |||
18 | 12 | from lava_dispatcher.job import validate_job_data | ||
19 | 13 | 13 | ||
20 | 14 | from linaro_django_xmlrpc.models import AuthToken | 14 | from linaro_django_xmlrpc.models import AuthToken |
21 | 15 | 15 | ||
22 | 16 | |||
23 | 16 | class JSONDataError(ValueError): | 17 | class JSONDataError(ValueError): |
24 | 17 | """Error raised when JSON is syntactically valid but ill-formed.""" | 18 | """Error raised when JSON is syntactically valid but ill-formed.""" |
25 | 18 | 19 | ||
26 | @@ -30,12 +31,9 @@ | |||
27 | 30 | def validate_job_json(data): | 31 | def validate_job_json(data): |
28 | 31 | try: | 32 | try: |
29 | 32 | ob = simplejson.loads(data) | 33 | ob = simplejson.loads(data) |
30 | 34 | validate_job_data(ob) | ||
31 | 33 | except ValueError, e: | 35 | except ValueError, e: |
32 | 34 | raise ValidationError(str(e)) | 36 | raise ValidationError(str(e)) |
33 | 35 | else: | ||
34 | 36 | if not isinstance(ob, dict): | ||
35 | 37 | raise ValidationError( | ||
36 | 38 | "job json must be an object, not %s" % type(ob).__name__) | ||
37 | 39 | 37 | ||
38 | 40 | 38 | ||
39 | 41 | class DeviceType(models.Model): | 39 | class DeviceType(models.Model): |
40 | @@ -266,6 +264,7 @@ | |||
41 | 266 | @classmethod | 264 | @classmethod |
42 | 267 | def from_json_and_user(cls, json_data, user): | 265 | def from_json_and_user(cls, json_data, user): |
43 | 268 | job_data = simplejson.loads(json_data) | 266 | job_data = simplejson.loads(json_data) |
44 | 267 | validate_job_data(job_data) | ||
45 | 269 | if 'target' in job_data: | 268 | if 'target' in job_data: |
46 | 270 | target = Device.objects.get(hostname=job_data['target']) | 269 | target = Device.objects.get(hostname=job_data['target']) |
47 | 271 | device_type = None | 270 | device_type = None |
48 | @@ -279,6 +278,25 @@ | |||
49 | 279 | 278 | ||
50 | 280 | is_check = job_data.get('health_check', False) | 279 | is_check = job_data.get('health_check', False) |
51 | 281 | 280 | ||
52 | 281 | submitter = user | ||
53 | 282 | group = None | ||
54 | 283 | is_public = True | ||
55 | 284 | |||
56 | 285 | for action in job_data['actions']: | ||
57 | 286 | if not action['command'].startswith('submit_results'): | ||
58 | 287 | continue | ||
59 | 288 | stream = action['parameters']['stream'] | ||
60 | 289 | try: | ||
61 | 290 | bundle_stream = BundleStream.objects.get(pathname=stream) | ||
62 | 291 | except BundleStream.DoesNotExist: | ||
63 | 292 | raise ValueError("stream %s not found" % stream) | ||
64 | 293 | if not bundle_stream.is_owned_by(submitter): | ||
65 | 294 | raise ValueError( | ||
66 | 295 | "you cannot submit to the stream %s" % stream) | ||
67 | 296 | user, group, is_public = (bundle_stream.user, | ||
68 | 297 | bundle_stream.group, | ||
69 | 298 | bundle_stream.is_public) | ||
70 | 299 | |||
71 | 282 | tags = [] | 300 | tags = [] |
72 | 283 | for tag_name in job_data.get('device_tags', []): | 301 | for tag_name in job_data.get('device_tags', []): |
73 | 284 | try: | 302 | try: |
74 | @@ -286,9 +304,10 @@ | |||
75 | 286 | except Tag.DoesNotExist: | 304 | except Tag.DoesNotExist: |
76 | 287 | raise JSONDataError("tag %r does not exist" % tag_name) | 305 | raise JSONDataError("tag %r does not exist" % tag_name) |
77 | 288 | job = TestJob( | 306 | job = TestJob( |
81 | 289 | definition=json_data, submitter=user, requested_device=target, | 307 | definition=json_data, submitter=submitter, |
82 | 290 | requested_device_type=device_type, description=job_name, | 308 | requested_device=target, requested_device_type=device_type, |
83 | 291 | health_check=is_check, user=user) | 309 | description=job_name, health_check=is_check, user=user, |
84 | 310 | group=group, is_public=is_public) | ||
85 | 292 | job.save() | 311 | job.save() |
86 | 293 | for tag in tags: | 312 | for tag in tags: |
87 | 294 | job.tags.add(tag) | 313 | job.tags.add(tag) |
88 | 295 | 314 | ||
89 | === modified file 'lava_scheduler_app/tests.py' | |||
90 | --- lava_scheduler_app/tests.py 2012-02-28 03:53:16 +0000 | |||
91 | +++ lava_scheduler_app/tests.py 2012-03-09 04:16:33 +0000 | |||
92 | @@ -3,7 +3,9 @@ | |||
93 | 3 | import json | 3 | import json |
94 | 4 | import xmlrpclib | 4 | import xmlrpclib |
95 | 5 | 5 | ||
97 | 6 | from django.contrib.auth.models import Permission, User | 6 | from dashboard_app.models import BundleStream |
98 | 7 | |||
99 | 8 | from django.contrib.auth.models import Group, Permission, User | ||
100 | 7 | from django.test import TransactionTestCase | 9 | from django.test import TransactionTestCase |
101 | 8 | from django.test.client import Client | 10 | from django.test.client import Client |
102 | 9 | 11 | ||
103 | @@ -84,9 +86,22 @@ | |||
104 | 84 | device.save() | 86 | device.save() |
105 | 85 | return device | 87 | return device |
106 | 86 | 88 | ||
107 | 89 | def make_job_data(self, actions=[], **kw): | ||
108 | 90 | data = {'actions': actions, 'timeout': 1} | ||
109 | 91 | data.update(kw) | ||
110 | 92 | if 'target' not in data and 'device_type' not in data: | ||
111 | 93 | if DeviceType.objects.all(): | ||
112 | 94 | data['device_type'] = DeviceType.objects.all()[0].name | ||
113 | 95 | else: | ||
114 | 96 | data['device_type'] = self.ensure_device_type().name | ||
115 | 97 | return data | ||
116 | 98 | |||
117 | 99 | def make_job_json(self, **kw): | ||
118 | 100 | return json.dumps(self.make_job_data(**kw)) | ||
119 | 101 | |||
120 | 87 | def make_testjob(self, definition=None, submitter=None, **kwargs): | 102 | def make_testjob(self, definition=None, submitter=None, **kwargs): |
121 | 88 | if definition is None: | 103 | if definition is None: |
123 | 89 | definition = json.dumps({}) | 104 | definition = self.make_job_json() |
124 | 90 | if submitter is None: | 105 | if submitter is None: |
125 | 91 | submitter = self.make_user() | 106 | submitter = self.make_user() |
126 | 92 | if 'user' not in kwargs: | 107 | if 'user' not in kwargs: |
127 | @@ -107,70 +122,68 @@ | |||
128 | 107 | class TestTestJob(TestCaseWithFactory): | 122 | class TestTestJob(TestCaseWithFactory): |
129 | 108 | 123 | ||
130 | 109 | def test_from_json_and_user_sets_definition(self): | 124 | def test_from_json_and_user_sets_definition(self): |
133 | 110 | self.factory.ensure_device_type(name='panda') | 125 | definition = self.factory.make_job_json() |
132 | 111 | definition = json.dumps({'device_type':'panda'}) | ||
134 | 112 | job = TestJob.from_json_and_user(definition, self.factory.make_user()) | 126 | job = TestJob.from_json_and_user(definition, self.factory.make_user()) |
135 | 113 | self.assertEqual(definition, job.definition) | 127 | self.assertEqual(definition, job.definition) |
136 | 114 | 128 | ||
137 | 115 | def test_from_json_and_user_sets_submitter(self): | 129 | def test_from_json_and_user_sets_submitter(self): |
138 | 116 | self.factory.ensure_device_type(name='panda') | ||
139 | 117 | user = self.factory.make_user() | 130 | user = self.factory.make_user() |
140 | 118 | job = TestJob.from_json_and_user( | 131 | job = TestJob.from_json_and_user( |
142 | 119 | json.dumps({'device_type':'panda'}), user) | 132 | self.factory.make_job_json(), user) |
143 | 120 | self.assertEqual(user, job.submitter) | 133 | self.assertEqual(user, job.submitter) |
144 | 121 | 134 | ||
145 | 122 | def test_from_json_and_user_sets_device_type(self): | 135 | def test_from_json_and_user_sets_device_type(self): |
146 | 123 | panda_type = self.factory.ensure_device_type(name='panda') | 136 | panda_type = self.factory.ensure_device_type(name='panda') |
147 | 124 | job = TestJob.from_json_and_user( | 137 | job = TestJob.from_json_and_user( |
149 | 125 | json.dumps({'device_type':'panda'}), self.factory.make_user()) | 138 | self.factory.make_job_json(device_type='panda'), |
150 | 139 | self.factory.make_user()) | ||
151 | 126 | self.assertEqual(panda_type, job.requested_device_type) | 140 | self.assertEqual(panda_type, job.requested_device_type) |
152 | 127 | 141 | ||
153 | 128 | def test_from_json_and_user_sets_target(self): | 142 | def test_from_json_and_user_sets_target(self): |
154 | 129 | panda_board = self.factory.make_device(hostname='panda01') | 143 | panda_board = self.factory.make_device(hostname='panda01') |
155 | 130 | job = TestJob.from_json_and_user( | 144 | job = TestJob.from_json_and_user( |
157 | 131 | json.dumps({'target':'panda01'}), self.factory.make_user()) | 145 | self.factory.make_job_json(target='panda01'), |
158 | 146 | self.factory.make_user()) | ||
159 | 132 | self.assertEqual(panda_board, job.requested_device) | 147 | self.assertEqual(panda_board, job.requested_device) |
160 | 133 | 148 | ||
161 | 134 | def test_from_json_and_user_does_not_set_device_type_from_target(self): | 149 | def test_from_json_and_user_does_not_set_device_type_from_target(self): |
162 | 135 | panda_type = self.factory.ensure_device_type(name='panda') | 150 | panda_type = self.factory.ensure_device_type(name='panda') |
163 | 136 | self.factory.make_device(device_type=panda_type, hostname='panda01') | 151 | self.factory.make_device(device_type=panda_type, hostname='panda01') |
164 | 137 | job = TestJob.from_json_and_user( | 152 | job = TestJob.from_json_and_user( |
166 | 138 | json.dumps({'target':'panda01'}), self.factory.make_user()) | 153 | self.factory.make_job_json(target='panda01'), |
167 | 154 | self.factory.make_user()) | ||
168 | 139 | self.assertEqual(None, job.requested_device_type) | 155 | self.assertEqual(None, job.requested_device_type) |
169 | 140 | 156 | ||
170 | 141 | def test_from_json_and_user_sets_date_submitted(self): | 157 | def test_from_json_and_user_sets_date_submitted(self): |
171 | 142 | self.factory.ensure_device_type(name='panda') | ||
172 | 143 | before = datetime.datetime.now() | 158 | before = datetime.datetime.now() |
173 | 144 | job = TestJob.from_json_and_user( | 159 | job = TestJob.from_json_and_user( |
175 | 145 | json.dumps({'device_type':'panda'}), self.factory.make_user()) | 160 | self.factory.make_job_json(), |
176 | 161 | self.factory.make_user()) | ||
177 | 146 | after = datetime.datetime.now() | 162 | after = datetime.datetime.now() |
178 | 147 | self.assertTrue(before < job.submit_time < after) | 163 | self.assertTrue(before < job.submit_time < after) |
179 | 148 | 164 | ||
180 | 149 | def test_from_json_and_user_sets_status_to_SUBMITTED(self): | 165 | def test_from_json_and_user_sets_status_to_SUBMITTED(self): |
181 | 150 | self.factory.ensure_device_type(name='panda') | ||
182 | 151 | job = TestJob.from_json_and_user( | 166 | job = TestJob.from_json_and_user( |
184 | 152 | json.dumps({'device_type':'panda'}), self.factory.make_user()) | 167 | self.factory.make_job_json(), |
185 | 168 | self.factory.make_user()) | ||
186 | 153 | self.assertEqual(job.status, TestJob.SUBMITTED) | 169 | self.assertEqual(job.status, TestJob.SUBMITTED) |
187 | 154 | 170 | ||
188 | 155 | def test_from_json_and_user_sets_no_tags_if_no_tags(self): | 171 | def test_from_json_and_user_sets_no_tags_if_no_tags(self): |
189 | 156 | self.factory.ensure_device_type(name='panda') | ||
190 | 157 | job = TestJob.from_json_and_user( | 172 | job = TestJob.from_json_and_user( |
192 | 158 | json.dumps({'device_type':'panda', 'device_tags':[]}), | 173 | self.factory.make_job_json(device_tags=[]), |
193 | 159 | self.factory.make_user()) | 174 | self.factory.make_user()) |
194 | 160 | self.assertEqual(set(job.tags.all()), set([])) | 175 | self.assertEqual(set(job.tags.all()), set([])) |
195 | 161 | 176 | ||
196 | 162 | def test_from_json_and_user_errors_on_unknown_tags(self): | 177 | def test_from_json_and_user_errors_on_unknown_tags(self): |
197 | 163 | self.factory.ensure_device_type(name='panda') | ||
198 | 164 | self.assertRaises( | 178 | self.assertRaises( |
199 | 165 | JSONDataError, TestJob.from_json_and_user, | 179 | JSONDataError, TestJob.from_json_and_user, |
201 | 166 | json.dumps({'device_type':'panda', 'device_tags':['unknown']}), | 180 | self.factory.make_job_json(device_tags=['unknown']), |
202 | 167 | self.factory.make_user()) | 181 | self.factory.make_user()) |
203 | 168 | 182 | ||
204 | 169 | def test_from_json_and_user_sets_tag_from_device_tags(self): | 183 | def test_from_json_and_user_sets_tag_from_device_tags(self): |
205 | 170 | self.factory.ensure_device_type(name='panda') | ||
206 | 171 | self.factory.ensure_tag('tag') | 184 | self.factory.ensure_tag('tag') |
207 | 172 | job = TestJob.from_json_and_user( | 185 | job = TestJob.from_json_and_user( |
209 | 173 | json.dumps({'device_type':'panda', 'device_tags':['tag']}), | 186 | self.factory.make_job_json(device_tags=['tag']), |
210 | 174 | self.factory.make_user()) | 187 | self.factory.make_user()) |
211 | 175 | self.assertEqual( | 188 | self.assertEqual( |
212 | 176 | set(tag.name for tag in job.tags.all()), set(['tag'])) | 189 | set(tag.name for tag in job.tags.all()), set(['tag'])) |
213 | @@ -180,7 +193,7 @@ | |||
214 | 180 | self.factory.ensure_tag('tag1') | 193 | self.factory.ensure_tag('tag1') |
215 | 181 | self.factory.ensure_tag('tag2') | 194 | self.factory.ensure_tag('tag2') |
216 | 182 | job = TestJob.from_json_and_user( | 195 | job = TestJob.from_json_and_user( |
218 | 183 | json.dumps({'device_type':'panda', 'device_tags':['tag1', 'tag2']}), | 196 | self.factory.make_job_json(device_tags=['tag1', 'tag2']), |
219 | 184 | self.factory.make_user()) | 197 | self.factory.make_user()) |
220 | 185 | self.assertEqual( | 198 | self.assertEqual( |
221 | 186 | set(tag.name for tag in job.tags.all()), set(['tag1', 'tag2'])) | 199 | set(tag.name for tag in job.tags.all()), set(['tag1', 'tag2'])) |
222 | @@ -189,15 +202,75 @@ | |||
223 | 189 | self.factory.ensure_device_type(name='panda') | 202 | self.factory.ensure_device_type(name='panda') |
224 | 190 | self.factory.ensure_tag('tag') | 203 | self.factory.ensure_tag('tag') |
225 | 191 | job1 = TestJob.from_json_and_user( | 204 | job1 = TestJob.from_json_and_user( |
227 | 192 | json.dumps({'device_type':'panda', 'device_tags':['tag']}), | 205 | self.factory.make_job_json(device_tags=['tag']), |
228 | 193 | self.factory.make_user()) | 206 | self.factory.make_user()) |
229 | 194 | job2 = TestJob.from_json_and_user( | 207 | job2 = TestJob.from_json_and_user( |
231 | 195 | json.dumps({'device_type':'panda', 'device_tags':['tag']}), | 208 | self.factory.make_job_json(device_tags=['tag']), |
232 | 196 | self.factory.make_user()) | 209 | self.factory.make_user()) |
233 | 197 | self.assertEqual( | 210 | self.assertEqual( |
234 | 198 | set(tag.pk for tag in job1.tags.all()), | 211 | set(tag.pk for tag in job1.tags.all()), |
235 | 199 | set(tag.pk for tag in job2.tags.all())) | 212 | set(tag.pk for tag in job2.tags.all())) |
236 | 200 | 213 | ||
237 | 214 | def test_from_json_and_user_rejects_invalid_json(self): | ||
238 | 215 | self.assertRaises( | ||
239 | 216 | ValueError, TestJob.from_json_and_user, '{', | ||
240 | 217 | self.factory.make_user()) | ||
241 | 218 | |||
242 | 219 | def test_from_json_and_user_rejects_invalid_job(self): | ||
243 | 220 | # job data must have the 'actions' and 'timeout' properties, so this | ||
244 | 221 | # will be rejected. | ||
245 | 222 | self.assertRaises( | ||
246 | 223 | ValueError, TestJob.from_json_and_user, '{}', | ||
247 | 224 | self.factory.make_user()) | ||
248 | 225 | |||
249 | 226 | def make_job_json_for_stream_name(self, stream_name): | ||
250 | 227 | return self.factory.make_job_json( | ||
251 | 228 | actions=[ | ||
252 | 229 | { | ||
253 | 230 | 'command':'submit_results', | ||
254 | 231 | 'parameters': { | ||
255 | 232 | 'server': '...', | ||
256 | 233 | 'stream': stream_name, | ||
257 | 234 | } | ||
258 | 235 | } | ||
259 | 236 | ]) | ||
260 | 237 | |||
261 | 238 | def test_from_json_and_user_sets_group_from_bundlestream(self): | ||
262 | 239 | group = Group.objects.create(name='group') | ||
263 | 240 | user = self.factory.make_user() | ||
264 | 241 | user.groups.add(group) | ||
265 | 242 | b = BundleStream.objects.create( | ||
266 | 243 | group=group, slug='blah', is_public=True) | ||
267 | 244 | b.save() | ||
268 | 245 | j = self.make_job_json_for_stream_name(b.pathname) | ||
269 | 246 | job = TestJob.from_json_and_user(j, user) | ||
270 | 247 | self.assertEqual(group, job.group) | ||
271 | 248 | |||
272 | 249 | def test_from_json_and_user_sets_is_public_from_bundlestream(self): | ||
273 | 250 | group = Group.objects.create(name='group') | ||
274 | 251 | user = self.factory.make_user() | ||
275 | 252 | user.groups.add(group) | ||
276 | 253 | b = BundleStream.objects.create( | ||
277 | 254 | group=group, slug='blah', is_public=False) | ||
278 | 255 | b.save() | ||
279 | 256 | j = self.make_job_json_for_stream_name(b.pathname) | ||
280 | 257 | job = TestJob.from_json_and_user(j, user) | ||
281 | 258 | self.assertEqual(False, job.is_public) | ||
282 | 259 | |||
283 | 260 | def test_from_json_and_user_rejects_missing_bundlestream(self): | ||
284 | 261 | user = self.factory.make_user() | ||
285 | 262 | j = self.make_job_json_for_stream_name('no such stream') | ||
286 | 263 | self.assertRaises(ValueError, TestJob.from_json_and_user, j, user) | ||
287 | 264 | |||
288 | 265 | def test_from_json_and_user_rejects_inaccessible_bundlestream(self): | ||
289 | 266 | stream_user = self.factory.make_user() | ||
290 | 267 | job_user = self.factory.make_user() | ||
291 | 268 | b = BundleStream.objects.create( | ||
292 | 269 | user=stream_user, slug='blah', is_public=True) | ||
293 | 270 | b.save() | ||
294 | 271 | j = self.make_job_json_for_stream_name(b.pathname) | ||
295 | 272 | self.assertRaises(ValueError, TestJob.from_json_and_user, j, job_user) | ||
296 | 273 | |||
297 | 201 | 274 | ||
298 | 202 | class TestSchedulerAPI(TestCaseWithFactory): | 275 | class TestSchedulerAPI(TestCaseWithFactory): |
299 | 203 | 276 | ||
300 | @@ -231,8 +304,7 @@ | |||
301 | 231 | Permission.objects.get(codename='add_testjob')) | 304 | Permission.objects.get(codename='add_testjob')) |
302 | 232 | user.save() | 305 | user.save() |
303 | 233 | server = self.server_proxy('test', 'test') | 306 | server = self.server_proxy('test', 'test') |
306 | 234 | self.factory.ensure_device_type(name='panda') | 307 | definition = self.factory.make_job_json() |
305 | 235 | definition = json.dumps({'device_type':'panda'}) | ||
307 | 236 | job_id = server.scheduler.submit_job(definition) | 308 | job_id = server.scheduler.submit_job(definition) |
308 | 237 | job = TestJob.objects.get(id=job_id) | 309 | job = TestJob.objects.get(id=job_id) |
309 | 238 | self.assertEqual(definition, job.definition) | 310 | self.assertEqual(definition, job.definition) |
310 | @@ -296,14 +368,19 @@ | |||
311 | 296 | 368 | ||
312 | 297 | def test_getJobForBoard_returns_json(self): | 369 | def test_getJobForBoard_returns_json(self): |
313 | 298 | device = self.factory.make_device(hostname='panda01') | 370 | device = self.factory.make_device(hostname='panda01') |
315 | 299 | definition = {'foo': 'bar', 'target': 'panda01'} | 371 | definition = self.factory.make_job_data(target='panda01') |
316 | 300 | self.factory.make_testjob( | 372 | self.factory.make_testjob( |
317 | 301 | requested_device=device, definition=json.dumps(definition)) | 373 | requested_device=device, definition=json.dumps(definition)) |
318 | 302 | self.assertEqual( | 374 | self.assertEqual( |
319 | 303 | definition, self.source.getJobForBoard('panda01')) | 375 | definition, self.source.getJobForBoard('panda01')) |
320 | 304 | 376 | ||
323 | 305 | health_job = json.dumps({'health_check': True}) | 377 | @property |
324 | 306 | ordinary_job = json.dumps({'health_check': False}) | 378 | def health_job(self): |
325 | 379 | return self.factory.make_job_json(health_check=True) | ||
326 | 380 | |||
327 | 381 | @property | ||
328 | 382 | def ordinary_job(self): | ||
329 | 383 | return self.factory.make_job_json(health_check=False) | ||
330 | 307 | 384 | ||
331 | 308 | def assertHealthJobAssigned(self, device): | 385 | def assertHealthJobAssigned(self, device): |
332 | 309 | job_data = self.source.getJobForBoard(device.hostname) | 386 | job_data = self.source.getJobForBoard(device.hostname) |
333 | @@ -371,7 +448,7 @@ | |||
334 | 371 | def test_getJobForBoard_considers_device_type(self): | 448 | def test_getJobForBoard_considers_device_type(self): |
335 | 372 | panda_type = self.factory.ensure_device_type(name='panda') | 449 | panda_type = self.factory.ensure_device_type(name='panda') |
336 | 373 | self.factory.make_device(hostname='panda01', device_type=panda_type) | 450 | self.factory.make_device(hostname='panda01', device_type=panda_type) |
338 | 374 | definition = {'foo': 'bar'} | 451 | definition = self.factory.make_job_data() |
339 | 375 | self.factory.make_testjob( | 452 | self.factory.make_testjob( |
340 | 376 | requested_device_type=panda_type, | 453 | requested_device_type=panda_type, |
341 | 377 | definition=json.dumps(definition)) | 454 | definition=json.dumps(definition)) |
342 | @@ -383,8 +460,8 @@ | |||
343 | 383 | panda_type = self.factory.ensure_device_type(name='panda') | 460 | panda_type = self.factory.ensure_device_type(name='panda') |
344 | 384 | panda01 = self.factory.make_device( | 461 | panda01 = self.factory.make_device( |
345 | 385 | hostname='panda01', device_type=panda_type) | 462 | hostname='panda01', device_type=panda_type) |
348 | 386 | first_definition = {'foo': 'bar', 'target': 'panda01'} | 463 | first_definition = self.factory.make_job_data(foo='bar', target='panda01') |
349 | 387 | second_definition = {'foo': 'baz', 'target': 'panda01'} | 464 | second_definition = self.factory.make_job_data(foo='baz', target='panda01') |
350 | 388 | self.factory.make_testjob( | 465 | self.factory.make_testjob( |
351 | 389 | requested_device=panda01, definition=json.dumps(first_definition), | 466 | requested_device=panda01, definition=json.dumps(first_definition), |
352 | 390 | submit_time=datetime.datetime.now() - datetime.timedelta(days=1)) | 467 | submit_time=datetime.datetime.now() - datetime.timedelta(days=1)) |
353 | @@ -399,12 +476,13 @@ | |||
354 | 399 | panda_type = self.factory.ensure_device_type(name='panda') | 476 | panda_type = self.factory.ensure_device_type(name='panda') |
355 | 400 | panda01 = self.factory.make_device( | 477 | panda01 = self.factory.make_device( |
356 | 401 | hostname='panda01', device_type=panda_type) | 478 | hostname='panda01', device_type=panda_type) |
358 | 402 | type_definition = {'foo': 'bar'} | 479 | type_definition = self.factory.make_job_data() |
359 | 403 | self.factory.make_testjob( | 480 | self.factory.make_testjob( |
360 | 404 | requested_device_type=panda_type, | 481 | requested_device_type=panda_type, |
361 | 405 | definition=json.dumps(type_definition), | 482 | definition=json.dumps(type_definition), |
362 | 406 | submit_time=datetime.datetime.now() - datetime.timedelta(days=1)) | 483 | submit_time=datetime.datetime.now() - datetime.timedelta(days=1)) |
364 | 407 | device_definition = {'foo': 'baz', 'target': 'panda01'} | 484 | device_definition = self.factory.make_job_data( |
365 | 485 | foo='baz', target='panda01') | ||
366 | 408 | self.factory.make_testjob( | 486 | self.factory.make_testjob( |
367 | 409 | requested_device=panda01, | 487 | requested_device=panda01, |
368 | 410 | definition=json.dumps(device_definition)) | 488 | definition=json.dumps(device_definition)) |
369 | @@ -417,7 +495,7 @@ | |||
370 | 417 | panda01 = self.factory.make_device( | 495 | panda01 = self.factory.make_device( |
371 | 418 | hostname='panda01', device_type=panda_type) | 496 | hostname='panda01', device_type=panda_type) |
372 | 419 | self.factory.make_device(hostname='panda02', device_type=panda_type) | 497 | self.factory.make_device(hostname='panda02', device_type=panda_type) |
374 | 420 | definition = {'foo': 'bar', 'target': 'panda01'} | 498 | definition = self.factory.make_job_data(foo='bar', target='panda01') |
375 | 421 | self.factory.make_testjob( | 499 | self.factory.make_testjob( |
376 | 422 | requested_device=panda01, | 500 | requested_device=panda01, |
377 | 423 | definition=json.dumps(definition)) | 501 | definition=json.dumps(definition)) |
378 | @@ -515,7 +593,7 @@ | |||
379 | 515 | def test_getJobForBoard_inserts_target_into_json(self): | 593 | def test_getJobForBoard_inserts_target_into_json(self): |
380 | 516 | panda_type = self.factory.ensure_device_type(name='panda') | 594 | panda_type = self.factory.ensure_device_type(name='panda') |
381 | 517 | self.factory.make_device(hostname='panda01', device_type=panda_type) | 595 | self.factory.make_device(hostname='panda01', device_type=panda_type) |
383 | 518 | definition = {'foo': 'bar'} | 596 | definition = self.factory.make_job_data(device_type='panda') |
384 | 519 | self.factory.make_testjob( | 597 | self.factory.make_testjob( |
385 | 520 | requested_device_type=panda_type, | 598 | requested_device_type=panda_type, |
386 | 521 | definition=json.dumps(definition)) | 599 | definition=json.dumps(definition)) |
387 | 522 | 600 | ||
388 | === modified file 'lava_scheduler_daemon/dbjobsource.py' | |||
389 | --- lava_scheduler_daemon/dbjobsource.py 2012-03-07 01:59:51 +0000 | |||
390 | +++ lava_scheduler_daemon/dbjobsource.py 2012-03-09 04:16:33 +0000 | |||
391 | @@ -88,25 +88,11 @@ | |||
392 | 88 | def _get_json_data(self, job): | 88 | def _get_json_data(self, job): |
393 | 89 | json_data = json.loads(job.definition) | 89 | json_data = json.loads(job.definition) |
394 | 90 | json_data['target'] = job.actual_device.hostname | 90 | json_data['target'] = job.actual_device.hostname |
411 | 91 | # The rather extreme paranoia in what follows could be much reduced if | 91 | for action in json_data['actions']: |
412 | 92 | # we thoroughly validated job data in submit_job. We don't (yet?) | 92 | if not action['command'].startswith('submit_results'): |
413 | 93 | # and there is no sane way to report errors at this stage, so, | 93 | continue |
414 | 94 | # paranoia (the dispatcher will choke on bogus input in a more | 94 | params = action['parameters'] |
399 | 95 | # informative way). | ||
400 | 96 | if 'actions' not in json_data: | ||
401 | 97 | return json_data | ||
402 | 98 | actions = json_data['actions'] | ||
403 | 99 | for action in actions: | ||
404 | 100 | if not isinstance(action, dict): | ||
405 | 101 | continue | ||
406 | 102 | if action.get('command') != 'submit_results': | ||
407 | 103 | continue | ||
408 | 104 | params = action.get('parameters') | ||
409 | 105 | if not isinstance(params, dict): | ||
410 | 106 | continue | ||
415 | 107 | params['token'] = job.submit_token.secret | 95 | params['token'] = job.submit_token.secret |
416 | 108 | if not 'server' in params or not isinstance(params['server'], unicode): | ||
417 | 109 | continue | ||
418 | 110 | parsed = urlparse.urlsplit(params['server']) | 96 | parsed = urlparse.urlsplit(params['server']) |
419 | 111 | netloc = job.submitter.username + '@' + parsed.hostname | 97 | netloc = job.submitter.username + '@' + parsed.hostname |
420 | 112 | parsed = list(parsed) | 98 | parsed = list(parsed) |
421 | 113 | 99 | ||
422 | === modified file 'setup.py' | |||
423 | --- setup.py 2012-03-07 03:49:20 +0000 | |||
424 | +++ setup.py 2012-03-09 04:16:33 +0000 | |||
425 | @@ -35,6 +35,8 @@ | |||
426 | 35 | install_requires=[ | 35 | install_requires=[ |
427 | 36 | "django-restricted-resource", | 36 | "django-restricted-resource", |
428 | 37 | "django-tables2 >= 0.9.4", | 37 | "django-tables2 >= 0.9.4", |
429 | 38 | "lava-dashboard", | ||
430 | 39 | "lava-dispatcher", | ||
431 | 38 | "lava-server >= 0.10", | 40 | "lava-server >= 0.10", |
432 | 39 | "simplejson", | 41 | "simplejson", |
433 | 40 | "south >= 0.7.3", | 42 | "south >= 0.7.3", |
I think you forgot the prerequisite branch - this makes reading it harder.
60 + try: objects. get(pathname= stream) DoesNotExist:
61 + bundle_stream = BundleStream.
62 + except BundleStream.
63 + raise ValueError("stream %s not found" % stream)
429 + "lava-dashboard",
So it's official (netcraft confirms it). The scheduler depends on the dashboard now. Can we please look at how to merge them? This is a bigger topic. I'd like to get rid of lots of things from the dashboard proper. Perhaps we should move the base models to lava-server (tree-wise) application. lava.silo or lava.testdata or something like that.