Home | History | Annotate | Download | only in afe
      1 from django import http
      2 from autotest_lib.frontend.shared import query_lib, resource_lib, exceptions
      3 from autotest_lib.frontend.afe import control_file, models, rpc_utils
      4 from autotest_lib.frontend.afe import model_attributes
      5 from autotest_lib.frontend import thread_local
      6 from autotest_lib.client.common_lib import host_protections
      7 from autotest_lib.client.common_lib import control_data
      8 from autotest_lib.client.common_lib import priorities
      9 
     10 
     11 class 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 
     24 class 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 
     65 class AtomicGroupClassCollection(resource_lib.Collection):
     66     queryset = models.AtomicGroup.valid_objects.all()
     67     entry_class = AtomicGroupClass
     68 
     69 
     70 class 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 
    120 class LabelCollection(resource_lib.Collection):
    121     queryset = models.Label.valid_objects.all()
    122     entry_class = Label
    123 
    124 
    125 class AtomicLabelTagging(resource_lib.Relationship):
    126     related_classes = {'label': Label, 'atomic_group_class': AtomicGroupClass}
    127 
    128 
    129 class AtomicLabelTaggingCollection(resource_lib.RelationshipCollection):
    130     entry_class = AtomicLabelTagging
    131 
    132 
    133 class 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 
    167 class UserCollection(resource_lib.Collection):
    168     _permitted_methods = ('GET',)
    169     queryset = models.User.objects.all()
    170     entry_class = User
    171 
    172 
    173 class 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 
    211 class AclCollection(resource_lib.Collection):
    212     queryset = models.AclGroup.objects.all()
    213     entry_class = Acl
    214 
    215 
    216 class 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 
    223 class UserAclMembershipCollection(resource_lib.RelationshipCollection):
    224     entry_class = UserAclMembership
    225 
    226 
    227 class 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 
    331 class HostCollection(resource_lib.Collection):
    332     queryset = models.Host.valid_objects.all()
    333     entry_class = Host
    334 
    335 
    336 class HostLabeling(resource_lib.Relationship):
    337     related_classes = {'host': Host, 'label': Label}
    338 
    339 
    340 class HostLabelingCollection(resource_lib.RelationshipCollection):
    341     entry_class = HostLabeling
    342 
    343 
    344 class 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 
    351 class HostAclMembershipCollection(resource_lib.RelationshipCollection):
    352     entry_class = HostAclMembership
    353 
    354 
    355 class 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 
    415 class TestCollection(resource_lib.Collection):
    416     queryset = models.Test.objects.all()
    417     entry_class = Test
    418 
    419 
    420 class TestDependency(resource_lib.Relationship):
    421     related_classes = {'test': Test, 'label': Label}
    422 
    423 
    424 class TestDependencyCollection(resource_lib.RelationshipCollection):
    425     entry_class = TestDependency
    426 
    427 
    428 # TODO profilers
    429 
    430 
    431 class 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 
    531 class 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 
    579 class 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 
    738 class JobCollection(resource_lib.Collection):
    739     queryset = models.Job.objects.order_by('-id')
    740     entry_class = Job
    741 
    742 
    743 class 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 
    800 class QueueEntryCollection(resource_lib.Collection):
    801     queryset = models.HostQueueEntry.objects.order_by('-id')
    802     entry_class = QueueEntry
    803 
    804 
    805 class 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 
    855 class 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 
    863 class 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