1#!/usr/bin/python
2# pylint: disable=missing-docstring
3
4import unittest
5
6import common
7from autotest_lib.frontend import setup_django_environment
8from autotest_lib.frontend.afe import frontend_test_utils
9from autotest_lib.frontend.afe import models, model_logic
10
11
12class 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
43class 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
82class 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
141class 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
159class 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
231class 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
552if __name__ == '__main__':
553    unittest.main()
554