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