1#!/usr/bin/python 2# pylint: disable=missing-docstring 3 4import unittest 5 6import common 7from autotest_lib.client.common_lib import error 8from autotest_lib.frontend import setup_django_environment 9from autotest_lib.frontend.afe import frontend_test_utils 10from autotest_lib.frontend.afe import models, model_logic 11 12 13class AclGroupTest(unittest.TestCase, 14 frontend_test_utils.FrontendTestMixin): 15 def setUp(self): 16 self._frontend_common_setup() 17 18 19 def tearDown(self): 20 self._frontend_common_teardown() 21 22 23 def _check_acls(self, host, acl_name_list): 24 actual_acl_names = [acl_group.name for acl_group 25 in host.aclgroup_set.all()] 26 self.assertEquals(set(actual_acl_names), set(acl_name_list)) 27 28 29 def test_on_host_membership_change(self): 30 host1, host2 = self.hosts[1:3] 31 everyone_acl = models.AclGroup.objects.get(name='Everyone') 32 33 host1.aclgroup_set.clear() 34 self._check_acls(host1, []) 35 host2.aclgroup_set.add(everyone_acl) 36 self._check_acls(host2, ['Everyone', 'my_acl']) 37 38 models.AclGroup.on_host_membership_change() 39 40 self._check_acls(host1, ['Everyone']) 41 self._check_acls(host2, ['my_acl']) 42 43 44class HostTest(unittest.TestCase, 45 frontend_test_utils.FrontendTestMixin): 46 def setUp(self): 47 self._frontend_common_setup() 48 49 50 def tearDown(self): 51 self._frontend_common_teardown() 52 53 54 def _get_attributes(self, host): 55 models.Host.objects.populate_relationships( 56 [host], models.HostAttribute, 'attribute_list') 57 return dict((attribute.attribute, attribute.value) 58 for attribute in host.attribute_list) 59 60 def test_delete_attribute(self): 61 previous_config = models.RESPECT_STATIC_ATTRIBUTES 62 models.RESPECT_STATIC_ATTRIBUTES = False 63 host1 = models.Host.objects.create(hostname='test_host1') 64 host1.set_attribute('test_attribute1', 'test_value1') 65 66 attributes = self._get_attributes(host1) 67 self.assertEquals(attributes['test_attribute1'], 'test_value1') 68 69 host1.set_or_delete_attribute('test_attribute1', None) 70 attributes = self._get_attributes(host1) 71 self.assertNotIn('test_attribute1', attributes.keys()) 72 73 models.RESPECT_STATIC_ATTRIBUTES = previous_config 74 75 76 def test_delete_static_attribute(self): 77 previous_config = models.RESPECT_STATIC_ATTRIBUTES 78 models.RESPECT_STATIC_ATTRIBUTES = True 79 host1 = models.Host.objects.create(hostname='test_host1') 80 host1.set_attribute('test_attribute1', 'test_value1') 81 self._set_static_attribute(host1, 'test_attribute1', 'test_value1') 82 83 self.assertRaises( 84 error.UnmodifiableAttributeException, 85 host1.set_or_delete_attribute, 86 'test_attribute1', None) 87 88 models.RESPECT_STATIC_ATTRIBUTES = previous_config 89 90 91 def test_set_attribute(self): 92 previous_config = models.RESPECT_STATIC_ATTRIBUTES 93 models.RESPECT_STATIC_ATTRIBUTES = False 94 host1 = models.Host.objects.create(hostname='test_host1') 95 host1.set_attribute('test_attribute1', 'test_value1') 96 97 host1.set_or_delete_attribute('test_attribute1', 'test_new_value1') 98 99 attributes = self._get_attributes(host1) 100 self.assertEquals(attributes['test_attribute1'], 'test_new_value1') 101 102 models.RESPECT_STATIC_ATTRIBUTES = previous_config 103 104 105 def test_set_static_attribute(self): 106 previous_config = models.RESPECT_STATIC_ATTRIBUTES 107 models.RESPECT_STATIC_ATTRIBUTES = True 108 host1 = models.Host.objects.create(hostname='test_host1') 109 host1.set_attribute('test_attribute1', 'test_value1') 110 self._set_static_attribute(host1, 'test_attribute1', 'test_value1') 111 112 self.assertRaises( 113 error.UnmodifiableAttributeException, 114 host1.set_or_delete_attribute, 115 'test_attribute1', 'test_value2') 116 117 models.RESPECT_STATIC_ATTRIBUTES = previous_config 118 119 120 def test_add_host_previous_one_time_host(self): 121 # ensure that when adding a host which was previously used as a one-time 122 # host, the status isn't reset, since this can interfere with the 123 # scheduler. 124 host = models.Host.create_one_time_host('othost') 125 self.assertEquals(host.invalid, True) 126 self.assertEquals(host.status, models.Host.Status.READY) 127 128 host.status = models.Host.Status.RUNNING 129 host.save() 130 131 host2 = models.Host.add_object(hostname='othost') 132 self.assertEquals(host2.id, host.id) 133 self.assertEquals(host2.status, models.Host.Status.RUNNING) 134 135 136 def test_check_board_labels_allowed(self): 137 host = models.Host.create_one_time_host('othost') 138 # First check with host with no board label. 139 self.assertEqual(host.check_board_labels_allowed([host]), None) 140 141 # Second check with host with board label 142 label = models.Label.add_object(name='board:test') 143 label.host_set.add(host) 144 self.assertRaises(model_logic.ValidationError, 145 host.check_board_labels_allowed, [host], 146 ['board:new_board']) 147 148 149class SpecialTaskUnittest(unittest.TestCase, 150 frontend_test_utils.FrontendTestMixin): 151 def setUp(self): 152 self._frontend_common_setup() 153 154 155 def tearDown(self): 156 self._frontend_common_teardown() 157 158 159 def _create_task(self): 160 return models.SpecialTask.objects.create( 161 host=self.hosts[0], task=models.SpecialTask.Task.VERIFY, 162 requested_by=models.User.current_user()) 163 164 165 def test_execution_path(self): 166 task = self._create_task() 167 self.assertEquals(task.execution_path(), 'hosts/host1/1-verify') 168 169 170 def test_status(self): 171 task = self._create_task() 172 self.assertEquals(task.status, 'Queued') 173 174 task.update_object(is_active=True) 175 self.assertEquals(task.status, 'Running') 176 177 task.update_object(is_active=False, is_complete=True, success=True) 178 self.assertEquals(task.status, 'Completed') 179 180 task.update_object(success=False) 181 self.assertEquals(task.status, 'Failed') 182 183 184 def test_activate(self): 185 task = self._create_task() 186 task.activate() 187 self.assertTrue(task.is_active) 188 self.assertFalse(task.is_complete) 189 190 191 def test_finish(self): 192 task = self._create_task() 193 task.activate() 194 task.finish(True) 195 self.assertFalse(task.is_active) 196 self.assertTrue(task.is_complete) 197 self.assertTrue(task.success) 198 199 200 def test_requested_by_from_queue_entry(self): 201 job = self._create_job(hosts=[0]) 202 task = models.SpecialTask.objects.create( 203 host=self.hosts[0], task=models.SpecialTask.Task.VERIFY, 204 queue_entry=job.hostqueueentry_set.all()[0]) 205 self.assertEquals(task.requested_by.login, 'autotest_system') 206 207 208class HostQueueEntryUnittest(unittest.TestCase, 209 frontend_test_utils.FrontendTestMixin): 210 def setUp(self): 211 self._frontend_common_setup() 212 213 214 def tearDown(self): 215 self._frontend_common_teardown() 216 217 218 def test_execution_path(self): 219 entry = self._create_job(hosts=[1]).hostqueueentry_set.all()[0] 220 entry.execution_subdir = 'subdir' 221 entry.save() 222 223 self.assertEquals(entry.execution_path(), '1-autotest_system/subdir') 224 225 226class ModelWithInvalidTest(unittest.TestCase, 227 frontend_test_utils.FrontendTestMixin): 228 def setUp(self): 229 self._frontend_common_setup() 230 231 232 def tearDown(self): 233 self._frontend_common_teardown() 234 235 236 def test_model_with_invalid_delete(self): 237 self.assertFalse(self.hosts[0].invalid) 238 self.hosts[0].delete() 239 self.assertTrue(self.hosts[0].invalid) 240 self.assertTrue(models.Host.objects.get(id=self.hosts[0].id)) 241 242 243 def test_model_with_invalid_delete_queryset(self): 244 for host in self.hosts: 245 self.assertFalse(host.invalid) 246 247 hosts = models.Host.objects.all() 248 hosts.delete() 249 self.assertEqual(hosts.count(), len(self.hosts)) 250 251 for host in hosts: 252 self.assertTrue(host.invalid) 253 254 255 def test_cloned_queryset_delete(self): 256 """ 257 Make sure that a cloned queryset maintains the custom delete() 258 """ 259 to_delete = ('host1', 'host2') 260 261 for host in self.hosts: 262 self.assertFalse(host.invalid) 263 264 hosts = models.Host.objects.all().filter(hostname__in=to_delete) 265 hosts.delete() 266 all_hosts = models.Host.objects.all() 267 self.assertEqual(all_hosts.count(), len(self.hosts)) 268 269 for host in all_hosts: 270 if host.hostname in to_delete: 271 self.assertTrue( 272 host.invalid, 273 '%s.invalid expected to be True' % host.hostname) 274 else: 275 self.assertFalse( 276 host.invalid, 277 '%s.invalid expected to be False' % host.hostname) 278 279 280 def test_normal_delete(self): 281 job = self._create_job(hosts=[1]) 282 self.assertEqual(1, models.Job.objects.all().count()) 283 284 job.delete() 285 self.assertEqual(0, models.Job.objects.all().count()) 286 287 288 def test_normal_delete_queryset(self): 289 self._create_job(hosts=[1]) 290 self._create_job(hosts=[2]) 291 292 self.assertEqual(2, models.Job.objects.all().count()) 293 294 models.Job.objects.all().delete() 295 self.assertEqual(0, models.Job.objects.all().count()) 296 297 298class SerializationTest(unittest.TestCase, 299 frontend_test_utils.FrontendTestMixin): 300 def setUp(self): 301 self._frontend_common_setup(fill_data=False) 302 303 304 def tearDown(self): 305 self._frontend_common_teardown() 306 307 308 def _get_example_response(self): 309 return {'hosts': [{'aclgroup_set': [{'description': '', 310 'id': 1, 311 'name': 'Everyone', 312 'users': [{ 313 'access_level': 100, 314 'id': 1, 315 'login': 'autotest_system', 316 'reboot_after': 0, 317 'reboot_before': 1, 318 'show_experimental': False}]}], 319 'dirty': True, 320 'hostattribute_set': [], 321 'hostname': '100.107.2.163', 322 'id': 2, 323 'invalid': False, 324 'labels': [{'id': 7, 325 'invalid': False, 326 'kernel_config': '', 327 'name': 'power:battery', 328 'only_if_needed': False, 329 'platform': False}, 330 {'id': 9, 331 'invalid': False, 332 'kernel_config': '', 333 'name': 'hw_video_acc_h264', 334 'only_if_needed': False, 335 'platform': False}, 336 {'id': 10, 337 'invalid': False, 338 'kernel_config': '', 339 'name': 'hw_video_acc_enc_h264', 340 'only_if_needed': False, 341 'platform': False}, 342 {'id': 11, 343 'invalid': False, 344 'kernel_config': '', 345 'name': 'webcam', 346 'only_if_needed': False, 347 'platform': False}, 348 {'id': 12, 349 'invalid': False, 350 'kernel_config': '', 351 'name': 'touchpad', 352 'only_if_needed': False, 353 'platform': False}, 354 {'id': 13, 355 'invalid': False, 356 'kernel_config': '', 357 'name': 'spring', 358 'only_if_needed': False, 359 'platform': False}, 360 {'id': 14, 361 'invalid': False, 362 'kernel_config': '', 363 'name': 'board:daisy', 364 'only_if_needed': False, 365 'platform': True}, 366 {'id': 15, 367 'invalid': False, 368 'kernel_config': '', 369 'name': 'board_freq_mem:daisy_1.7GHz', 370 'only_if_needed': False, 371 'platform': False}, 372 {'id': 16, 373 'invalid': False, 374 'kernel_config': '', 375 'name': 'bluetooth', 376 'only_if_needed': False, 377 'platform': False}, 378 {'id': 17, 379 'invalid': False, 380 'kernel_config': '', 381 'name': 'gpu_family:mali', 382 'only_if_needed': False, 383 'platform': False}, 384 {'id': 19, 385 'invalid': False, 386 'kernel_config': '', 387 'name': 'ec:cros', 388 'only_if_needed': False, 389 'platform': False}, 390 {'id': 20, 391 'invalid': False, 392 'kernel_config': '', 393 'name': 'storage:mmc', 394 'only_if_needed': False, 395 'platform': False}, 396 {'id': 21, 397 'invalid': False, 398 'kernel_config': '', 399 'name': 'hw_video_acc_vp8', 400 'only_if_needed': False, 401 'platform': False}, 402 {'id': 22, 403 'invalid': False, 404 'kernel_config': '', 405 'name': 'video_glitch_detection', 406 'only_if_needed': False, 407 'platform': False}, 408 {'id': 23, 409 'invalid': False, 410 'kernel_config': '', 411 'name': 'pool:suites', 412 'only_if_needed': False, 413 'platform': False}, 414 {'id': 25, 415 'invalid': False, 416 'kernel_config': '', 417 'name': 'daisy-board-name', 418 'only_if_needed': False, 419 'platform': False}], 420 'leased': False, 421 'lock_reason': '', 422 'lock_time': None, 423 'locked': False, 424 'protection': 0, 425 'shard': {'hostname': '1', 'id': 1}, 426 'status': 'Ready', 427 'synch_id': None}], 428 'jobs': [{'control_file': 'some control file\n\n\n', 429 'control_type': 2, 430 'created_on': '2014-09-04T13:09:35', 431 'dependency_labels': [{'id': 14, 432 'invalid': False, 433 'kernel_config': '', 434 'name': 'board:daisy', 435 'only_if_needed': False, 436 'platform': True}, 437 {'id': 23, 438 'invalid': False, 439 'kernel_config': '', 440 'name': 'pool:suites', 441 'only_if_needed': False, 442 'platform': False}, 443 {'id': 25, 444 'invalid': False, 445 'kernel_config': '', 446 'name': 'daisy-board-name', 447 'only_if_needed': False, 448 'platform': False}], 449 'email_list': '', 450 'hostqueueentry_set': [{'aborted': False, 451 'active': False, 452 'complete': False, 453 'deleted': False, 454 'execution_subdir': '', 455 'finished_on': None, 456 'id': 5, 457 'meta_host': { 458 'id': 14, 459 'invalid': False, 460 'kernel_config': '', 461 'name': 'board:daisy', 462 'only_if_needed': False, 463 'platform': True}, 464 'host_id': None, 465 'started_on': None, 466 'status': 'Queued'}], 467 'id': 5, 468 'jobkeyval_set': [{'id': 10, 469 'job_id': 5, 470 'key': 'suite', 471 'value': 'dummy'}, 472 {'id': 11, 473 'job_id': 5, 474 'key': 'build', 475 'value': 'daisy-release'}, 476 {'id': 12, 477 'job_id': 5, 478 'key': 'experimental', 479 'value': 'False'}], 480 'max_runtime_hrs': 72, 481 'max_runtime_mins': 1440, 482 'name': 'daisy-experimental', 483 'owner': 'autotest', 484 'parse_failed_repair': True, 485 'priority': 40, 486 'reboot_after': 0, 487 'reboot_before': 1, 488 'run_reset': True, 489 'run_verify': False, 490 'shard': {'hostname': '1', 'id': 1}, 491 'synch_count': 1, 492 'test_retry': 0, 493 'timeout': 24, 494 'timeout_mins': 1440, 495 'require_ssp': None}, 496 {'control_file': 'some control file\n\n\n', 497 'control_type': 2, 498 'created_on': '2014-09-04T13:09:35', 499 'dependency_labels': [{'id': 14, 500 'invalid': False, 501 'kernel_config': '', 502 'name': 'board:daisy', 503 'only_if_needed': False, 504 'platform': True}, 505 {'id': 23, 506 'invalid': False, 507 'kernel_config': '', 508 'name': 'pool:suites', 509 'only_if_needed': False, 510 'platform': False}, 511 {'id': 25, 512 'invalid': False, 513 'kernel_config': '', 514 'name': 'daisy-board-name', 515 'only_if_needed': False, 516 'platform': False}], 517 'email_list': '', 518 'hostqueueentry_set': [{'aborted': False, 519 'active': False, 520 'complete': False, 521 'deleted': False, 522 'execution_subdir': '', 523 'finished_on': None, 524 'id': 7, 525 'meta_host': { 526 'id': 14, 527 'invalid': False, 528 'kernel_config': '', 529 'name': 'board:daisy', 530 'only_if_needed': False, 531 'platform': True}, 532 'host_id': None, 533 'started_on': None, 534 'status': 'Queued'}], 535 'id': 7, 536 'jobkeyval_set': [{'id': 16, 537 'job_id': 7, 538 'key': 'suite', 539 'value': 'dummy'}, 540 {'id': 17, 541 'job_id': 7, 542 'key': 'build', 543 'value': 'daisy-release'}, 544 {'id': 18, 545 'job_id': 7, 546 'key': 'experimental', 547 'value': 'False'}], 548 'max_runtime_hrs': 72, 549 'max_runtime_mins': 1440, 550 'name': 'daisy-experimental', 551 'owner': 'autotest', 552 'parse_failed_repair': True, 553 'priority': 40, 554 'reboot_after': 0, 555 'reboot_before': 1, 556 'run_reset': True, 557 'run_verify': False, 558 'shard': {'hostname': '1', 'id': 1}, 559 'synch_count': 1, 560 'test_retry': 0, 561 'timeout': 24, 562 'timeout_mins': 1440, 563 'require_ssp': None}]} 564 565 566 def test_response(self): 567 heartbeat_response = self._get_example_response() 568 hosts_serialized = heartbeat_response['hosts'] 569 jobs_serialized = heartbeat_response['jobs'] 570 571 # Persisting is automatically done inside deserialize 572 hosts = [models.Host.deserialize(host) for host in hosts_serialized] 573 jobs = [models.Job.deserialize(job) for job in jobs_serialized] 574 575 generated_heartbeat_response = { 576 'hosts': [host.serialize() for host in hosts], 577 'jobs': [job.serialize() for job in jobs] 578 } 579 example_response = self._get_example_response() 580 # For attribute-like objects, we don't care about its id. 581 for r in [generated_heartbeat_response, example_response]: 582 for job in r['jobs']: 583 for keyval in job['jobkeyval_set']: 584 keyval.pop('id') 585 for host in r['hosts']: 586 for attribute in host['hostattribute_set']: 587 keyval.pop('id') 588 self.assertEqual(generated_heartbeat_response, example_response) 589 590 591 def test_update(self): 592 job = self._create_job(hosts=[1]) 593 serialized = job.serialize(include_dependencies=False) 594 serialized['owner'] = 'some_other_owner' 595 596 job.update_from_serialized(serialized) 597 self.assertEqual(job.owner, 'some_other_owner') 598 599 serialized = job.serialize() 600 self.assertRaises( 601 ValueError, 602 job.update_from_serialized, serialized) 603 604 605 def test_sync_aborted(self): 606 job = self._create_job(hosts=[1]) 607 serialized = job.serialize() 608 609 serialized['hostqueueentry_set'][0]['aborted'] = True 610 serialized['hostqueueentry_set'][0]['status'] = 'Running' 611 612 models.Job.deserialize(serialized) 613 614 job = models.Job.objects.get(pk=job.id) 615 self.assertTrue(job.hostqueueentry_set.all()[0].aborted) 616 self.assertEqual(job.hostqueueentry_set.all()[0].status, 'Queued') 617 618 619if __name__ == '__main__': 620 unittest.main() 621