# Copyright 2019 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import contextlib import datetime import unittest import common # import has side-effects, must appear before any django imports. from autotest_lib.frontend import setup_django_environment from autotest_lib.frontend import setup_test_environment from autotest_lib.frontend.afe import models class ShardHeartbeatTest(unittest.TestCase): def setUp(self): self._tag_creator = _TagCreator('ShardHeartbeatTest') setup_test_environment.set_up() def tearDown(self): setup_test_environment.tear_down() def testJobsWithDepsIsFilteredByShardLabel(self): label = self._create_label() shard = self._create_shard(label) job = self._create_job_with_label(label) # Should not be assigned. self._create_job_with_label(self._create_label()) assigned = models.Job.assign_to_shard(shard, []) self.assertEqual(set(assigned), {job}) def testJobsForHostsIsFilteredByShardLabel(self): label = self._create_label() shard = self._create_shard(label) job = self._create_job_for_host(self._create_host(label)) # Should not be assigned. self._create_job_for_host(self._create_host(self._create_label())) assigned = models.Job.assign_to_shard(shard, []) self.assertEqual(set(assigned), {job}) def testJobsWithKnownIDsIsIgnored(self): label = self._create_label() shard = self._create_shard(label) known_job = self._create_job_with_label(label) new_job = self._create_job_with_label(label) assigned_jobs = models.Job.assign_to_shard(shard, [known_job.id]) self.assertEqual(set(assigned_jobs), {new_job}) def testOldJobsAreIgnoredWhenOptionEnabled(self): with self._ignore_jobs_older_than(2): label = self._create_label() shard = self._create_shard(label) job = self._create_job_with_label(label) # Should not be assigned. self._create_job_with_label(label, datetime.timedelta(hours=3)) assigned = models.Job.assign_to_shard(shard, []) self.assertEqual(set(assigned), {job}) def testOldJobsAreNotIgnoredWhenOptionDisabled(self): with self._ignore_jobs_older_than(0): label = self._create_label() shard = self._create_shard(label) job = self._create_job_with_label(label, datetime.timedelta(hours=3)) assigned = models.Job.assign_to_shard(shard, []) self.assertEqual(set(assigned), {job}) @contextlib.contextmanager def _ignore_jobs_older_than(self, value): old = models.Job.SKIP_JOBS_CREATED_BEFORE try: models.Job.SKIP_JOBS_CREATED_BEFORE = value yield finally: models.Job.SKIP_JOBS_CREATED_BEFORE = old def _create_job_for_host(self, host, pending_age=None): """Create a job for the given host created pending_age ago. @param host: A models.Host object. @param pending_age: A datetime.datetime object. @return: A models.Job object. """ job = models.Job.objects.create( name=self._tag_creator.next(), created_on=_past_timestamp(pending_age), ) hqe = models.HostQueueEntry.objects.create( job=job, host_id=host.id, status=models.HostQueueEntry.Status.QUEUED, ) return job def _create_job_with_label(self, label, pending_age=None): """Create a job for the given label created pending_age ago. @param host: A models.Label object. @param pending_age: A datetime.datetime object. @return: A models.Job object. """ job = models.Job.objects.create( name=self._tag_creator.next(), created_on=_past_timestamp(pending_age), ) job.dependency_labels.add(label) hqe = models.HostQueueEntry.objects.create( job=job, meta_host_id=label.id, status=models.HostQueueEntry.Status.QUEUED, ) return job def _create_host(self, label): """Create a models.Host with the given models.Label.""" host = models.Host.objects.create(hostname=self._tag_creator.next()) host.labels.add(label) return host def _create_label(self): """Create a models.Label.""" return models.Label.objects.create(name=self._tag_creator.next()) def _create_shard(self, label): """Create a models.Shard with the givem models.Label.""" shard = models.Shard.objects.create(hostname=self._tag_creator.next()) shard.labels.add(label) return shard class _TagCreator(object): """Create unique but deterministic str tags by calling next().""" def __init__(self, prefix): self._prefix = prefix self._count = 0 def next(self): self._count += 1 return self._prefix + str(self._count) def _past_timestamp(delta=None): """Compute datetime.datetime given timedelta in the past. @param delta: A datetime.timedelta object. @return: A datetime.datetime object. """ now = datetime.datetime.now() if delta is None: return now return now - delta if __name__ == '__main__': unittest.main()