xref: /aosp_15_r20/external/autotest/frontend/afe/shard_heartbeat_unittest.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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()