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