1*9c5db199SXin Li# Copyright 2019 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Liimport contextlib 6*9c5db199SXin Liimport datetime 7*9c5db199SXin Liimport unittest 8*9c5db199SXin Li 9*9c5db199SXin Liimport common 10*9c5db199SXin Li# import has side-effects, must appear before any django imports. 11*9c5db199SXin Lifrom autotest_lib.frontend import setup_django_environment 12*9c5db199SXin Li 13*9c5db199SXin Lifrom autotest_lib.frontend import setup_test_environment 14*9c5db199SXin Lifrom autotest_lib.frontend.afe import models 15*9c5db199SXin Li 16*9c5db199SXin Li 17*9c5db199SXin Liclass ShardHeartbeatTest(unittest.TestCase): 18*9c5db199SXin Li def setUp(self): 19*9c5db199SXin Li self._tag_creator = _TagCreator('ShardHeartbeatTest') 20*9c5db199SXin Li setup_test_environment.set_up() 21*9c5db199SXin Li 22*9c5db199SXin Li 23*9c5db199SXin Li def tearDown(self): 24*9c5db199SXin Li setup_test_environment.tear_down() 25*9c5db199SXin Li 26*9c5db199SXin Li 27*9c5db199SXin Li def testJobsWithDepsIsFilteredByShardLabel(self): 28*9c5db199SXin Li label = self._create_label() 29*9c5db199SXin Li shard = self._create_shard(label) 30*9c5db199SXin Li job = self._create_job_with_label(label) 31*9c5db199SXin Li # Should not be assigned. 32*9c5db199SXin Li self._create_job_with_label(self._create_label()) 33*9c5db199SXin Li 34*9c5db199SXin Li assigned = models.Job.assign_to_shard(shard, []) 35*9c5db199SXin Li self.assertEqual(set(assigned), {job}) 36*9c5db199SXin Li 37*9c5db199SXin Li 38*9c5db199SXin Li def testJobsForHostsIsFilteredByShardLabel(self): 39*9c5db199SXin Li label = self._create_label() 40*9c5db199SXin Li shard = self._create_shard(label) 41*9c5db199SXin Li job = self._create_job_for_host(self._create_host(label)) 42*9c5db199SXin Li # Should not be assigned. 43*9c5db199SXin Li self._create_job_for_host(self._create_host(self._create_label())) 44*9c5db199SXin Li 45*9c5db199SXin Li assigned = models.Job.assign_to_shard(shard, []) 46*9c5db199SXin Li self.assertEqual(set(assigned), {job}) 47*9c5db199SXin Li 48*9c5db199SXin Li 49*9c5db199SXin Li def testJobsWithKnownIDsIsIgnored(self): 50*9c5db199SXin Li label = self._create_label() 51*9c5db199SXin Li shard = self._create_shard(label) 52*9c5db199SXin Li known_job = self._create_job_with_label(label) 53*9c5db199SXin Li new_job = self._create_job_with_label(label) 54*9c5db199SXin Li assigned_jobs = models.Job.assign_to_shard(shard, [known_job.id]) 55*9c5db199SXin Li self.assertEqual(set(assigned_jobs), {new_job}) 56*9c5db199SXin Li 57*9c5db199SXin Li 58*9c5db199SXin Li def testOldJobsAreIgnoredWhenOptionEnabled(self): 59*9c5db199SXin Li with self._ignore_jobs_older_than(2): 60*9c5db199SXin Li label = self._create_label() 61*9c5db199SXin Li shard = self._create_shard(label) 62*9c5db199SXin Li job = self._create_job_with_label(label) 63*9c5db199SXin Li # Should not be assigned. 64*9c5db199SXin Li self._create_job_with_label(label, datetime.timedelta(hours=3)) 65*9c5db199SXin Li assigned = models.Job.assign_to_shard(shard, []) 66*9c5db199SXin Li self.assertEqual(set(assigned), {job}) 67*9c5db199SXin Li 68*9c5db199SXin Li 69*9c5db199SXin Li def testOldJobsAreNotIgnoredWhenOptionDisabled(self): 70*9c5db199SXin Li with self._ignore_jobs_older_than(0): 71*9c5db199SXin Li label = self._create_label() 72*9c5db199SXin Li shard = self._create_shard(label) 73*9c5db199SXin Li job = self._create_job_with_label(label, 74*9c5db199SXin Li datetime.timedelta(hours=3)) 75*9c5db199SXin Li assigned = models.Job.assign_to_shard(shard, []) 76*9c5db199SXin Li self.assertEqual(set(assigned), {job}) 77*9c5db199SXin Li 78*9c5db199SXin Li 79*9c5db199SXin Li @contextlib.contextmanager 80*9c5db199SXin Li def _ignore_jobs_older_than(self, value): 81*9c5db199SXin Li old = models.Job.SKIP_JOBS_CREATED_BEFORE 82*9c5db199SXin Li try: 83*9c5db199SXin Li models.Job.SKIP_JOBS_CREATED_BEFORE = value 84*9c5db199SXin Li yield 85*9c5db199SXin Li finally: 86*9c5db199SXin Li models.Job.SKIP_JOBS_CREATED_BEFORE = old 87*9c5db199SXin Li 88*9c5db199SXin Li 89*9c5db199SXin Li def _create_job_for_host(self, host, pending_age=None): 90*9c5db199SXin Li """Create a job for the given host created pending_age ago. 91*9c5db199SXin Li 92*9c5db199SXin Li @param host: A models.Host object. 93*9c5db199SXin Li @param pending_age: A datetime.datetime object. 94*9c5db199SXin Li @return: A models.Job object. 95*9c5db199SXin Li """ 96*9c5db199SXin Li job = models.Job.objects.create( 97*9c5db199SXin Li name=self._tag_creator.next(), 98*9c5db199SXin Li created_on=_past_timestamp(pending_age), 99*9c5db199SXin Li ) 100*9c5db199SXin Li hqe = models.HostQueueEntry.objects.create( 101*9c5db199SXin Li job=job, 102*9c5db199SXin Li host_id=host.id, 103*9c5db199SXin Li status=models.HostQueueEntry.Status.QUEUED, 104*9c5db199SXin Li ) 105*9c5db199SXin Li return job 106*9c5db199SXin Li 107*9c5db199SXin Li 108*9c5db199SXin Li def _create_job_with_label(self, label, pending_age=None): 109*9c5db199SXin Li """Create a job for the given label created pending_age ago. 110*9c5db199SXin Li 111*9c5db199SXin Li @param host: A models.Label object. 112*9c5db199SXin Li @param pending_age: A datetime.datetime object. 113*9c5db199SXin Li @return: A models.Job object. 114*9c5db199SXin Li """ 115*9c5db199SXin Li job = models.Job.objects.create( 116*9c5db199SXin Li name=self._tag_creator.next(), 117*9c5db199SXin Li created_on=_past_timestamp(pending_age), 118*9c5db199SXin Li ) 119*9c5db199SXin Li job.dependency_labels.add(label) 120*9c5db199SXin Li hqe = models.HostQueueEntry.objects.create( 121*9c5db199SXin Li job=job, 122*9c5db199SXin Li meta_host_id=label.id, 123*9c5db199SXin Li status=models.HostQueueEntry.Status.QUEUED, 124*9c5db199SXin Li ) 125*9c5db199SXin Li return job 126*9c5db199SXin Li 127*9c5db199SXin Li 128*9c5db199SXin Li def _create_host(self, label): 129*9c5db199SXin Li """Create a models.Host with the given models.Label.""" 130*9c5db199SXin Li host = models.Host.objects.create(hostname=self._tag_creator.next()) 131*9c5db199SXin Li host.labels.add(label) 132*9c5db199SXin Li return host 133*9c5db199SXin Li 134*9c5db199SXin Li 135*9c5db199SXin Li def _create_label(self): 136*9c5db199SXin Li """Create a models.Label.""" 137*9c5db199SXin Li return models.Label.objects.create(name=self._tag_creator.next()) 138*9c5db199SXin Li 139*9c5db199SXin Li 140*9c5db199SXin Li def _create_shard(self, label): 141*9c5db199SXin Li """Create a models.Shard with the givem models.Label.""" 142*9c5db199SXin Li shard = models.Shard.objects.create(hostname=self._tag_creator.next()) 143*9c5db199SXin Li shard.labels.add(label) 144*9c5db199SXin Li return shard 145*9c5db199SXin Li 146*9c5db199SXin Li 147*9c5db199SXin Liclass _TagCreator(object): 148*9c5db199SXin Li """Create unique but deterministic str tags by calling next().""" 149*9c5db199SXin Li def __init__(self, prefix): 150*9c5db199SXin Li self._prefix = prefix 151*9c5db199SXin Li self._count = 0 152*9c5db199SXin Li 153*9c5db199SXin Li def next(self): 154*9c5db199SXin Li self._count += 1 155*9c5db199SXin Li return self._prefix + str(self._count) 156*9c5db199SXin Li 157*9c5db199SXin Li 158*9c5db199SXin Lidef _past_timestamp(delta=None): 159*9c5db199SXin Li """Compute datetime.datetime given timedelta in the past. 160*9c5db199SXin Li 161*9c5db199SXin Li @param delta: A datetime.timedelta object. 162*9c5db199SXin Li @return: A datetime.datetime object. 163*9c5db199SXin Li """ 164*9c5db199SXin Li now = datetime.datetime.now() 165*9c5db199SXin Li if delta is None: 166*9c5db199SXin Li return now 167*9c5db199SXin Li return now - delta 168*9c5db199SXin Li 169*9c5db199SXin Li 170*9c5db199SXin Liif __name__ == '__main__': 171*9c5db199SXin Li unittest.main()