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