blob: eaeeea00973d40bb75db9041e607a2c07e98e460 [file] [log] [blame]
showardf828c772010-01-25 21:49:42 +00001from django import http
2from autotest_lib.frontend.shared import query_lib, resource_lib
3from autotest_lib.frontend.afe import control_file, models, rpc_utils
jamesrendd855242010-03-02 22:23:44 +00004from autotest_lib.frontend.afe import model_attributes
showardf828c772010-01-25 21:49:42 +00005from autotest_lib.frontend import thread_local
6from autotest_lib.client.common_lib import host_protections
7
jamesren3981f442010-02-16 19:27:59 +00008class EntryWithInvalid(resource_lib.InstanceEntry):
showardf46ad4c2010-02-03 20:28:59 +00009 def put(self):
showardf828c772010-01-25 21:49:42 +000010 if self.instance.invalid:
showardf46ad4c2010-02-03 20:28:59 +000011 raise http.Http404('%s has been deleted' % self.instance)
12 return super(EntryWithInvalid, self).put()
showardf828c772010-01-25 21:49:42 +000013
14
showardf46ad4c2010-02-03 20:28:59 +000015 def delete(self):
showardf828c772010-01-25 21:49:42 +000016 if self.instance.invalid:
showardf46ad4c2010-02-03 20:28:59 +000017 raise http.Http404('%s has already been deleted' % self.instance)
18 return super(EntryWithInvalid, self).delete()
showardf828c772010-01-25 21:49:42 +000019
20
21class AtomicGroupClass(EntryWithInvalid):
jamesren3981f442010-02-16 19:27:59 +000022 model = models.AtomicGroup
23
24
showardf828c772010-01-25 21:49:42 +000025 @classmethod
jamesren3981f442010-02-16 19:27:59 +000026 def from_uri_args(cls, request, ag_name, **kwargs):
27 return cls(request, models.AtomicGroup.objects.get(name=ag_name))
showardf828c772010-01-25 21:49:42 +000028
29
30 def _uri_args(self):
jamesren3981f442010-02-16 19:27:59 +000031 return {'ag_name': self.instance.name}
showardf828c772010-01-25 21:49:42 +000032
33
34 def short_representation(self):
35 rep = super(AtomicGroupClass, self).short_representation()
36 rep['name'] = self.instance.name
37 return rep
38
39
40 def full_representation(self):
41 rep = super(AtomicGroupClass, self).full_representation()
42 rep.update({'max_number_of_machines':
43 self.instance.max_number_of_machines,
jamesren3981f442010-02-16 19:27:59 +000044 'labels':
45 AtomicLabelTaggingCollection(fixed_entry=self).link()})
showardf828c772010-01-25 21:49:42 +000046 return rep
47
48
49 @classmethod
50 def create_instance(cls, input_dict, containing_collection):
51 cls._check_for_required_fields(input_dict, ('name',))
52 return models.AtomicGroup.add_object(name=input_dict['name'])
53
54
55 def update(self, input_dict):
56 data = {'max_number_of_machines':
57 input_dict.get('max_number_of_machines')}
58 data = input_dict.remove_unspecified_fields(data)
59 self.instance.update_object(**data)
60
61
62class AtomicGroupClassCollection(resource_lib.Collection):
63 queryset = models.AtomicGroup.valid_objects.all()
64 entry_class = AtomicGroupClass
65
66
showardf828c772010-01-25 21:49:42 +000067class Label(EntryWithInvalid):
jamesren3981f442010-02-16 19:27:59 +000068 model = models.Label
69
70 @classmethod
71 def add_query_selectors(cls, query_processor):
72 query_processor.add_field_selector('name')
73 query_processor.add_field_selector(
74 'is_platform', field='platform',
75 value_transform=query_processor.read_boolean)
showardf828c772010-01-25 21:49:42 +000076
77
78 @classmethod
jamesren3981f442010-02-16 19:27:59 +000079 def from_uri_args(cls, request, label_name, **kwargs):
80 return cls(request, models.Label.objects.get(name=label_name))
showardf828c772010-01-25 21:49:42 +000081
82
83 def _uri_args(self):
jamesren3981f442010-02-16 19:27:59 +000084 return {'label_name': self.instance.name}
showardf828c772010-01-25 21:49:42 +000085
86
87 def short_representation(self):
88 rep = super(Label, self).short_representation()
89 rep.update({'name': self.instance.name,
90 'is_platform': bool(self.instance.platform)})
91 return rep
92
93
94 def full_representation(self):
95 rep = super(Label, self).full_representation()
showardf46ad4c2010-02-03 20:28:59 +000096 atomic_group_class = AtomicGroupClass.from_optional_instance(
97 self._request, self.instance.atomic_group)
98 rep.update({'atomic_group_class':
99 atomic_group_class.short_representation(),
jamesren3981f442010-02-16 19:27:59 +0000100 'hosts': HostLabelingCollection(fixed_entry=self).link()})
showardf828c772010-01-25 21:49:42 +0000101 return rep
102
103
104 @classmethod
105 def create_instance(cls, input_dict, containing_collection):
106 cls._check_for_required_fields(input_dict, ('name',))
107 return models.Label.add_object(name=input_dict['name'])
108
109
110 def update(self, input_dict):
111 # TODO update atomic group
jamesrencd7a81a2010-04-21 20:39:08 +0000112 if 'is_platform' in input_dict:
113 self.instance.platform = input_dict['is_platform']
114 self.instance.save()
showardf828c772010-01-25 21:49:42 +0000115
116
117class LabelCollection(resource_lib.Collection):
118 queryset = models.Label.valid_objects.all()
119 entry_class = Label
120
121
jamesren3981f442010-02-16 19:27:59 +0000122class AtomicLabelTagging(resource_lib.Relationship):
123 related_classes = {'label': Label, 'atomic_group_class': AtomicGroupClass}
showardf828c772010-01-25 21:49:42 +0000124
125
jamesren3981f442010-02-16 19:27:59 +0000126class AtomicLabelTaggingCollection(resource_lib.RelationshipCollection):
127 entry_class = AtomicLabelTagging
showardf828c772010-01-25 21:49:42 +0000128
129
jamesren3981f442010-02-16 19:27:59 +0000130class User(resource_lib.InstanceEntry):
131 model = models.User
showardf828c772010-01-25 21:49:42 +0000132 _permitted_methods = ('GET,')
133
134
135 @classmethod
jamesren3981f442010-02-16 19:27:59 +0000136 def from_uri_args(cls, request, username, **kwargs):
showardf828c772010-01-25 21:49:42 +0000137 if username == '@me':
showardf46ad4c2010-02-03 20:28:59 +0000138 username = models.User.current_user().login
139 return cls(request, models.User.objects.get(login=username))
showardf828c772010-01-25 21:49:42 +0000140
141
142 def _uri_args(self):
jamesren3981f442010-02-16 19:27:59 +0000143 return {'username': self.instance.login}
showardf828c772010-01-25 21:49:42 +0000144
145
146 def short_representation(self):
147 rep = super(User, self).short_representation()
148 rep['username'] = self.instance.login
149 return rep
150
151
152 def full_representation(self):
153 rep = super(User, self).full_representation()
jamesren3981f442010-02-16 19:27:59 +0000154 accessible_hosts = HostCollection(self._request)
155 accessible_hosts.set_query_parameters(accessible_by=self.instance.login)
showardf828c772010-01-25 21:49:42 +0000156 rep.update({'jobs': 'TODO',
157 'recurring_runs': 'TODO',
jamesren3981f442010-02-16 19:27:59 +0000158 'acls':
159 UserAclMembershipCollection(fixed_entry=self).link(),
160 'accessible_hosts': accessible_hosts.link()})
showardf828c772010-01-25 21:49:42 +0000161 return rep
162
163
164class UserCollection(resource_lib.Collection):
165 _permitted_methods = ('GET',)
166 queryset = models.User.objects.all()
167 entry_class = User
168
169
jamesren3981f442010-02-16 19:27:59 +0000170class Acl(resource_lib.InstanceEntry):
showardf828c772010-01-25 21:49:42 +0000171 _permitted_methods = ('GET',)
jamesren3981f442010-02-16 19:27:59 +0000172 model = models.AclGroup
showardf828c772010-01-25 21:49:42 +0000173
174 @classmethod
jamesren3981f442010-02-16 19:27:59 +0000175 def from_uri_args(cls, request, acl_name, **kwargs):
176 return cls(request, models.AclGroup.objects.get(name=acl_name))
showardf828c772010-01-25 21:49:42 +0000177
178
179 def _uri_args(self):
jamesren3981f442010-02-16 19:27:59 +0000180 return {'acl_name': self.instance.name}
showardf828c772010-01-25 21:49:42 +0000181
182
183 def short_representation(self):
184 rep = super(Acl, self).short_representation()
185 rep['name'] = self.instance.name
186 return rep
187
188
189 def full_representation(self):
190 rep = super(Acl, self).full_representation()
jamesren3981f442010-02-16 19:27:59 +0000191 rep.update({'users':
192 UserAclMembershipCollection(fixed_entry=self).link(),
193 'hosts':
194 HostAclMembershipCollection(fixed_entry=self).link()})
showardf828c772010-01-25 21:49:42 +0000195 return rep
196
197
198 @classmethod
199 def create_instance(cls, input_dict, containing_collection):
200 cls._check_for_required_fields(input_dict, ('name',))
201 return models.AclGroup.add_object(name=input_dict['name'])
202
203
204 def update(self, input_dict):
205 pass
206
207
208class AclCollection(resource_lib.Collection):
209 queryset = models.AclGroup.objects.all()
210 entry_class = Acl
211
212
jamesren3981f442010-02-16 19:27:59 +0000213class UserAclMembership(resource_lib.Relationship):
214 related_classes = {'user': User, 'acl': Acl}
215
216 # TODO: check permissions
217 # TODO: check for and add/remove "Everyone"
showardf828c772010-01-25 21:49:42 +0000218
219
jamesren3981f442010-02-16 19:27:59 +0000220class UserAclMembershipCollection(resource_lib.RelationshipCollection):
221 entry_class = UserAclMembership
showardf828c772010-01-25 21:49:42 +0000222
223
224class Host(EntryWithInvalid):
jamesren3981f442010-02-16 19:27:59 +0000225 model = models.Host
226
227 @classmethod
228 def add_query_selectors(cls, query_processor):
229 query_processor.add_field_selector('hostname')
230 query_processor.add_field_selector(
231 'locked', value_transform=query_processor.read_boolean)
232 query_processor.add_field_selector(
showardf828c772010-01-25 21:49:42 +0000233 'locked_by', field='locked_by__login',
234 doc='Username of user who locked this host, if locked')
jamesren3981f442010-02-16 19:27:59 +0000235 query_processor.add_field_selector('status')
236 query_processor.add_field_selector(
237 'protection_level', field='protection',
238 doc='Verify/repair protection level',
239 value_transform=cls._read_protection)
240 query_processor.add_field_selector(
241 'accessible_by', field='aclgroup__users__login',
242 doc='Username of user with access to this host')
243 query_processor.add_related_existence_selector(
244 'has_label', models.Label, 'name')
showardf828c772010-01-25 21:49:42 +0000245
246
247 @classmethod
248 def _read_protection(cls, protection_input):
249 return host_protections.Protection.get_value(protection_input)
250
251
252 @classmethod
jamesren3981f442010-02-16 19:27:59 +0000253 def from_uri_args(cls, request, hostname, **kwargs):
showardf46ad4c2010-02-03 20:28:59 +0000254 return cls(request, models.Host.objects.get(hostname=hostname))
showardf828c772010-01-25 21:49:42 +0000255
256
257 def _uri_args(self):
jamesren3981f442010-02-16 19:27:59 +0000258 return {'hostname': self.instance.hostname}
showardf828c772010-01-25 21:49:42 +0000259
260
261 def short_representation(self):
262 rep = super(Host, self).short_representation()
263 # TODO calling platform() over and over is inefficient
showardf46ad4c2010-02-03 20:28:59 +0000264 platform_rep = (Label.from_optional_instance(self._request,
265 self.instance.platform())
showardf828c772010-01-25 21:49:42 +0000266 .short_representation())
267 rep.update({'hostname': self.instance.hostname,
268 'locked': bool(self.instance.locked),
269 'status': self.instance.status,
270 'platform': platform_rep})
271 return rep
272
273
274 def full_representation(self):
275 rep = super(Host, self).full_representation()
276 protection = host_protections.Protection.get_string(
277 self.instance.protection)
showardf46ad4c2010-02-03 20:28:59 +0000278 locked_by = (User.from_optional_instance(self._request,
279 self.instance.locked_by)
showardf828c772010-01-25 21:49:42 +0000280 .short_representation())
jamesren3981f442010-02-16 19:27:59 +0000281 labels = HostLabelingCollection(fixed_entry=self)
282 acls = HostAclMembershipCollection(fixed_entry=self)
283 queue_entries = QueueEntryCollection(self._request)
284 queue_entries.set_query_parameters(host=self.instance.hostname)
285 health_tasks = HealthTaskCollection(self._request)
286 health_tasks.set_query_parameters(host=self.instance.hostname)
287
showardf828c772010-01-25 21:49:42 +0000288 rep.update({'locked_by': locked_by,
289 'locked_on': self._format_datetime(self.instance.lock_time),
290 'invalid': self.instance.invalid,
291 'protection_level': protection,
292 # TODO make these efficient
jamesren3981f442010-02-16 19:27:59 +0000293 'labels': labels.full_representation(),
294 'acls': acls.full_representation(),
295 'queue_entries': queue_entries.link(),
296 'health_tasks': health_tasks.link()})
showardf828c772010-01-25 21:49:42 +0000297 return rep
298
299
300 @classmethod
301 def create_instance(cls, input_dict, containing_collection):
302 cls._check_for_required_fields(input_dict, ('hostname',))
jamesren3981f442010-02-16 19:27:59 +0000303 # include locked here, rather than waiting for update(), to avoid race
304 # conditions
305 host = models.Host.add_object(hostname=input_dict['hostname'],
306 locked=input_dict.get('locked', False))
307 return host
showardf828c772010-01-25 21:49:42 +0000308
309 def update(self, input_dict):
310 data = {'locked': input_dict.get('locked'),
311 'protection': input_dict.get('protection_level')}
312 data = input_dict.remove_unspecified_fields(data)
313
314 if 'protection' in data:
315 data['protection'] = self._read_protection(data['protection'])
316
317 self.instance.update_object(**data)
318
319 if 'platform' in input_dict:
showardf46ad4c2010-02-03 20:28:59 +0000320 label = self.resolve_link(input_dict['platform']) .instance
showardf828c772010-01-25 21:49:42 +0000321 if not label.platform:
322 raise BadRequest('Label %s is not a platform' % label.name)
323 for label in self.instance.labels.filter(platform=True):
324 self.instance.labels.remove(label)
325 self.instance.labels.add(label)
326
327
328class HostCollection(resource_lib.Collection):
329 queryset = models.Host.valid_objects.all()
330 entry_class = Host
331
332
jamesren3981f442010-02-16 19:27:59 +0000333class HostLabeling(resource_lib.Relationship):
334 related_classes = {'host': Host, 'label': Label}
showardf828c772010-01-25 21:49:42 +0000335
336
jamesren3981f442010-02-16 19:27:59 +0000337class HostLabelingCollection(resource_lib.RelationshipCollection):
338 entry_class = HostLabeling
showardf828c772010-01-25 21:49:42 +0000339
340
jamesren3981f442010-02-16 19:27:59 +0000341class HostAclMembership(resource_lib.Relationship):
342 related_classes = {'host': Host, 'acl': Acl}
343
344 # TODO: acl.check_for_acl_violation_acl_group()
345 # TODO: models.AclGroup.on_host_membership_change()
showardf828c772010-01-25 21:49:42 +0000346
347
jamesren3981f442010-02-16 19:27:59 +0000348class HostAclMembershipCollection(resource_lib.RelationshipCollection):
349 entry_class = HostAclMembership
showardf828c772010-01-25 21:49:42 +0000350
351
jamesren3981f442010-02-16 19:27:59 +0000352class Test(resource_lib.InstanceEntry):
353 model = models.Test
showardf828c772010-01-25 21:49:42 +0000354
355
showardf828c772010-01-25 21:49:42 +0000356 @classmethod
jamesrencd7a81a2010-04-21 20:39:08 +0000357 def add_query_selectors(cls, query_processor):
358 query_processor.add_field_selector('name')
359
360
361 @classmethod
jamesren3981f442010-02-16 19:27:59 +0000362 def from_uri_args(cls, request, test_name, **kwargs):
363 return cls(request, models.Test.objects.get(name=test_name))
showardf828c772010-01-25 21:49:42 +0000364
365
366 def _uri_args(self):
jamesren3981f442010-02-16 19:27:59 +0000367 return {'test_name': self.instance.name}
showardf828c772010-01-25 21:49:42 +0000368
369
370 def short_representation(self):
371 rep = super(Test, self).short_representation()
372 rep['name'] = self.instance.name
373 return rep
374
375
376 def full_representation(self):
377 rep = super(Test, self).full_representation()
378 rep.update({'author': self.instance.author,
379 'class': self.instance.test_class,
380 'control_file_type':
jamesrendd855242010-03-02 22:23:44 +0000381 model_attributes.TestTypes.get_string(
382 self.instance.test_type),
showardf828c772010-01-25 21:49:42 +0000383 'control_file_path': self.instance.path,
jamesrencd7a81a2010-04-21 20:39:08 +0000384 'sync_count': self.instance.sync_count,
jamesren3981f442010-02-16 19:27:59 +0000385 'dependencies':
386 TestDependencyCollection(fixed_entry=self).link(),
showardf828c772010-01-25 21:49:42 +0000387 })
388 return rep
389
390
391 @classmethod
392 def create_instance(cls, input_dict, containing_collection):
393 cls._check_for_required_fields(input_dict,
394 ('name', 'control_file_type',
395 'control_file_path'))
jamesrendd855242010-03-02 22:23:44 +0000396 test_type = model_attributes.TestTypes.get_value(
397 input['control_file_type'])
showardf828c772010-01-25 21:49:42 +0000398 return models.Test.add_object(name=input_dict['name'],
399 test_type=test_type,
400 path=input_dict['control_file_path'])
401
402
403 def update(self, input_dict):
404 data = {'test_type': input_dict.get('control_file_type'),
405 'path': input_dict.get('control_file_path'),
406 'class': input_dict.get('class'),
407 }
408 data = input_dict.remove_unspecified_fields(data)
409 self.instance.update_object(**data)
410
411
412class TestCollection(resource_lib.Collection):
413 queryset = models.Test.objects.all()
414 entry_class = Test
415
416
jamesren3981f442010-02-16 19:27:59 +0000417class TestDependency(resource_lib.Relationship):
418 related_classes = {'test': Test, 'label': Label}
showardf828c772010-01-25 21:49:42 +0000419
420
jamesren3981f442010-02-16 19:27:59 +0000421class TestDependencyCollection(resource_lib.RelationshipCollection):
422 entry_class = TestDependency
showardf828c772010-01-25 21:49:42 +0000423
424
425# TODO profilers
426
427
428class ExecutionInfo(resource_lib.Resource):
429 _permitted_methods = ('GET','POST')
430 _job_fields = models.Job.get_field_dict()
431 _DEFAULTS = {
432 'control_file': '',
433 'is_server': True,
434 'dependencies': [],
435 'machines_per_execution': 1,
436 'run_verify': bool(_job_fields['run_verify'].default),
437 'timeout_hrs': _job_fields['timeout'].default,
438 'maximum_runtime_hrs': _job_fields['max_runtime_hrs'].default,
439 'cleanup_before_job':
jamesrendd855242010-03-02 22:23:44 +0000440 model_attributes.RebootBefore.get_string(
441 models.DEFAULT_REBOOT_BEFORE),
showardf828c772010-01-25 21:49:42 +0000442 'cleanup_after_job':
jamesrendd855242010-03-02 22:23:44 +0000443 model_attributes.RebootAfter.get_string(
444 models.DEFAULT_REBOOT_AFTER),
showardf828c772010-01-25 21:49:42 +0000445 }
446
447
jamesren3981f442010-02-16 19:27:59 +0000448 def _query_parameters_accepted(self):
showardf828c772010-01-25 21:49:42 +0000449 return (('tests', 'Comma-separated list of test names to run'),
450 ('kernels', 'TODO'),
451 ('client_control_file',
452 'Client control file segment to run after all specified '
453 'tests'),
454 ('profilers',
455 'Comma-separated list of profilers to activate during the '
456 'job'),
457 ('use_container', 'TODO'),
458 ('profile_only',
459 'If true, run only profiled iterations; otherwise, always run '
460 'at least one non-profiled iteration in addition to a '
461 'profiled iteration'),
462 ('upload_kernel_config',
463 'If true, generate a server control file code that uploads '
464 'the kernel config file to the client and tells the client of '
465 'the new (local) path when compiling the kernel; the tests '
466 'must be server side tests'))
467
468
469 @classmethod
470 def execution_info_from_job(cls, job):
471 return {'control_file': job.control_file,
472 'is_server': job.control_type == models.Job.ControlType.SERVER,
473 'dependencies': [label.name for label
474 in job.dependency_labels.all()],
475 'machines_per_execution': job.synch_count,
476 'run_verify': bool(job.run_verify),
477 'timeout_hrs': job.timeout,
478 'maximum_runtime_hrs': job.max_runtime_hrs,
479 'cleanup_before_job':
jamesrendd855242010-03-02 22:23:44 +0000480 model_attributes.RebootBefore.get_string(job.reboot_before),
showardf828c772010-01-25 21:49:42 +0000481 'cleanup_after_job':
jamesrendd855242010-03-02 22:23:44 +0000482 model_attributes.RebootAfter.get_string(job.reboot_after),
showardf828c772010-01-25 21:49:42 +0000483 }
484
485
486 def _get_execution_info(self, input_dict):
487 tests = input_dict.get('tests', '')
488 client_control_file = input_dict.get('client_control_file', None)
489 if not tests and not client_control_file:
490 return self._DEFAULTS
491
492 test_list = tests.split(',')
493 if 'profilers' in input_dict:
494 profilers_list = input_dict['profilers'].split(',')
495 else:
496 profilers_list = []
497 kernels = input_dict.get('kernels', '') # TODO
498 if kernels:
499 kernels = [dict(version=kernel) for kernel in kernels.split(',')]
500
501 cf_info, test_objects, profiler_objects, label = (
502 rpc_utils.prepare_generate_control_file(
503 test_list, kernels, None, profilers_list))
504 control_file_contents = control_file.generate_control(
505 tests=test_objects, kernels=kernels,
506 profilers=profiler_objects, is_server=cf_info['is_server'],
507 client_control_file=client_control_file,
508 profile_only=input_dict.get('profile_only', None),
509 upload_kernel_config=input_dict.get(
510 'upload_kernel_config', None))
511 return dict(self._DEFAULTS,
512 control_file=control_file_contents,
513 is_server=cf_info['is_server'],
514 dependencies=cf_info['dependencies'],
515 machines_per_execution=cf_info['synch_count'])
516
517
showardf46ad4c2010-02-03 20:28:59 +0000518 def handle_request(self):
showardf828c772010-01-25 21:49:42 +0000519 result = self.link()
showardf46ad4c2010-02-03 20:28:59 +0000520 result['execution_info'] = self._get_execution_info(
521 self._request.REQUEST)
showardf828c772010-01-25 21:49:42 +0000522 return self._basic_response(result)
523
524
525class QueueEntriesRequest(resource_lib.Resource):
526 _permitted_methods = ('GET',)
527
528
jamesren3981f442010-02-16 19:27:59 +0000529 def _query_parameters_accepted(self):
showardf828c772010-01-25 21:49:42 +0000530 return (('hosts', 'Comma-separated list of hostnames'),
531 ('one_time_hosts',
532 'Comma-separated list of hostnames not already in the '
533 'Autotest system'),
534 ('meta_hosts',
535 'Comma-separated list of label names; for each one, an entry '
536 'will be created and assigned at runtime to an available host '
537 'with that label'),
538 ('atomic_group_class', 'TODO'))
539
540
541 def _read_list(self, list_string):
542 if list_string:
543 return list_string.split(',')
544 return []
545
546
showardf46ad4c2010-02-03 20:28:59 +0000547 def handle_request(self):
548 request_dict = self._request.REQUEST
showardf828c772010-01-25 21:49:42 +0000549 hosts = self._read_list(request_dict.get('hosts'))
550 one_time_hosts = self._read_list(request_dict.get('one_time_hosts'))
551 meta_hosts = self._read_list(request_dict.get('meta_hosts'))
552 atomic_group_class = request_dict.get('atomic_group_class')
553
554 # TODO: bring in all the atomic groups magic from create_job()
555
556 entries = []
557 for hostname in one_time_hosts:
558 models.Host.create_one_time_host(hostname)
559 for hostname in hosts:
showardf46ad4c2010-02-03 20:28:59 +0000560 entry = Host.from_uri_args(self._request, hostname)
showardf828c772010-01-25 21:49:42 +0000561 entries.append({'host': entry.link()})
562 for label_name in meta_hosts:
showardf46ad4c2010-02-03 20:28:59 +0000563 entry = Label.from_uri_args(self._request, label_name)
showardf828c772010-01-25 21:49:42 +0000564 entries.append({'meta_host': entry.link()})
jamesren3e9f6092010-03-11 21:32:10 +0000565 if atomic_group_class:
566 entries.append({'atomic_group_class': atomic_group_class})
showardf828c772010-01-25 21:49:42 +0000567
568 result = self.link()
569 result['queue_entries'] = entries
570 return self._basic_response(result)
571
572
jamesren3981f442010-02-16 19:27:59 +0000573class Job(resource_lib.InstanceEntry):
showardf828c772010-01-25 21:49:42 +0000574 _permitted_methods = ('GET',)
jamesren3981f442010-02-16 19:27:59 +0000575 model = models.Job
576
showardf828c772010-01-25 21:49:42 +0000577
578 class _StatusConstraint(query_lib.Constraint):
jamesren3981f442010-02-16 19:27:59 +0000579 def apply_constraint(self, queryset, value, comparison_type,
580 is_inverse):
showardf828c772010-01-25 21:49:42 +0000581 if comparison_type != 'equals' or is_inverse:
582 raise query_lib.ConstraintError('Can only use this selector '
583 'with equals')
584 non_queued_statuses = [
585 status for status, _
586 in models.HostQueueEntry.Status.choices()
587 if status != models.HostQueueEntry.Status.QUEUED]
588 if value == 'queued':
589 return queryset.exclude(
590 hostqueueentry__status__in=non_queued_statuses)
591 elif value == 'active':
592 return queryset.filter(
593 hostqueueentry__status__in=non_queued_statuses).filter(
594 hostqueueentry__complete=False).distinct()
595 elif value == 'complete':
596 return queryset.exclude(hostqueueentry__complete=False)
597 else:
598 raise query_lib.ConstraintError('Value must be one of queued, '
599 'active or complete')
600
601
jamesren3981f442010-02-16 19:27:59 +0000602 @classmethod
603 def add_query_selectors(cls, query_processor):
604 query_processor.add_field_selector('id')
jamesrenc3940222010-02-19 21:57:37 +0000605 query_processor.add_field_selector('name')
jamesren3981f442010-02-16 19:27:59 +0000606 query_processor.add_selector(
607 query_lib.Selector('status',
608 doc='One of queued, active or complete'),
609 Job._StatusConstraint())
jamesrencd7a81a2010-04-21 20:39:08 +0000610 query_processor.add_keyval_selector('has_keyval', models.JobKeyval,
611 'key', 'value')
showardf828c772010-01-25 21:49:42 +0000612
613
614 @classmethod
jamesren3981f442010-02-16 19:27:59 +0000615 def from_uri_args(cls, request, job_id, **kwargs):
showardf46ad4c2010-02-03 20:28:59 +0000616 return cls(request, models.Job.objects.get(id=job_id))
showardf828c772010-01-25 21:49:42 +0000617
618
619 def _uri_args(self):
jamesren3981f442010-02-16 19:27:59 +0000620 return {'job_id': self.instance.id}
showardf828c772010-01-25 21:49:42 +0000621
622
jamesrencd7a81a2010-04-21 20:39:08 +0000623 @classmethod
624 def _do_prepare_for_full_representation(cls, instances):
625 models.Job.objects.populate_relationships(instances, models.JobKeyval,
626 'keyvals')
627
628
showardf828c772010-01-25 21:49:42 +0000629 def short_representation(self):
630 rep = super(Job, self).short_representation()
631 rep.update({'id': self.instance.id,
632 'owner': self.instance.owner,
633 'name': self.instance.name,
634 'priority':
635 models.Job.Priority.get_string(self.instance.priority),
636 'created_on':
637 self._format_datetime(self.instance.created_on),
638 })
639 return rep
640
641
642 def full_representation(self):
643 rep = super(Job, self).full_representation()
jamesren3981f442010-02-16 19:27:59 +0000644 queue_entries = QueueEntryCollection(self._request)
645 queue_entries.set_query_parameters(job=self.instance.id)
jamesren76fcf192010-04-21 20:39:50 +0000646 drone_set = self.instance.drone_set and self.instance.drone_set.name
showardf828c772010-01-25 21:49:42 +0000647 rep.update({'email_list': self.instance.email_list,
648 'parse_failed_repair':
649 bool(self.instance.parse_failed_repair),
jamesren76fcf192010-04-21 20:39:50 +0000650 'drone_set': drone_set,
showardf828c772010-01-25 21:49:42 +0000651 'execution_info':
652 ExecutionInfo.execution_info_from_job(self.instance),
jamesren3981f442010-02-16 19:27:59 +0000653 'queue_entries': queue_entries.link(),
jamesrencd7a81a2010-04-21 20:39:08 +0000654 'keyvals': dict((keyval.key, keyval.value)
655 for keyval in self.instance.keyvals)
showardf828c772010-01-25 21:49:42 +0000656 })
657 return rep
658
659
660 @classmethod
661 def create_instance(cls, input_dict, containing_collection):
jamesrene38a0a72010-04-19 18:05:31 +0000662 owner = input_dict.get('owner')
663 if not owner:
664 owner = models.User.current_user().login
665
showardf828c772010-01-25 21:49:42 +0000666 cls._check_for_required_fields(input_dict, ('name', 'execution_info',
667 'queue_entries'))
668 execution_info = input_dict['execution_info']
669 cls._check_for_required_fields(execution_info, ('control_file',
670 'is_server'))
671
672 if execution_info['is_server']:
673 control_type = models.Job.ControlType.SERVER
674 else:
675 control_type = models.Job.ControlType.CLIENT
676 options = dict(
677 name=input_dict['name'],
678 priority=input_dict.get('priority', None),
679 control_file=execution_info['control_file'],
680 control_type=control_type,
681 is_template=input_dict.get('is_template', None),
682 timeout=execution_info.get('timeout_hrs'),
683 max_runtime_hrs=execution_info.get('maximum_runtime_hrs'),
684 synch_count=execution_info.get('machines_per_execution'),
685 run_verify=execution_info.get('run_verify'),
686 email_list=input_dict.get('email_list', None),
687 dependencies=execution_info.get('dependencies', ()),
688 reboot_before=execution_info.get('cleanup_before_job'),
689 reboot_after=execution_info.get('cleanup_after_job'),
690 parse_failed_repair=input_dict.get('parse_failed_repair', None),
jamesren76fcf192010-04-21 20:39:50 +0000691 drone_set=input_dict.get('drone_set', None),
showardf828c772010-01-25 21:49:42 +0000692 keyvals=input_dict.get('keyvals', None))
693
694 host_objects, metahost_label_objects, atomic_group = [], [], None
695 for queue_entry in input_dict['queue_entries']:
696 if 'host' in queue_entry:
697 host = queue_entry['host']
698 if host: # can be None, indicated a hostless job
showardf46ad4c2010-02-03 20:28:59 +0000699 host_entry = containing_collection.resolve_link(host)
showardf828c772010-01-25 21:49:42 +0000700 host_objects.append(host_entry.instance)
701 elif 'meta_host' in queue_entry:
showardf46ad4c2010-02-03 20:28:59 +0000702 label_entry = containing_collection.resolve_link(
showardf828c772010-01-25 21:49:42 +0000703 queue_entry['meta_host'])
704 metahost_label_objects.append(label_entry.instance)
jamesren3e9f6092010-03-11 21:32:10 +0000705 if 'atomic_group_class' in queue_entry:
showardf46ad4c2010-02-03 20:28:59 +0000706 atomic_group_entry = containing_collection.resolve_link(
jamesren3e9f6092010-03-11 21:32:10 +0000707 queue_entry['atomic_group_class'])
showardf828c772010-01-25 21:49:42 +0000708 if atomic_group:
709 assert atomic_group_entry.instance.id == atomic_group.id
710 else:
711 atomic_group = atomic_group_entry.instance
712
713 job_id = rpc_utils.create_new_job(
jamesrene38a0a72010-04-19 18:05:31 +0000714 owner=owner,
showardf828c772010-01-25 21:49:42 +0000715 options=options,
716 host_objects=host_objects,
717 metahost_objects=metahost_label_objects,
718 atomic_group=atomic_group)
719 return models.Job.objects.get(id=job_id)
720
721
722 def update(self, input_dict):
723 # Required for POST, doesn't actually support PUT
724 pass
725
726
727class JobCollection(resource_lib.Collection):
728 queryset = models.Job.objects.order_by('-id')
729 entry_class = Job
730
731
jamesren3981f442010-02-16 19:27:59 +0000732class QueueEntry(resource_lib.InstanceEntry):
showardf828c772010-01-25 21:49:42 +0000733 _permitted_methods = ('GET', 'PUT')
jamesren3981f442010-02-16 19:27:59 +0000734 model = models.HostQueueEntry
735
showardf828c772010-01-25 21:49:42 +0000736
737 @classmethod
jamesren3981f442010-02-16 19:27:59 +0000738 def add_query_selectors(cls, query_processor):
739 query_processor.add_field_selector('host', field='host__hostname')
740 query_processor.add_field_selector('job', field='job__id')
741
742
743 @classmethod
744 def from_uri_args(cls, request, queue_entry_id):
showardf828c772010-01-25 21:49:42 +0000745 instance = models.HostQueueEntry.objects.get(id=queue_entry_id)
showardf46ad4c2010-02-03 20:28:59 +0000746 return cls(request, instance)
showardf828c772010-01-25 21:49:42 +0000747
748
749 def _uri_args(self):
jamesren3981f442010-02-16 19:27:59 +0000750 return {'queue_entry_id': self.instance.id}
showardf828c772010-01-25 21:49:42 +0000751
752
753 def short_representation(self):
754 rep = super(QueueEntry, self).short_representation()
755 if self.instance.host:
showardf46ad4c2010-02-03 20:28:59 +0000756 host = (Host(self._request, self.instance.host)
757 .short_representation())
showardf828c772010-01-25 21:49:42 +0000758 else:
759 host = None
showardf46ad4c2010-02-03 20:28:59 +0000760 job = Job(self._request, self.instance.job)
761 host = Host.from_optional_instance(self._request, self.instance.host)
762 label = Label.from_optional_instance(self._request,
763 self.instance.meta_host)
764 atomic_group_class = AtomicGroupClass.from_optional_instance(
765 self._request, self.instance.atomic_group)
showardf828c772010-01-25 21:49:42 +0000766 rep.update(
showardf46ad4c2010-02-03 20:28:59 +0000767 {'job': job.short_representation(),
768 'host': host.short_representation(),
769 'label': label.short_representation(),
showardf828c772010-01-25 21:49:42 +0000770 'atomic_group_class':
showardf46ad4c2010-02-03 20:28:59 +0000771 atomic_group_class.short_representation(),
showardf828c772010-01-25 21:49:42 +0000772 'status': self.instance.status,
773 'execution_path': self.instance.execution_subdir,
774 'started_on': self._format_datetime(self.instance.started_on),
775 'aborted': bool(self.instance.aborted)})
776 return rep
777
778
779 def update(self, input_dict):
780 if 'aborted' in input_dict:
781 if input_dict['aborted'] != True:
782 raise BadRequest('"aborted" can only be set to true')
783 query = models.HostQueueEntry.objects.filter(pk=self.instance.pk)
784 models.AclGroup.check_abort_permissions(query)
785 rpc_utils.check_abort_synchronous_jobs(query)
786 self.instance.abort(thread_local.get_user())
787
788
jamesren3981f442010-02-16 19:27:59 +0000789class QueueEntryCollection(resource_lib.Collection):
790 queryset = models.HostQueueEntry.objects.order_by('-id')
791 entry_class = QueueEntry
792
793
794class HealthTask(resource_lib.InstanceEntry):
showardf828c772010-01-25 21:49:42 +0000795 _permitted_methods = ('GET',)
jamesren3981f442010-02-16 19:27:59 +0000796 model = models.SpecialTask
797
showardf828c772010-01-25 21:49:42 +0000798
799 @classmethod
jamesren3981f442010-02-16 19:27:59 +0000800 def add_query_selectors(cls, query_processor):
801 query_processor.add_field_selector('host', field='host__hostname')
802
803
804 @classmethod
805 def from_uri_args(cls, request, task_id):
showardf828c772010-01-25 21:49:42 +0000806 instance = models.SpecialTask.objects.get(id=task_id)
showardf46ad4c2010-02-03 20:28:59 +0000807 return cls(request, instance)
showardf828c772010-01-25 21:49:42 +0000808
809
810 def _uri_args(self):
jamesren3981f442010-02-16 19:27:59 +0000811 return {'task_id': self.instance.id}
showardf828c772010-01-25 21:49:42 +0000812
813
814 def short_representation(self):
815 rep = super(HealthTask, self).short_representation()
showardf46ad4c2010-02-03 20:28:59 +0000816 host = Host(self._request, self.instance.host)
817 queue_entry = QueueEntry.from_optional_instance(
818 self._request, self.instance.queue_entry)
showardf828c772010-01-25 21:49:42 +0000819 rep.update(
showardf46ad4c2010-02-03 20:28:59 +0000820 {'host': host.short_representation(),
showardf828c772010-01-25 21:49:42 +0000821 'task_type': self.instance.task,
822 'started_on':
823 self._format_datetime(self.instance.time_started),
824 'status': self.instance.status,
showardf46ad4c2010-02-03 20:28:59 +0000825 'queue_entry': queue_entry.short_representation()
showardf828c772010-01-25 21:49:42 +0000826 })
827 return rep
828
829
830 @classmethod
831 def create_instance(cls, input_dict, containing_collection):
832 cls._check_for_required_fields(input_dict, ('task_type',))
833 host = containing_collection.base_entry.instance
834 models.AclGroup.check_for_acl_violation_hosts((host,))
835 return models.SpecialTask.schedule_special_task(host,
836 input_dict['task_type'])
837
838
839 def update(self, input_dict):
840 # Required for POST, doesn't actually support PUT
841 pass
842
843
jamesren3981f442010-02-16 19:27:59 +0000844class HealthTaskCollection(resource_lib.Collection):
845 entry_class = HealthTask
846
847
848 def _fresh_queryset(self):
849 return models.SpecialTask.objects.order_by('-id')
850
851
showardf828c772010-01-25 21:49:42 +0000852class ResourceDirectory(resource_lib.Resource):
853 _permitted_methods = ('GET',)
854
showardf46ad4c2010-02-03 20:28:59 +0000855 def handle_request(self):
showardf828c772010-01-25 21:49:42 +0000856 result = self.link()
857 result.update({
showardf46ad4c2010-02-03 20:28:59 +0000858 'atomic_group_classes':
859 AtomicGroupClassCollection(self._request).link(),
860 'labels': LabelCollection(self._request).link(),
861 'users': UserCollection(self._request).link(),
862 'acl_groups': AclCollection(self._request).link(),
863 'hosts': HostCollection(self._request).link(),
864 'tests': TestCollection(self._request).link(),
865 'execution_info': ExecutionInfo(self._request).link(),
866 'queue_entries_request':
867 QueueEntriesRequest(self._request).link(),
868 'jobs': JobCollection(self._request).link(),
showardf828c772010-01-25 21:49:42 +0000869 })
870 return self._basic_response(result)