398 lines
18 KiB
Python
Executable file
398 lines
18 KiB
Python
Executable file
#!/usr/bin/python
|
|
#pylint: disable-msg=C0111
|
|
|
|
# Copyright (c) 2014 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 mock
|
|
import unittest
|
|
|
|
import common
|
|
from autotest_lib.frontend import setup_django_environment
|
|
from autotest_lib.frontend.afe import frontend_test_utils
|
|
from autotest_lib.frontend.afe import models
|
|
from autotest_lib.server.cros.dynamic_suite import constants
|
|
from autotest_lib.scheduler import host_scheduler
|
|
from autotest_lib.scheduler import monitor_db
|
|
from autotest_lib.scheduler import rdb
|
|
from autotest_lib.scheduler import rdb_lib
|
|
from autotest_lib.scheduler import rdb_testing_utils
|
|
from autotest_lib.scheduler import scheduler_models
|
|
|
|
|
|
class QueryManagerTests(rdb_testing_utils.AbstractBaseRDBTester,
|
|
unittest.TestCase):
|
|
"""Verify scheduler behavior when pending jobs are already given hosts."""
|
|
|
|
_config_section = 'AUTOTEST_WEB'
|
|
|
|
|
|
def testPendingQueueEntries(self):
|
|
"""Test retrieval of pending queue entries."""
|
|
job = self.create_job(deps=set(['a']))
|
|
|
|
# Check that we don't pull the job we just created with only_hostless.
|
|
jobs_with_hosts = self.job_query_manager.get_pending_queue_entries(
|
|
only_hostless=True)
|
|
self.assertTrue(len(jobs_with_hosts) == 0)
|
|
|
|
# Check that only_hostless=False pulls new jobs, as always.
|
|
jobs_without_hosts = self.job_query_manager.get_pending_queue_entries(
|
|
only_hostless=False)
|
|
self.assertTrue(jobs_without_hosts[0].id == job.id and
|
|
jobs_without_hosts[0].host_id is None)
|
|
|
|
|
|
def testPendingQueueEntriesForShard(self):
|
|
"""Test queue entries for shards aren't executed by master scheduler"""
|
|
job1 = self.create_job(deps=set(['a']))
|
|
job2 = self.create_job(deps=set(['b']))
|
|
shard = models.Shard.objects.create()
|
|
# Assign the job's label to a shard
|
|
shard.labels.add(job1.dependency_labels.all()[0])
|
|
|
|
# Check that we only pull jobs which are not assigned to a shard.
|
|
jobs_with_hosts = self.job_query_manager.get_pending_queue_entries()
|
|
self.assertTrue(len(jobs_with_hosts) == 1)
|
|
self.assertEqual(jobs_with_hosts[0].id, job2.id)
|
|
|
|
|
|
def testHostQueries(self):
|
|
"""Verify that the host query manager maintains its data structures."""
|
|
# Create a job and use the host_query_managers internal datastructures
|
|
# to retrieve its job info.
|
|
job = self.create_job(
|
|
deps=rdb_testing_utils.DEFAULT_DEPS,
|
|
acls=rdb_testing_utils.DEFAULT_ACLS)
|
|
queue_entries = self._dispatcher._refresh_pending_queue_entries()
|
|
job_manager = rdb_lib.JobQueryManager(queue_entries)
|
|
job_info = job_manager.get_job_info(queue_entries[0])
|
|
default_dep_ids = set([label.id for label in self.db_helper.get_labels(
|
|
name__in=rdb_testing_utils.DEFAULT_DEPS)])
|
|
default_acl_ids = set([acl.id for acl in self.db_helper.get_acls(
|
|
name__in=rdb_testing_utils.DEFAULT_ACLS)])
|
|
self.assertTrue(set(job_info['deps']) == default_dep_ids)
|
|
self.assertTrue(set(job_info['acls']) == default_acl_ids)
|
|
|
|
|
|
def testNewJobsWithHosts(self):
|
|
"""Test that we handle inactive hqes with unleased hosts correctly."""
|
|
# Create a job and assign it an unleased host, then check that the
|
|
# HQE becomes active and the host remains assigned to it.
|
|
job = self.create_job(deps=['a'])
|
|
host = self.db_helper.create_host('h1', deps=['a'])
|
|
self.db_helper.add_host_to_job(host, job.id)
|
|
|
|
queue_entries = self._dispatcher._refresh_pending_queue_entries()
|
|
self._dispatcher._schedule_new_jobs()
|
|
|
|
host = self.db_helper.get_host(hostname='h1')[0]
|
|
self.assertTrue(host.leased == True and
|
|
host.status == models.Host.Status.READY)
|
|
hqes = list(self.db_helper.get_hqes(host_id=host.id))
|
|
self.assertTrue(len(hqes) == 1 and hqes[0].active and
|
|
hqes[0].status == models.HostQueueEntry.Status.QUEUED)
|
|
|
|
|
|
def testNewJobsWithInvalidHost(self):
|
|
"""Test handling of inactive hqes assigned invalid, unleased hosts."""
|
|
# Create a job and assign it an unleased host, then check that the
|
|
# HQE becomes DOES NOT become active, because we validate the
|
|
# assignment again.
|
|
job = self.create_job(deps=['a'])
|
|
host = self.db_helper.create_host('h1', deps=['b'])
|
|
self.db_helper.add_host_to_job(host, job.id)
|
|
|
|
queue_entries = self._dispatcher._refresh_pending_queue_entries()
|
|
self._dispatcher._schedule_new_jobs()
|
|
|
|
host = self.db_helper.get_host(hostname='h1')[0]
|
|
self.assertTrue(host.leased == False and
|
|
host.status == models.Host.Status.READY)
|
|
hqes = list(self.db_helper.get_hqes(host_id=host.id))
|
|
self.assertTrue(len(hqes) == 1 and not hqes[0].active and
|
|
hqes[0].status == models.HostQueueEntry.Status.QUEUED)
|
|
|
|
|
|
def testNewJobsWithLeasedHost(self):
|
|
"""Test handling of inactive hqes assigned leased hosts."""
|
|
# Create a job and assign it a leased host, then check that the
|
|
# HQE does not become active through the scheduler, and that the
|
|
# host gets released.
|
|
job = self.create_job(deps=['a'])
|
|
host = self.db_helper.create_host('h1', deps=['b'])
|
|
self.db_helper.add_host_to_job(host, job.id)
|
|
host.leased = 1
|
|
host.save()
|
|
|
|
rdb.batch_acquire_hosts = mock.MagicMock()
|
|
queue_entries = self._dispatcher._refresh_pending_queue_entries()
|
|
self._dispatcher._schedule_new_jobs()
|
|
self.assertTrue(rdb.batch_acquire_hosts.call_count == 0)
|
|
host = self.db_helper.get_host(hostname='h1')[0]
|
|
self.assertTrue(host.leased == True and
|
|
host.status == models.Host.Status.READY)
|
|
hqes = list(self.db_helper.get_hqes(host_id=host.id))
|
|
self.assertTrue(len(hqes) == 1 and not hqes[0].active and
|
|
hqes[0].status == models.HostQueueEntry.Status.QUEUED)
|
|
self.host_scheduler._release_hosts()
|
|
self.assertTrue(self.db_helper.get_host(hostname='h1')[0].leased == 0)
|
|
|
|
|
|
def testSpecialTaskOrdering(self):
|
|
"""Test priority ordering of special tasks."""
|
|
|
|
# Create 2 special tasks, one with and one without an hqe.
|
|
# Activate the hqe and make sure it gets scheduled before the other.
|
|
host = self.db_helper.create_host('h1', deps=['a'])
|
|
job1 = self.create_job(deps=['a'])
|
|
self.db_helper.add_host_to_job(host, job1.id)
|
|
task1 = self.db_helper.create_special_task(job1.id)
|
|
hqe = self.db_helper.get_hqes(job=job1.id)[0]
|
|
|
|
# This task has no queue entry.
|
|
task2 = self.db_helper.create_special_task(host_id=host.id)
|
|
|
|
# Since the hqe task isn't active we get both back.
|
|
tasks = self.job_query_manager.get_prioritized_special_tasks()
|
|
self.assertTrue(tasks[1].queue_entry_id is None and
|
|
tasks[0].queue_entry_id == hqe.id)
|
|
|
|
# Activate the hqe and make sure the frontned task isn't returned.
|
|
self.db_helper.update_hqe(hqe.id, active=True)
|
|
tasks = self.job_query_manager.get_prioritized_special_tasks()
|
|
self.assertTrue(tasks[0].id == task1.id)
|
|
|
|
|
|
class HostSchedulerTests(rdb_testing_utils.AbstractBaseRDBTester,
|
|
unittest.TestCase):
|
|
"""Verify scheduler behavior when pending jobs are already given hosts."""
|
|
|
|
_config_section = 'AUTOTEST_WEB'
|
|
|
|
|
|
def setUp(self):
|
|
super(HostSchedulerTests, self).setUp()
|
|
self.host_scheduler = host_scheduler.HostScheduler()
|
|
|
|
|
|
def testSpecialTaskLocking(self):
|
|
"""Test that frontend special tasks lock hosts."""
|
|
# Create multiple tasks with hosts and make sure the hosts get locked.
|
|
host = self.db_helper.create_host('h')
|
|
host1 = self.db_helper.create_host('h1')
|
|
task = self.db_helper.create_special_task(host_id=host.id)
|
|
task1 = self.db_helper.create_special_task(host_id=host1.id)
|
|
self.host_scheduler._lease_hosts_of_frontend_tasks()
|
|
self.assertTrue(self.db_helper.get_host(hostname='h')[0].leased == 1 and
|
|
self.db_helper.get_host(hostname='h1')[0].leased == 1)
|
|
|
|
|
|
def testJobScheduling(self):
|
|
"""Test new host acquisitions."""
|
|
# Create a job that will find a host through the host scheduler, and
|
|
# make sure the hqe is activated, and a special task is created.
|
|
job = self.create_job(deps=set(['a']))
|
|
host = self.db_helper.create_host('h1', deps=set(['a']))
|
|
self.host_scheduler._schedule_jobs()
|
|
hqe = self.db_helper.get_hqes(job_id=job.id)[0]
|
|
self.assertTrue(hqe.active and hqe.host_id == host.id and
|
|
hqe.status == models.HostQueueEntry.Status.QUEUED)
|
|
task = self.db_helper.get_tasks(queue_entry_id=hqe.id)[0]
|
|
self.assertTrue(task.is_active == 0 and task.host_id == host.id)
|
|
|
|
|
|
def _check_agent_invariants(self, host, agent):
|
|
host_agents = list(self._dispatcher._host_agents[host.id])
|
|
self.assertTrue(len(host_agents) == 1)
|
|
self.assertTrue(host_agents[0].task.task.id == agent.id)
|
|
return host_agents[0]
|
|
|
|
|
|
def testLeasedFrontendTaskHost(self):
|
|
"""Check that we don't scheduler a special task on an unleased host."""
|
|
# Create a special task without an hqe and make sure it isn't returned
|
|
# for scheduling till its host is leased.
|
|
host = self.db_helper.create_host('h1', deps=['a'])
|
|
task = self.db_helper.create_special_task(host_id=host.id)
|
|
|
|
tasks = self.job_query_manager.get_prioritized_special_tasks(
|
|
only_tasks_with_leased_hosts=True)
|
|
self.assertTrue(tasks == [])
|
|
tasks = self.job_query_manager.get_prioritized_special_tasks(
|
|
only_tasks_with_leased_hosts=False)
|
|
self.assertTrue(tasks[0].id == task.id)
|
|
self.host_scheduler._lease_hosts_of_frontend_tasks()
|
|
tasks = self.job_query_manager.get_prioritized_special_tasks(
|
|
only_tasks_with_leased_hosts=True)
|
|
self.assertTrue(tasks[0].id == task.id)
|
|
|
|
|
|
def testTickLockStep(self):
|
|
"""Check that a frontend task and an hqe never run simultaneously."""
|
|
|
|
self.god.stub_with(monitor_db, '_inline_host_acquisition', False)
|
|
|
|
# Create a frontend special task against a host.
|
|
host = self.db_helper.create_host('h1', deps=set(['a']))
|
|
frontend_task = self.db_helper.create_special_task(host_id=host.id)
|
|
self._dispatcher._schedule_special_tasks()
|
|
# The frontend special task shouldn't get scheduled on the host till
|
|
# the host is leased.
|
|
self.assertFalse(self._dispatcher.host_has_agent(host))
|
|
|
|
# Create a job for the same host and make the host scheduler lease the
|
|
# host out to that job.
|
|
job = self.create_job(deps=set(['a']))
|
|
self.host_scheduler._schedule_jobs()
|
|
hqe = self.db_helper.get_hqes(job_id=job.id)[0]
|
|
tasks = self.job_query_manager.get_prioritized_special_tasks(
|
|
only_tasks_with_leased_hosts=True)
|
|
# We should not find the frontend special task, even though its host is
|
|
# now leased, because its leased by an active hqe.
|
|
self.assertTrue(len(tasks) == 1 and tasks[0].queue_entry_id == hqe.id)
|
|
self._dispatcher._schedule_special_tasks()
|
|
self.assertTrue(self._dispatcher.host_has_agent(host))
|
|
|
|
# Deactivate the hqe task and make sure the frontend task gets the host.
|
|
task = tasks[0]
|
|
self._dispatcher.remove_agent(self._check_agent_invariants(host, task))
|
|
task.is_complete = 1
|
|
task.is_active = 0
|
|
task.save()
|
|
self.db_helper.update_hqe(hqe.id, active=False)
|
|
self._dispatcher._schedule_special_tasks()
|
|
self.assertTrue(self._dispatcher.host_has_agent(host))
|
|
self._check_agent_invariants(host, frontend_task)
|
|
|
|
# Make sure we don't release the host being used by the incomplete task.
|
|
self.host_scheduler._release_hosts()
|
|
host = self.db_helper.get_host(hostname='h1')[0]
|
|
self.assertTrue(host.leased == True)
|
|
|
|
|
|
class SuiteRecorderTest(rdb_testing_utils.AbstractBaseRDBTester,
|
|
unittest.TestCase):
|
|
"""Test the functionality of SuiteRecorder"""
|
|
|
|
_config_section = 'AUTOTEST_WEB'
|
|
|
|
def testGetSuiteHostAssignment(self):
|
|
"""Test the initialization of SuiteRecord."""
|
|
hosts = []
|
|
num = 4
|
|
for i in range (0, num):
|
|
hosts.append(self.db_helper.create_host(
|
|
'h%d' % i, deps=set(['board:lumpy'])))
|
|
single_job = self.create_job(deps=set(['a']))
|
|
jobs_1 = self.create_suite(num=2, board='board:lumpy')
|
|
jobs_2 = self.create_suite(num=2, board='board:lumpy')
|
|
# We have 4 hosts, 5 jobs, one job in the second suite won't
|
|
# get a host.
|
|
all_jobs = ([single_job] +
|
|
[jobs_1[k] for k in jobs_1 if k !='parent_job'] +
|
|
[jobs_2[k] for k in jobs_2 if k !='parent_job'])
|
|
for i in range(0, num):
|
|
self.db_helper.add_host_to_job(hosts[i], all_jobs[i].id,
|
|
activate=True)
|
|
r = host_scheduler.SuiteRecorder(self.job_query_manager)
|
|
self.assertEqual(r.suite_host_num,
|
|
{jobs_1['parent_job'].id:2,
|
|
jobs_2['parent_job'].id:1})
|
|
self.assertEqual(r.hosts_to_suites,
|
|
{hosts[1].id: jobs_1['parent_job'].id,
|
|
hosts[2].id: jobs_1['parent_job'].id,
|
|
hosts[3].id: jobs_2['parent_job'].id})
|
|
|
|
|
|
def verify_state(self, recorder, suite_host_num, hosts_to_suites):
|
|
"""Verify the suite, host information held by SuiteRecorder.
|
|
|
|
@param recorder: A SuiteRecorder object.
|
|
@param suite_host_num: a dict, expected value of suite_host_num.
|
|
@param hosts_to_suites: a dict, expected value of hosts_to_suites.
|
|
"""
|
|
self.assertEqual(recorder.suite_host_num, suite_host_num)
|
|
self.assertEqual(recorder.hosts_to_suites, hosts_to_suites)
|
|
|
|
|
|
def assign_host_to_job(self, host, job, recorder=None):
|
|
"""A helper function that adds a host to a job and record it.
|
|
|
|
@param host: A Host object.
|
|
@param job: A Job object.
|
|
@param recorder: A SuiteRecorder object to record the assignment.
|
|
|
|
@return a HostQueueEntry object that binds the host and job together.
|
|
"""
|
|
self.db_helper.add_host_to_job(host, job)
|
|
hqe = scheduler_models.HostQueueEntry.fetch(where='job_id=%s',
|
|
params=(job.id,))[0]
|
|
if recorder:
|
|
recorder.record_assignment(hqe)
|
|
return hqe
|
|
|
|
|
|
def testRecordAssignmentAndRelease(self):
|
|
"""Test when a host is assigned to suite"""
|
|
r = host_scheduler.SuiteRecorder(self.job_query_manager)
|
|
self.verify_state(r, {}, {})
|
|
host1 = self.db_helper.create_host('h1')
|
|
host2 = self.db_helper.create_host('h2')
|
|
jobs = self.create_suite(num=2)
|
|
hqe = scheduler_models.HostQueueEntry.fetch(where='job_id=%s',
|
|
params=(jobs[0].id,))[0]
|
|
# HQE got a host.
|
|
hqe = self.assign_host_to_job(host1, jobs[0], r)
|
|
self.verify_state(r, {jobs['parent_job'].id:1},
|
|
{host1.id: jobs['parent_job'].id})
|
|
# Tried to call record_assignment again, nothing should happen.
|
|
r.record_assignment(hqe)
|
|
self.verify_state(r, {jobs['parent_job'].id:1},
|
|
{host1.id: jobs['parent_job'].id})
|
|
# Second hqe got a host
|
|
self.assign_host_to_job(host2, jobs[1], r)
|
|
self.verify_state(r, {jobs['parent_job'].id:2},
|
|
{host1.id: jobs['parent_job'].id,
|
|
host2.id: jobs['parent_job'].id})
|
|
# Release host1
|
|
r.record_release([host1])
|
|
self.verify_state(r, {jobs['parent_job'].id:1},
|
|
{host2.id: jobs['parent_job'].id})
|
|
# Release host2
|
|
r.record_release([host2])
|
|
self.verify_state(r, {}, {})
|
|
|
|
|
|
def testGetMinDuts(self):
|
|
"""Test get min dut for suite."""
|
|
host1 = self.db_helper.create_host('h1')
|
|
host2 = self.db_helper.create_host('h2')
|
|
host3 = self.db_helper.create_host('h3')
|
|
jobs = self.create_suite(num=3)
|
|
pid = jobs['parent_job'].id
|
|
# Set min_dut=1 for the suite as a job keyval.
|
|
keyval = models.JobKeyval(
|
|
job_id=pid, key=constants.SUITE_MIN_DUTS_KEY, value=2)
|
|
keyval.save()
|
|
r = host_scheduler.SuiteRecorder(self.job_query_manager)
|
|
# Not job has got any host, min dut to request should equal to what's
|
|
# specified in the job keyval.
|
|
self.assertEqual(r.get_min_duts([pid]), {pid: 2})
|
|
self.assign_host_to_job(host1, jobs[0], r)
|
|
self.assertEqual(r.get_min_duts([pid]), {pid: 1})
|
|
self.assign_host_to_job(host2, jobs[1], r)
|
|
self.assertEqual(r.get_min_duts([pid]), {pid: 0})
|
|
self.assign_host_to_job(host3, jobs[2], r)
|
|
self.assertEqual(r.get_min_duts([pid]), {pid: 0})
|
|
r.record_release([host1])
|
|
self.assertEqual(r.get_min_duts([pid]), {pid: 0})
|
|
r.record_release([host2])
|
|
self.assertEqual(r.get_min_duts([pid]), {pid: 1})
|
|
r.record_release([host3])
|
|
self.assertEqual(r.get_min_duts([pid]), {pid: 2})
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|
|
|