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