blob: 966bfd9c47a13874ec8d145aaeab92ad98aaa012 [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
4from autotest_lib.frontend import thread_local
5from autotest_lib.client.common_lib import host_protections
6
7class EntryWithInvalid(resource_lib.Entry):
8 def put(self, request):
9 if self.instance.invalid:
10 raise http.Http404
11 return super(EntryWithInvalid, self).put(request)
12
13
14 def delete(self, request):
15 if self.instance.invalid:
16 raise http.Http404
17 return super(EntryWithInvalid, self).delete(request)
18
19
20class AtomicGroupClass(EntryWithInvalid):
21 @classmethod
22 def from_uri_args(cls, name):
23 return cls(models.AtomicGroup.objects.get(name=name))
24
25
26 def _uri_args(self):
27 return (self.instance.name,), {}
28
29
30 def short_representation(self):
31 rep = super(AtomicGroupClass, self).short_representation()
32 rep['name'] = self.instance.name
33 return rep
34
35
36 def full_representation(self):
37 rep = super(AtomicGroupClass, self).full_representation()
38 rep.update({'max_number_of_machines':
39 self.instance.max_number_of_machines,
40 'labels': AtomicGroupLabels(self).link()})
41 return rep
42
43
44 @classmethod
45 def create_instance(cls, input_dict, containing_collection):
46 cls._check_for_required_fields(input_dict, ('name',))
47 return models.AtomicGroup.add_object(name=input_dict['name'])
48
49
50 def update(self, input_dict):
51 data = {'max_number_of_machines':
52 input_dict.get('max_number_of_machines')}
53 data = input_dict.remove_unspecified_fields(data)
54 self.instance.update_object(**data)
55
56
57class AtomicGroupClassCollection(resource_lib.Collection):
58 queryset = models.AtomicGroup.valid_objects.all()
59 entry_class = AtomicGroupClass
60
61
62class AtomicGroupLabels(resource_lib.Relationship):
63 base_entry_class = AtomicGroupClass
64 entry_class = 'autotest_lib.frontend.afe.resources.Label'
65
66 def _fresh_queryset(self):
67 return self.base_entry.instance.label_set.all()
68
69
70 def _update_relationship(self, related_instances):
71 self.base_entry.instance.label_set = related_instances
72
73
74class Label(EntryWithInvalid):
75 class QueryProcessor(query_lib.BaseQueryProcessor):
76 @classmethod
77 def _add_all_selectors(cls):
78 cls._add_field_selector('name')
79 cls._add_field_selector('is_platform', field='platform',
80 value_transform=cls.read_boolean)
81
82
83 @classmethod
84 def from_uri_args(cls, name):
85 return cls(models.Label.objects.get(name=name))
86
87
88 def _uri_args(self):
89 return (self.instance.name,), {}
90
91
92 def short_representation(self):
93 rep = super(Label, self).short_representation()
94 rep.update({'name': self.instance.name,
95 'is_platform': bool(self.instance.platform)})
96 return rep
97
98
99 def full_representation(self):
100 rep = super(Label, self).full_representation()
101 atomic_group_class_rep = AtomicGroupClass.from_optional_instance(
102 self.instance.atomic_group).short_representation()
103 rep.update({'atomic_group_class': atomic_group_class_rep,
104 'hosts': LabelHosts(self).link()})
105 return rep
106
107
108 @classmethod
109 def create_instance(cls, input_dict, containing_collection):
110 cls._check_for_required_fields(input_dict, ('name',))
111 return models.Label.add_object(name=input_dict['name'])
112
113
114 def update(self, input_dict):
115 # TODO update atomic group
116 raise NotImplementedError
117
118
119class LabelCollection(resource_lib.Collection):
120 queryset = models.Label.valid_objects.all()
121 entry_class = Label
122
123
124class LabelHosts(resource_lib.Relationship):
125 base_entry_class = Label
126 entry_class = 'autotest_lib.frontend.afe.resources.Host'
127
128
129 def _fresh_queryset(self):
130 return self.base_entry.instance.host_set.all()
131
132
133 def _update_relationship(self, related_instances):
134 self.base_entry.instance.host_set = related_instances
135
136
137class User(resource_lib.Entry):
138 _permitted_methods = ('GET,')
139
140
141 @classmethod
142 def from_uri_args(cls, username):
143 if username == '@me':
144 return cls(models.User.current_user())
145 return cls(models.User.objects.get(login=username))
146
147
148 def _uri_args(self):
149 return (self.instance.login,), {}
150
151
152 def short_representation(self):
153 rep = super(User, self).short_representation()
154 rep['username'] = self.instance.login
155 return rep
156
157
158 def full_representation(self):
159 rep = super(User, self).full_representation()
160 rep.update({'jobs': 'TODO',
161 'recurring_runs': 'TODO',
162 'accessible_hosts': UserAccessibleHosts(self).link(),
163 'acls': UserAcls(self).link()})
164 return rep
165
166
167class UserCollection(resource_lib.Collection):
168 _permitted_methods = ('GET',)
169 queryset = models.User.objects.all()
170 entry_class = User
171
172
173class UserAcls(resource_lib.Relationship):
174 base_entry_class = User
175 entry_class = 'autotest_lib.frontend.afe.resources.Acl'
176
177
178 def _fresh_queryset(self):
179 return self.base_entry.instance.aclgroup_set.all()
180
181
182 def _update_relationship(self, related_instances):
183 # TODO check for and add/remove "everyone"
184 self.base_entry.instance.aclgroup_set = related_instances
185
186
187class UserAccessibleHosts(resource_lib.Relationship):
188 _permitted_methods = ('GET',)
189 base_entry_class = User
190 entry_class = 'autotest_lib.frontend.afe.resources.Host'
191
192
193 def _fresh_queryset(self):
194 return models.Host.objects.filter(
195 aclgroup__users=self.base_entry.instance)
196
197
198class Acl(resource_lib.Entry):
199 _permitted_methods = ('GET',)
200
201 @classmethod
202 def from_uri_args(cls, name):
203 return cls(models.AclGroup.objects.get(name=name))
204
205
206 def _uri_args(self):
207 return (self.instance.name,), {}
208
209
210 def short_representation(self):
211 rep = super(Acl, self).short_representation()
212 rep['name'] = self.instance.name
213 return rep
214
215
216 def full_representation(self):
217 rep = super(Acl, self).full_representation()
218 rep.update({'users': AclUsers(self).link(),
219 'hosts': AclHosts(self).link()})
220 return rep
221
222
223 @classmethod
224 def create_instance(cls, input_dict, containing_collection):
225 cls._check_for_required_fields(input_dict, ('name',))
226 return models.AclGroup.add_object(name=input_dict['name'])
227
228
229 def update(self, input_dict):
230 pass
231
232
233class AclCollection(resource_lib.Collection):
234 queryset = models.AclGroup.objects.all()
235 entry_class = Acl
236
237
238class AclUsers(resource_lib.Relationship):
239 base_entry_class = Acl
240 entry_class = User
241
242
243 def _fresh_queryset(self):
244 return self.base_entry.instance.users.all()
245
246
247 def _update_relationship(self, related_instances):
248 self.base_entry.instance.users = related_instances
249
250
251class AclHosts(resource_lib.Relationship):
252 base_entry_class = Acl
253 entry_class = 'autotest_lib.frontend.afe.resources.Host'
254
255
256 def _fresh_queryset(self):
257 return self.base_entry.instance.hosts.all()
258
259
260 def _update_relationship(self, related_instances):
261 self.base_entry.instance.hosts = related_instances
262
263
264class Host(EntryWithInvalid):
265 class QueryProcessor(query_lib.BaseQueryProcessor):
266 @classmethod
267 def _add_all_selectors(cls):
268 cls._add_field_selector('hostname')
269 cls._add_field_selector('locked', value_transform=cls.read_boolean)
270 cls._add_field_selector(
271 'locked_by', field='locked_by__login',
272 doc='Username of user who locked this host, if locked')
273 cls._add_field_selector('status')
274 cls._add_field_selector('protection_level', field='protection',
275 doc='Verify/repair protection level',
276 value_transform=Host._read_protection)
277 cls._add_related_existence_selector('has_label', models.Label,
278 'name')
279
280
281 @classmethod
282 def _read_protection(cls, protection_input):
283 return host_protections.Protection.get_value(protection_input)
284
285
286 @classmethod
287 def from_uri_args(cls, hostname):
288 return cls(models.Host.objects.get(hostname=hostname))
289
290
291 def _uri_args(self):
292 return (self.instance.hostname,), {}
293
294
295 def short_representation(self):
296 rep = super(Host, self).short_representation()
297 # TODO calling platform() over and over is inefficient
298 platform_rep = (Label.from_optional_instance(self.instance.platform())
299 .short_representation())
300 rep.update({'hostname': self.instance.hostname,
301 'locked': bool(self.instance.locked),
302 'status': self.instance.status,
303 'platform': platform_rep})
304 return rep
305
306
307 def full_representation(self):
308 rep = super(Host, self).full_representation()
309 protection = host_protections.Protection.get_string(
310 self.instance.protection)
311 locked_by = (User.from_optional_instance(self.instance.locked_by)
312 .short_representation())
313 rep.update({'locked_by': locked_by,
314 'locked_on': self._format_datetime(self.instance.lock_time),
315 'invalid': self.instance.invalid,
316 'protection_level': protection,
317 # TODO make these efficient
318 'labels': HostLabels(self).full_representation(),
319 'acls': HostAcls(self).full_representation(),
320 'queue_entries': HostQueueEntries(self).link(),
321 'health_tasks': HostHealthTasks(self).link()})
322 return rep
323
324
325 @classmethod
326 def create_instance(cls, input_dict, containing_collection):
327 cls._check_for_required_fields(input_dict, ('hostname',))
328
329 # always create locked, so we can set up ACLs safely
330 instance = models.Host.add_object(hostname=input_dict['hostname'],
331 locked=True)
332
333 if 'acls' in input_dict:
334 entry = Host(instance)
335 HostAcls(entry).update(input_dict['acls'])
336
337 instance.locked = False # restore default
338 instance.save()
339 return instance
340
341
342 def update(self, input_dict):
343 data = {'locked': input_dict.get('locked'),
344 'protection': input_dict.get('protection_level')}
345 data = input_dict.remove_unspecified_fields(data)
346
347 if 'protection' in data:
348 data['protection'] = self._read_protection(data['protection'])
349
350 self.instance.update_object(**data)
351
352 if 'platform' in input_dict:
353 label = (resource_lib.Resource.resolve_link(input_dict['platform'])
354 .instance)
355 if not label.platform:
356 raise BadRequest('Label %s is not a platform' % label.name)
357 for label in self.instance.labels.filter(platform=True):
358 self.instance.labels.remove(label)
359 self.instance.labels.add(label)
360
361
362class HostCollection(resource_lib.Collection):
363 queryset = models.Host.valid_objects.all()
364 entry_class = Host
365
366
367class HostLabels(resource_lib.Relationship):
368 base_entry_class = Host
369 entry_class = Label
370
371
372 def _fresh_queryset(self):
373 return self.base_entry.instance.labels.all()
374
375
376 def _update_relationship(self, related_instances):
377 self.base_entry.instance.labels = related_instances
378
379
380class HostAcls(resource_lib.Relationship):
381 base_entry_class = Host
382 entry_class = Acl
383
384
385 def _fresh_queryset(self):
386 return self.base_entry.instance.aclgroup_set.all()
387
388
389 def _update_relationship(self, related_instances):
390 for acl in related_instances:
391 acl.check_for_acl_violation_acl_group()
392 self.base_entry.instance.aclgroup_set = related_instances
393 models.AclGroup.on_host_membership_change()
394
395
396class HostQueueEntries(resource_lib.Relationship):
397 _permitted_methods = ('GET',)
398 base_entry_class = Host
399 entry_class = 'autotest_lib.frontend.afe.resources.QueueEntry'
400
401
402 def _fresh_queryset(self):
403 return self.base_entry.instance.hostqueueentry_set.order_by('-id')
404
405
406class HostHealthTasks(resource_lib.Relationship):
407 _permitted_methods = ('GET', 'POST')
408 base_entry_class = Host
409 entry_class = 'autotest_lib.frontend.afe.resources.HealthTask'
410
411
412 def _fresh_queryset(self):
413 return self.base_entry.instance.specialtask_set.order_by('-id')
414
415
416class Test(resource_lib.Entry):
417 @classmethod
418 def from_uri_args(cls, name):
419 return cls(models.Test.objects.get(name=name))
420
421
422 def _uri_args(self):
423 return (self.instance.name,), {}
424
425
426 def short_representation(self):
427 rep = super(Test, self).short_representation()
428 rep['name'] = self.instance.name
429 return rep
430
431
432 def full_representation(self):
433 rep = super(Test, self).full_representation()
434 rep.update({'author': self.instance.author,
435 'class': self.instance.test_class,
436 'control_file_type':
437 models.Test.Types.get_string(self.instance.test_type),
438 'control_file_path': self.instance.path,
439 'dependencies': TestDependencies(self).link(),
440 })
441 return rep
442
443
444 @classmethod
445 def create_instance(cls, input_dict, containing_collection):
446 cls._check_for_required_fields(input_dict,
447 ('name', 'control_file_type',
448 'control_file_path'))
449 test_type = models.Test.Type.get_value(input['control_file_type'])
450 return models.Test.add_object(name=input_dict['name'],
451 test_type=test_type,
452 path=input_dict['control_file_path'])
453
454
455 def update(self, input_dict):
456 data = {'test_type': input_dict.get('control_file_type'),
457 'path': input_dict.get('control_file_path'),
458 'class': input_dict.get('class'),
459 }
460 data = input_dict.remove_unspecified_fields(data)
461 self.instance.update_object(**data)
462
463
464class TestCollection(resource_lib.Collection):
465 queryset = models.Test.objects.all()
466 entry_class = Test
467
468
469class TestDependencies(resource_lib.Relationship):
470 base_entry_class = Test
471 entry_class = Label
472
473 def _fresh_queryset(self):
474 return self.base_entry.instance.dependency_labels.all()
475
476
477 def _update_relationship(self, related_instances):
478 self.base_entry.instance.dependency_labels = related_instances
479
480
481# TODO profilers
482
483
484class ExecutionInfo(resource_lib.Resource):
485 _permitted_methods = ('GET','POST')
486 _job_fields = models.Job.get_field_dict()
487 _DEFAULTS = {
488 'control_file': '',
489 'is_server': True,
490 'dependencies': [],
491 'machines_per_execution': 1,
492 'run_verify': bool(_job_fields['run_verify'].default),
493 'timeout_hrs': _job_fields['timeout'].default,
494 'maximum_runtime_hrs': _job_fields['max_runtime_hrs'].default,
495 'cleanup_before_job':
496 models.RebootBefore.get_string(models.DEFAULT_REBOOT_BEFORE),
497 'cleanup_after_job':
498 models.RebootAfter.get_string(models.DEFAULT_REBOOT_AFTER),
499 }
500
501
502 def _query_parameters(self):
503 return (('tests', 'Comma-separated list of test names to run'),
504 ('kernels', 'TODO'),
505 ('client_control_file',
506 'Client control file segment to run after all specified '
507 'tests'),
508 ('profilers',
509 'Comma-separated list of profilers to activate during the '
510 'job'),
511 ('use_container', 'TODO'),
512 ('profile_only',
513 'If true, run only profiled iterations; otherwise, always run '
514 'at least one non-profiled iteration in addition to a '
515 'profiled iteration'),
516 ('upload_kernel_config',
517 'If true, generate a server control file code that uploads '
518 'the kernel config file to the client and tells the client of '
519 'the new (local) path when compiling the kernel; the tests '
520 'must be server side tests'))
521
522
523 @classmethod
524 def execution_info_from_job(cls, job):
525 return {'control_file': job.control_file,
526 'is_server': job.control_type == models.Job.ControlType.SERVER,
527 'dependencies': [label.name for label
528 in job.dependency_labels.all()],
529 'machines_per_execution': job.synch_count,
530 'run_verify': bool(job.run_verify),
531 'timeout_hrs': job.timeout,
532 'maximum_runtime_hrs': job.max_runtime_hrs,
533 'cleanup_before_job':
534 models.RebootBefore.get_string(job.reboot_before),
535 'cleanup_after_job':
536 models.RebootAfter.get_string(job.reboot_after),
537 }
538
539
540 def _get_execution_info(self, input_dict):
541 tests = input_dict.get('tests', '')
542 client_control_file = input_dict.get('client_control_file', None)
543 if not tests and not client_control_file:
544 return self._DEFAULTS
545
546 test_list = tests.split(',')
547 if 'profilers' in input_dict:
548 profilers_list = input_dict['profilers'].split(',')
549 else:
550 profilers_list = []
551 kernels = input_dict.get('kernels', '') # TODO
552 if kernels:
553 kernels = [dict(version=kernel) for kernel in kernels.split(',')]
554
555 cf_info, test_objects, profiler_objects, label = (
556 rpc_utils.prepare_generate_control_file(
557 test_list, kernels, None, profilers_list))
558 control_file_contents = control_file.generate_control(
559 tests=test_objects, kernels=kernels,
560 profilers=profiler_objects, is_server=cf_info['is_server'],
561 client_control_file=client_control_file,
562 profile_only=input_dict.get('profile_only', None),
563 upload_kernel_config=input_dict.get(
564 'upload_kernel_config', None))
565 return dict(self._DEFAULTS,
566 control_file=control_file_contents,
567 is_server=cf_info['is_server'],
568 dependencies=cf_info['dependencies'],
569 machines_per_execution=cf_info['synch_count'])
570
571
572 def handle_request(self, request):
573 result = self.link()
574 result['execution_info'] = self._get_execution_info(request.REQUEST)
575 return self._basic_response(result)
576
577
578class QueueEntriesRequest(resource_lib.Resource):
579 _permitted_methods = ('GET',)
580
581
582 def _query_parameters(self):
583 return (('hosts', 'Comma-separated list of hostnames'),
584 ('one_time_hosts',
585 'Comma-separated list of hostnames not already in the '
586 'Autotest system'),
587 ('meta_hosts',
588 'Comma-separated list of label names; for each one, an entry '
589 'will be created and assigned at runtime to an available host '
590 'with that label'),
591 ('atomic_group_class', 'TODO'))
592
593
594 def _read_list(self, list_string):
595 if list_string:
596 return list_string.split(',')
597 return []
598
599
600 def handle_request(self, request):
601 request_dict = request.REQUEST
602 hosts = self._read_list(request_dict.get('hosts'))
603 one_time_hosts = self._read_list(request_dict.get('one_time_hosts'))
604 meta_hosts = self._read_list(request_dict.get('meta_hosts'))
605 atomic_group_class = request_dict.get('atomic_group_class')
606
607 # TODO: bring in all the atomic groups magic from create_job()
608
609 entries = []
610 for hostname in one_time_hosts:
611 models.Host.create_one_time_host(hostname)
612 for hostname in hosts:
613 entry = Host.from_uri_args(hostname)
614 entries.append({'host': entry.link()})
615 for label_name in meta_hosts:
616 entry = Label.from_uri_args(label_name)
617 entries.append({'meta_host': entry.link()})
618
619 result = self.link()
620 result['queue_entries'] = entries
621 return self._basic_response(result)
622
623
624class Job(resource_lib.Entry):
625 _permitted_methods = ('GET',)
626
627 class _StatusConstraint(query_lib.Constraint):
628 @classmethod
629 def apply_constraint(cls, queryset, value, comparison_type, is_inverse):
630 if comparison_type != 'equals' or is_inverse:
631 raise query_lib.ConstraintError('Can only use this selector '
632 'with equals')
633 non_queued_statuses = [
634 status for status, _
635 in models.HostQueueEntry.Status.choices()
636 if status != models.HostQueueEntry.Status.QUEUED]
637 if value == 'queued':
638 return queryset.exclude(
639 hostqueueentry__status__in=non_queued_statuses)
640 elif value == 'active':
641 return queryset.filter(
642 hostqueueentry__status__in=non_queued_statuses).filter(
643 hostqueueentry__complete=False).distinct()
644 elif value == 'complete':
645 return queryset.exclude(hostqueueentry__complete=False)
646 else:
647 raise query_lib.ConstraintError('Value must be one of queued, '
648 'active or complete')
649
650
651 class QueryProcessor(query_lib.BaseQueryProcessor):
652 @classmethod
653 def _add_all_selectors(cls):
654 cls._add_field_selector('id')
655 cls._add_selector(
656 query_lib.Selector('status',
657 doc='One of queued, active or complete'),
658 Job._StatusConstraint())
659
660
661 @classmethod
662 def from_uri_args(cls, job_id):
663 return cls(models.Job.objects.get(id=job_id))
664
665
666 def _uri_args(self):
667 return (self.instance.id,), {}
668
669
670 def short_representation(self):
671 rep = super(Job, self).short_representation()
672 rep.update({'id': self.instance.id,
673 'owner': self.instance.owner,
674 'name': self.instance.name,
675 'priority':
676 models.Job.Priority.get_string(self.instance.priority),
677 'created_on':
678 self._format_datetime(self.instance.created_on),
679 })
680 return rep
681
682
683 def full_representation(self):
684 rep = super(Job, self).full_representation()
685 rep.update({'email_list': self.instance.email_list,
686 'parse_failed_repair':
687 bool(self.instance.parse_failed_repair),
688 'execution_info':
689 ExecutionInfo.execution_info_from_job(self.instance),
690 'queue_entries': JobQueueEntries(self).link(),
691 })
692 return rep
693
694
695 @classmethod
696 def create_instance(cls, input_dict, containing_collection):
697 cls._check_for_required_fields(input_dict, ('name', 'execution_info',
698 'queue_entries'))
699 execution_info = input_dict['execution_info']
700 cls._check_for_required_fields(execution_info, ('control_file',
701 'is_server'))
702
703 if execution_info['is_server']:
704 control_type = models.Job.ControlType.SERVER
705 else:
706 control_type = models.Job.ControlType.CLIENT
707 options = dict(
708 name=input_dict['name'],
709 priority=input_dict.get('priority', None),
710 control_file=execution_info['control_file'],
711 control_type=control_type,
712 is_template=input_dict.get('is_template', None),
713 timeout=execution_info.get('timeout_hrs'),
714 max_runtime_hrs=execution_info.get('maximum_runtime_hrs'),
715 synch_count=execution_info.get('machines_per_execution'),
716 run_verify=execution_info.get('run_verify'),
717 email_list=input_dict.get('email_list', None),
718 dependencies=execution_info.get('dependencies', ()),
719 reboot_before=execution_info.get('cleanup_before_job'),
720 reboot_after=execution_info.get('cleanup_after_job'),
721 parse_failed_repair=input_dict.get('parse_failed_repair', None),
722 keyvals=input_dict.get('keyvals', None))
723
724 host_objects, metahost_label_objects, atomic_group = [], [], None
725 for queue_entry in input_dict['queue_entries']:
726 if 'host' in queue_entry:
727 host = queue_entry['host']
728 if host: # can be None, indicated a hostless job
729 host_entry = resource_lib.Resource.resolve_link(host)
730 host_objects.append(host_entry.instance)
731 elif 'meta_host' in queue_entry:
732 label_entry = resource_lib.Resource.resolve_link(
733 queue_entry['meta_host'])
734 metahost_label_objects.append(label_entry.instance)
735 if 'atomic_group' in queue_entry:
736 atomic_group_entry = resource_lib.Resource.resolve_link(
737 queue_entry['atomic_group'])
738 if atomic_group:
739 assert atomic_group_entry.instance.id == atomic_group.id
740 else:
741 atomic_group = atomic_group_entry.instance
742
743 job_id = rpc_utils.create_new_job(
744 owner=models.User.current_user().login,
745 options=options,
746 host_objects=host_objects,
747 metahost_objects=metahost_label_objects,
748 atomic_group=atomic_group)
749 return models.Job.objects.get(id=job_id)
750
751
752 def update(self, input_dict):
753 # Required for POST, doesn't actually support PUT
754 pass
755
756
757class JobCollection(resource_lib.Collection):
758 queryset = models.Job.objects.order_by('-id')
759 entry_class = Job
760
761
762class JobQueueEntries(resource_lib.Relationship):
763 _permitted_methods = ('GET',)
764
765 base_entry_class = Job
766 entry_class = 'autotest_lib.frontend.afe.resources.QueueEntry'
767
768 def _fresh_queryset(self):
769 return self.base_entry.instance.hostqueueentry_set.all()
770
771
772class QueueEntry(resource_lib.Entry):
773 _permitted_methods = ('GET', 'PUT')
774
775 @classmethod
776 def from_uri_args(cls, job_id, queue_entry_id):
777 instance = models.HostQueueEntry.objects.get(id=queue_entry_id)
778 if instance.job.id != int(job_id):
779 raise http.Http404('Incorrect job ID %r (expected %r)'
780 % (job_id, instance.job.id))
781 return cls(instance)
782
783
784 def _uri_args(self):
785 return (self.instance.job_id, self.instance.id), {}
786
787
788 def short_representation(self):
789 rep = super(QueueEntry, self).short_representation()
790 if self.instance.host:
791 host = Host(self.instance.host).short_representation()
792 else:
793 host = None
794 rep.update(
795 {'job': Job(self.instance.job).short_representation(),
796 'host': (Host.from_optional_instance(self.instance.host)
797 .short_representation()),
798 'label': (Label.from_optional_instance(self.instance.meta_host)
799 .short_representation()),
800 'atomic_group_class':
801 AtomicGroupClass.from_optional_instance(
802 self.instance.atomic_group).short_representation(),
803 'status': self.instance.status,
804 'execution_path': self.instance.execution_subdir,
805 'started_on': self._format_datetime(self.instance.started_on),
806 'aborted': bool(self.instance.aborted)})
807 return rep
808
809
810 def update(self, input_dict):
811 if 'aborted' in input_dict:
812 if input_dict['aborted'] != True:
813 raise BadRequest('"aborted" can only be set to true')
814 query = models.HostQueueEntry.objects.filter(pk=self.instance.pk)
815 models.AclGroup.check_abort_permissions(query)
816 rpc_utils.check_abort_synchronous_jobs(query)
817 self.instance.abort(thread_local.get_user())
818
819
820class HealthTask(resource_lib.Entry):
821 _permitted_methods = ('GET',)
822
823 @classmethod
824 def from_uri_args(cls, hostname, task_id):
825 instance = models.SpecialTask.objects.get(id=task_id)
826 if instance.host.hostname != hostname:
827 raise http.Http404('Incorrect hostname %r (expected %r)'
828 % (hostname, instance.host.hostname))
829 return cls(instance)
830
831
832 def _uri_args(self):
833 return (self.instance.host.hostname, self.instance.id), {}
834
835
836 def short_representation(self):
837 rep = super(HealthTask, self).short_representation()
838 rep.update(
839 {'host': Host(self.instance.host).short_representation(),
840 'task_type': self.instance.task,
841 'started_on':
842 self._format_datetime(self.instance.time_started),
843 'status': self.instance.status,
844 'queue_entry': QueueEntry.from_optional_instance(
845 self.instance.queue_entry).short_representation()
846 })
847 return rep
848
849
850 @classmethod
851 def create_instance(cls, input_dict, containing_collection):
852 cls._check_for_required_fields(input_dict, ('task_type',))
853 host = containing_collection.base_entry.instance
854 models.AclGroup.check_for_acl_violation_hosts((host,))
855 return models.SpecialTask.schedule_special_task(host,
856 input_dict['task_type'])
857
858
859 def update(self, input_dict):
860 # Required for POST, doesn't actually support PUT
861 pass
862
863
864class ResourceDirectory(resource_lib.Resource):
865 _permitted_methods = ('GET',)
866
867 def handle_request(self, request):
868 result = self.link()
869 result.update({
870 'atomic_group_classes': AtomicGroupClassCollection().link(),
871 'labels': LabelCollection().link(),
872 'users': UserCollection().link(),
873 'acl_groups': AclCollection().link(),
874 'hosts': HostCollection().link(),
875 'tests': TestCollection().link(),
876 'execution_info': ExecutionInfo().link(),
877 'queue_entries_request': QueueEntriesRequest().link(),
878 'jobs': JobCollection().link(),
879 })
880 return self._basic_response(result)