blob: 723c31eb6c55993c2f50879a86eefb346c3f1800 [file] [log] [blame]
showardce38e0c2008-05-29 19:36:16 +00001#!/usr/bin/python
2
showard8fe93b52008-11-18 17:53:22 +00003import unittest, time, subprocess, os, StringIO, tempfile, datetime, shutil
showard6157c632009-07-06 20:19:31 +00004import logging
showardce38e0c2008-05-29 19:36:16 +00005import common
mbligh8bcd23a2009-02-03 19:14:06 +00006import MySQLdb
showard364fe862008-10-17 02:01:16 +00007from autotest_lib.frontend import setup_django_environment
showardb6d16622009-05-26 19:35:29 +00008from autotest_lib.frontend.afe import frontend_test_utils
jadmanskifb7cfb12008-07-09 14:13:21 +00009from autotest_lib.client.common_lib import global_config, host_protections
jadmanski3d161b02008-06-06 15:43:36 +000010from autotest_lib.client.common_lib.test_utils import mock
showard442e71e2008-10-06 10:05:20 +000011from autotest_lib.database import database_connection, migrate
showard21baa452008-10-21 00:08:39 +000012from autotest_lib.frontend import thread_local
showardb1e51872008-10-07 11:08:18 +000013from autotest_lib.frontend.afe import models
showard170873e2009-01-07 00:22:26 +000014from autotest_lib.scheduler import monitor_db, drone_manager, email_manager
showardd1ee1dd2009-01-07 21:33:08 +000015from autotest_lib.scheduler import scheduler_config
showardce38e0c2008-05-29 19:36:16 +000016
17_DEBUG = False
18
showarda3c58572009-03-12 20:36:59 +000019
showard9bb960b2009-11-19 01:02:11 +000020class DummyAgentTask(object):
showardd1195652009-12-08 22:21:02 +000021 num_processes = 1
22 owner_username = 'my_user'
showard9bb960b2009-11-19 01:02:11 +000023
24
showard170873e2009-01-07 00:22:26 +000025class DummyAgent(object):
showard8cc058f2009-09-08 16:26:33 +000026 started = False
showard170873e2009-01-07 00:22:26 +000027 _is_done = False
showardd1195652009-12-08 22:21:02 +000028 host_ids = ()
29 queue_entry_ids = ()
30
31 def __init__(self):
32 self.task = DummyAgentTask()
showard170873e2009-01-07 00:22:26 +000033
showard170873e2009-01-07 00:22:26 +000034
35 def tick(self):
showard8cc058f2009-09-08 16:26:33 +000036 self.started = True
showard170873e2009-01-07 00:22:26 +000037
38
39 def is_done(self):
40 return self._is_done
41
42
43 def set_done(self, done):
44 self._is_done = done
showard04c82c52008-05-29 19:38:12 +000045
showard56193bb2008-08-13 20:07:41 +000046
47class IsRow(mock.argument_comparator):
48 def __init__(self, row_id):
49 self.row_id = row_id
showardce38e0c2008-05-29 19:36:16 +000050
51
showard56193bb2008-08-13 20:07:41 +000052 def is_satisfied_by(self, parameter):
53 return list(parameter)[0] == self.row_id
54
55
56 def __str__(self):
57 return 'row with id %s' % self.row_id
58
59
showardd3dc1992009-04-22 21:01:40 +000060class IsAgentWithTask(mock.argument_comparator):
mbligh1ef218d2009-08-03 16:57:56 +000061 def __init__(self, task):
62 self._task = task
showardd3dc1992009-04-22 21:01:40 +000063
64
mbligh1ef218d2009-08-03 16:57:56 +000065 def is_satisfied_by(self, parameter):
66 if not isinstance(parameter, monitor_db.Agent):
67 return False
68 tasks = list(parameter.queue.queue)
69 if len(tasks) != 1:
70 return False
71 return tasks[0] == self._task
showardd3dc1992009-04-22 21:01:40 +000072
73
showard6b733412009-04-27 20:09:18 +000074def _set_host_and_qe_ids(agent_or_task, id_list=None):
75 if id_list is None:
76 id_list = []
77 agent_or_task.host_ids = agent_or_task.queue_entry_ids = id_list
78
79
showardb6d16622009-05-26 19:35:29 +000080class BaseSchedulerTest(unittest.TestCase,
81 frontend_test_utils.FrontendTestMixin):
showard50c0e712008-09-22 16:20:37 +000082 _config_section = 'AUTOTEST_WEB'
showardce38e0c2008-05-29 19:36:16 +000083
jadmanski0afbb632008-06-06 21:10:57 +000084 def _do_query(self, sql):
showardb1e51872008-10-07 11:08:18 +000085 self._database.execute(sql)
showardce38e0c2008-05-29 19:36:16 +000086
87
showardb6d16622009-05-26 19:35:29 +000088 def _set_monitor_stubs(self):
89 # Clear the instance cache as this is a brand new database.
90 monitor_db.DBObject._clear_instance_cache()
showardce38e0c2008-05-29 19:36:16 +000091
showardb1e51872008-10-07 11:08:18 +000092 self._database = (
93 database_connection.DatabaseConnection.get_test_database(
94 self._test_db_file))
95 self._database.connect()
96 self._database.debug = _DEBUG
showardce38e0c2008-05-29 19:36:16 +000097
showardb1e51872008-10-07 11:08:18 +000098 monitor_db._db = self._database
showard170873e2009-01-07 00:22:26 +000099 monitor_db._drone_manager._results_dir = '/test/path'
100 monitor_db._drone_manager._temporary_directory = '/test/path/tmp'
showard56193bb2008-08-13 20:07:41 +0000101
102
showard56193bb2008-08-13 20:07:41 +0000103 def setUp(self):
showardb6d16622009-05-26 19:35:29 +0000104 self._frontend_common_setup()
showard56193bb2008-08-13 20:07:41 +0000105 self._set_monitor_stubs()
106 self._dispatcher = monitor_db.Dispatcher()
showardce38e0c2008-05-29 19:36:16 +0000107
108
showard56193bb2008-08-13 20:07:41 +0000109 def tearDown(self):
showardb6d16622009-05-26 19:35:29 +0000110 self._database.disconnect()
111 self._frontend_common_teardown()
showardce38e0c2008-05-29 19:36:16 +0000112
113
showard56193bb2008-08-13 20:07:41 +0000114 def _update_hqe(self, set, where=''):
115 query = 'UPDATE host_queue_entries SET ' + set
116 if where:
117 query += ' WHERE ' + where
118 self._do_query(query)
119
120
showarda3c58572009-03-12 20:36:59 +0000121class DBObjectTest(BaseSchedulerTest):
122 # It may seem odd to subclass BaseSchedulerTest for this but it saves us
123 # duplicating some setup work for what we want to test.
124
125
126 def test_compare_fields_in_row(self):
127 host = monitor_db.Host(id=1)
128 fields = list(host._fields)
129 row_data = [getattr(host, fieldname) for fieldname in fields]
130 self.assertEqual({}, host._compare_fields_in_row(row_data))
131 row_data[fields.index('hostname')] = 'spam'
132 self.assertEqual({'hostname': ('host1', 'spam')},
133 host._compare_fields_in_row(row_data))
134 row_data[fields.index('id')] = 23
135 self.assertEqual({'hostname': ('host1', 'spam'), 'id': (1, 23)},
136 host._compare_fields_in_row(row_data))
137
138
showarddae680a2009-10-12 20:26:43 +0000139 def test_compare_fields_in_row_datetime_ignores_microseconds(self):
140 datetime_with_us = datetime.datetime(2009, 10, 07, 12, 34, 56, 7890)
141 datetime_without_us = datetime.datetime(2009, 10, 07, 12, 34, 56, 0)
142 class TestTable(monitor_db.DBObject):
143 _table_name = 'test_table'
144 _fields = ('id', 'test_datetime')
145 tt = TestTable(row=[1, datetime_without_us])
146 self.assertEqual({}, tt._compare_fields_in_row([1, datetime_with_us]))
147
148
showarda3c58572009-03-12 20:36:59 +0000149 def test_always_query(self):
150 host_a = monitor_db.Host(id=2)
151 self.assertEqual(host_a.hostname, 'host2')
152 self._do_query('UPDATE hosts SET hostname="host2-updated" WHERE id=2')
153 host_b = monitor_db.Host(id=2, always_query=True)
154 self.assert_(host_a is host_b, 'Cached instance not returned.')
155 self.assertEqual(host_a.hostname, 'host2-updated',
156 'Database was not re-queried')
157
158 # If either of these are called, a query was made when it shouldn't be.
159 host_a._compare_fields_in_row = lambda _: self.fail('eek! a query!')
showard12f3e322009-05-13 21:27:42 +0000160 host_a._update_fields_from_row = host_a._compare_fields_in_row
showarda3c58572009-03-12 20:36:59 +0000161 host_c = monitor_db.Host(id=2, always_query=False)
162 self.assert_(host_a is host_c, 'Cached instance not returned')
163
164
165 def test_delete(self):
166 host = monitor_db.Host(id=3)
167 host.delete()
168 host = self.assertRaises(monitor_db.DBError, monitor_db.Host, id=3,
169 always_query=False)
170 host = self.assertRaises(monitor_db.DBError, monitor_db.Host, id=3,
171 always_query=True)
172
showard76e29d12009-04-15 21:53:10 +0000173 def test_save(self):
174 # Dummy Job to avoid creating a one in the HostQueueEntry __init__.
175 class MockJob(object):
176 def __init__(self, id):
177 pass
178 def tag(self):
179 return 'MockJob'
180 self.god.stub_with(monitor_db, 'Job', MockJob)
181 hqe = monitor_db.HostQueueEntry(
182 new_record=True,
showard12f3e322009-05-13 21:27:42 +0000183 row=[0, 1, 2, 'Queued', None, 0, 0, 0, '.', None, False, None])
showard76e29d12009-04-15 21:53:10 +0000184 hqe.save()
185 new_id = hqe.id
186 # Force a re-query and verify that the correct data was stored.
187 monitor_db.DBObject._clear_instance_cache()
188 hqe = monitor_db.HostQueueEntry(id=new_id)
189 self.assertEqual(hqe.id, new_id)
190 self.assertEqual(hqe.job_id, 1)
191 self.assertEqual(hqe.host_id, 2)
192 self.assertEqual(hqe.status, 'Queued')
193 self.assertEqual(hqe.meta_host, None)
194 self.assertEqual(hqe.active, False)
195 self.assertEqual(hqe.complete, False)
196 self.assertEqual(hqe.deleted, False)
197 self.assertEqual(hqe.execution_subdir, '.')
198 self.assertEqual(hqe.atomic_group_id, None)
showard12f3e322009-05-13 21:27:42 +0000199 self.assertEqual(hqe.started_on, None)
showarda3c58572009-03-12 20:36:59 +0000200
201
showardb2e2c322008-10-14 17:33:55 +0000202class DispatcherSchedulingTest(BaseSchedulerTest):
showard56193bb2008-08-13 20:07:41 +0000203 _jobs_scheduled = []
204
showard89f84db2009-03-12 20:39:13 +0000205
206 def tearDown(self):
207 super(DispatcherSchedulingTest, self).tearDown()
208
209
showard56193bb2008-08-13 20:07:41 +0000210 def _set_monitor_stubs(self):
211 super(DispatcherSchedulingTest, self)._set_monitor_stubs()
showard89f84db2009-03-12 20:39:13 +0000212
showard8cc058f2009-09-08 16:26:33 +0000213 def hqe__do_schedule_pre_job_tasks_stub(queue_entry):
214 """Called by HostQueueEntry.run()."""
showard77182562009-06-10 00:16:05 +0000215 self._record_job_scheduled(queue_entry.job.id, queue_entry.host.id)
showard89f84db2009-03-12 20:39:13 +0000216 queue_entry.set_status('Starting')
showard89f84db2009-03-12 20:39:13 +0000217
showard8cc058f2009-09-08 16:26:33 +0000218 self.god.stub_with(monitor_db.HostQueueEntry,
219 '_do_schedule_pre_job_tasks',
220 hqe__do_schedule_pre_job_tasks_stub)
showard89f84db2009-03-12 20:39:13 +0000221
222 def hqe_queue_log_record_stub(self, log_line):
223 """No-Op to avoid calls down to the _drone_manager during tests."""
224
225 self.god.stub_with(monitor_db.HostQueueEntry, 'queue_log_record',
226 hqe_queue_log_record_stub)
showard56193bb2008-08-13 20:07:41 +0000227
228
229 def _record_job_scheduled(self, job_id, host_id):
230 record = (job_id, host_id)
231 self.assert_(record not in self._jobs_scheduled,
232 'Job %d scheduled on host %d twice' %
233 (job_id, host_id))
234 self._jobs_scheduled.append(record)
235
236
237 def _assert_job_scheduled_on(self, job_id, host_id):
238 record = (job_id, host_id)
239 self.assert_(record in self._jobs_scheduled,
240 'Job %d not scheduled on host %d as expected\n'
241 'Jobs scheduled: %s' %
242 (job_id, host_id, self._jobs_scheduled))
243 self._jobs_scheduled.remove(record)
244
245
showard89f84db2009-03-12 20:39:13 +0000246 def _assert_job_scheduled_on_number_of(self, job_id, host_ids, number):
247 """Assert job was scheduled on exactly number hosts out of a set."""
248 found = []
249 for host_id in host_ids:
250 record = (job_id, host_id)
251 if record in self._jobs_scheduled:
252 found.append(record)
253 self._jobs_scheduled.remove(record)
254 if len(found) < number:
255 self.fail('Job %d scheduled on fewer than %d hosts in %s.\n'
256 'Jobs scheduled: %s' % (job_id, number, host_ids, found))
257 elif len(found) > number:
258 self.fail('Job %d scheduled on more than %d hosts in %s.\n'
259 'Jobs scheduled: %s' % (job_id, number, host_ids, found))
260
261
showard56193bb2008-08-13 20:07:41 +0000262 def _check_for_extra_schedulings(self):
263 if len(self._jobs_scheduled) != 0:
264 self.fail('Extra jobs scheduled: ' +
265 str(self._jobs_scheduled))
266
267
jadmanski0afbb632008-06-06 21:10:57 +0000268 def _convert_jobs_to_metahosts(self, *job_ids):
269 sql_tuple = '(' + ','.join(str(i) for i in job_ids) + ')'
270 self._do_query('UPDATE host_queue_entries SET '
271 'meta_host=host_id, host_id=NULL '
272 'WHERE job_id IN ' + sql_tuple)
showardce38e0c2008-05-29 19:36:16 +0000273
274
jadmanski0afbb632008-06-06 21:10:57 +0000275 def _lock_host(self, host_id):
276 self._do_query('UPDATE hosts SET locked=1 WHERE id=' +
277 str(host_id))
showardce38e0c2008-05-29 19:36:16 +0000278
279
jadmanski0afbb632008-06-06 21:10:57 +0000280 def setUp(self):
showard56193bb2008-08-13 20:07:41 +0000281 super(DispatcherSchedulingTest, self).setUp()
jadmanski0afbb632008-06-06 21:10:57 +0000282 self._jobs_scheduled = []
showardce38e0c2008-05-29 19:36:16 +0000283
284
jadmanski0afbb632008-06-06 21:10:57 +0000285 def _test_basic_scheduling_helper(self, use_metahosts):
286 'Basic nonmetahost scheduling'
287 self._create_job_simple([1], use_metahosts)
288 self._create_job_simple([2], use_metahosts)
289 self._dispatcher._schedule_new_jobs()
290 self._assert_job_scheduled_on(1, 1)
291 self._assert_job_scheduled_on(2, 2)
292 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000293
294
jadmanski0afbb632008-06-06 21:10:57 +0000295 def _test_priorities_helper(self, use_metahosts):
296 'Test prioritization ordering'
297 self._create_job_simple([1], use_metahosts)
298 self._create_job_simple([2], use_metahosts)
299 self._create_job_simple([1,2], use_metahosts)
300 self._create_job_simple([1], use_metahosts, priority=1)
301 self._dispatcher._schedule_new_jobs()
302 self._assert_job_scheduled_on(4, 1) # higher priority
303 self._assert_job_scheduled_on(2, 2) # earlier job over later
304 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000305
306
jadmanski0afbb632008-06-06 21:10:57 +0000307 def _test_hosts_ready_helper(self, use_metahosts):
308 """
309 Only hosts that are status=Ready, unlocked and not invalid get
310 scheduled.
311 """
312 self._create_job_simple([1], use_metahosts)
313 self._do_query('UPDATE hosts SET status="Running" WHERE id=1')
314 self._dispatcher._schedule_new_jobs()
315 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000316
jadmanski0afbb632008-06-06 21:10:57 +0000317 self._do_query('UPDATE hosts SET status="Ready", locked=1 '
318 'WHERE id=1')
319 self._dispatcher._schedule_new_jobs()
320 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000321
jadmanski0afbb632008-06-06 21:10:57 +0000322 self._do_query('UPDATE hosts SET locked=0, invalid=1 '
323 'WHERE id=1')
324 self._dispatcher._schedule_new_jobs()
showard5df2b192008-07-03 19:51:57 +0000325 if not use_metahosts:
326 self._assert_job_scheduled_on(1, 1)
jadmanski0afbb632008-06-06 21:10:57 +0000327 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000328
329
jadmanski0afbb632008-06-06 21:10:57 +0000330 def _test_hosts_idle_helper(self, use_metahosts):
331 'Only idle hosts get scheduled'
showard2bab8f42008-11-12 18:15:22 +0000332 self._create_job(hosts=[1], active=True)
jadmanski0afbb632008-06-06 21:10:57 +0000333 self._create_job_simple([1], use_metahosts)
334 self._dispatcher._schedule_new_jobs()
335 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000336
337
showard63a34772008-08-18 19:32:50 +0000338 def _test_obey_ACLs_helper(self, use_metahosts):
339 self._do_query('DELETE FROM acl_groups_hosts WHERE host_id=1')
340 self._create_job_simple([1], use_metahosts)
341 self._dispatcher._schedule_new_jobs()
342 self._check_for_extra_schedulings()
343
344
jadmanski0afbb632008-06-06 21:10:57 +0000345 def test_basic_scheduling(self):
346 self._test_basic_scheduling_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000347
348
jadmanski0afbb632008-06-06 21:10:57 +0000349 def test_priorities(self):
350 self._test_priorities_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000351
352
jadmanski0afbb632008-06-06 21:10:57 +0000353 def test_hosts_ready(self):
354 self._test_hosts_ready_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000355
356
jadmanski0afbb632008-06-06 21:10:57 +0000357 def test_hosts_idle(self):
358 self._test_hosts_idle_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000359
360
showard63a34772008-08-18 19:32:50 +0000361 def test_obey_ACLs(self):
362 self._test_obey_ACLs_helper(False)
363
364
showard2924b0a2009-06-18 23:16:15 +0000365 def test_one_time_hosts_ignore_ACLs(self):
366 self._do_query('DELETE FROM acl_groups_hosts WHERE host_id=1')
367 self._do_query('UPDATE hosts SET invalid=1 WHERE id=1')
368 self._create_job_simple([1])
369 self._dispatcher._schedule_new_jobs()
370 self._assert_job_scheduled_on(1, 1)
371 self._check_for_extra_schedulings()
372
373
showard63a34772008-08-18 19:32:50 +0000374 def test_non_metahost_on_invalid_host(self):
375 """
376 Non-metahost entries can get scheduled on invalid hosts (this is how
377 one-time hosts work).
378 """
379 self._do_query('UPDATE hosts SET invalid=1')
380 self._test_basic_scheduling_helper(False)
381
382
jadmanski0afbb632008-06-06 21:10:57 +0000383 def test_metahost_scheduling(self):
showard63a34772008-08-18 19:32:50 +0000384 """
385 Basic metahost scheduling
386 """
jadmanski0afbb632008-06-06 21:10:57 +0000387 self._test_basic_scheduling_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000388
389
jadmanski0afbb632008-06-06 21:10:57 +0000390 def test_metahost_priorities(self):
391 self._test_priorities_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000392
393
jadmanski0afbb632008-06-06 21:10:57 +0000394 def test_metahost_hosts_ready(self):
395 self._test_hosts_ready_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000396
397
jadmanski0afbb632008-06-06 21:10:57 +0000398 def test_metahost_hosts_idle(self):
399 self._test_hosts_idle_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000400
401
showard63a34772008-08-18 19:32:50 +0000402 def test_metahost_obey_ACLs(self):
403 self._test_obey_ACLs_helper(True)
404
405
showard89f84db2009-03-12 20:39:13 +0000406 def _setup_test_only_if_needed_labels(self):
showardade14e22009-01-26 22:38:32 +0000407 # apply only_if_needed label3 to host1
showard89f84db2009-03-12 20:39:13 +0000408 models.Host.smart_get('host1').labels.add(self.label3)
409 return self._create_job_simple([1], use_metahost=True)
showardade14e22009-01-26 22:38:32 +0000410
showard89f84db2009-03-12 20:39:13 +0000411
412 def test_only_if_needed_labels_avoids_host(self):
413 job = self._setup_test_only_if_needed_labels()
showardade14e22009-01-26 22:38:32 +0000414 # if the job doesn't depend on label3, there should be no scheduling
415 self._dispatcher._schedule_new_jobs()
416 self._check_for_extra_schedulings()
417
showard89f84db2009-03-12 20:39:13 +0000418
419 def test_only_if_needed_labels_schedules(self):
420 job = self._setup_test_only_if_needed_labels()
421 job.dependency_labels.add(self.label3)
showardade14e22009-01-26 22:38:32 +0000422 self._dispatcher._schedule_new_jobs()
423 self._assert_job_scheduled_on(1, 1)
424 self._check_for_extra_schedulings()
425
showard89f84db2009-03-12 20:39:13 +0000426
427 def test_only_if_needed_labels_via_metahost(self):
428 job = self._setup_test_only_if_needed_labels()
429 job.dependency_labels.add(self.label3)
showardade14e22009-01-26 22:38:32 +0000430 # should also work if the metahost is the only_if_needed label
431 self._do_query('DELETE FROM jobs_dependency_labels')
432 self._create_job(metahosts=[3])
433 self._dispatcher._schedule_new_jobs()
434 self._assert_job_scheduled_on(2, 1)
435 self._check_for_extra_schedulings()
showard989f25d2008-10-01 11:38:11 +0000436
437
jadmanski0afbb632008-06-06 21:10:57 +0000438 def test_nonmetahost_over_metahost(self):
439 """
440 Non-metahost entries should take priority over metahost entries
441 for the same host
442 """
443 self._create_job(metahosts=[1])
444 self._create_job(hosts=[1])
445 self._dispatcher._schedule_new_jobs()
446 self._assert_job_scheduled_on(2, 1)
447 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000448
449
jadmanski0afbb632008-06-06 21:10:57 +0000450 def test_metahosts_obey_blocks(self):
451 """
452 Metahosts can't get scheduled on hosts already scheduled for
453 that job.
454 """
455 self._create_job(metahosts=[1], hosts=[1])
456 # make the nonmetahost entry complete, so the metahost can try
457 # to get scheduled
showard56193bb2008-08-13 20:07:41 +0000458 self._update_hqe(set='complete = 1', where='host_id=1')
jadmanski0afbb632008-06-06 21:10:57 +0000459 self._dispatcher._schedule_new_jobs()
460 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000461
462
showard89f84db2009-03-12 20:39:13 +0000463 # TODO(gps): These should probably live in their own TestCase class
464 # specific to testing HostScheduler methods directly. It was convenient
465 # to put it here for now to share existing test environment setup code.
466 def test_HostScheduler_check_atomic_group_labels(self):
467 normal_job = self._create_job(metahosts=[0])
468 atomic_job = self._create_job(atomic_group=1)
469 # Indirectly initialize the internal state of the host scheduler.
470 self._dispatcher._refresh_pending_queue_entries()
471
showard6157c632009-07-06 20:19:31 +0000472 atomic_hqe = monitor_db.HostQueueEntry.fetch(where='job_id=%d' %
showard8cc058f2009-09-08 16:26:33 +0000473 atomic_job.id)[0]
showard6157c632009-07-06 20:19:31 +0000474 normal_hqe = monitor_db.HostQueueEntry.fetch(where='job_id=%d' %
showard8cc058f2009-09-08 16:26:33 +0000475 normal_job.id)[0]
showard89f84db2009-03-12 20:39:13 +0000476
477 host_scheduler = self._dispatcher._host_scheduler
478 self.assertTrue(host_scheduler._check_atomic_group_labels(
479 [self.label4.id], atomic_hqe))
480 self.assertFalse(host_scheduler._check_atomic_group_labels(
481 [self.label4.id], normal_hqe))
482 self.assertFalse(host_scheduler._check_atomic_group_labels(
483 [self.label5.id, self.label6.id, self.label7.id], normal_hqe))
484 self.assertTrue(host_scheduler._check_atomic_group_labels(
485 [self.label4.id, self.label6.id], atomic_hqe))
showard6157c632009-07-06 20:19:31 +0000486 self.assertTrue(host_scheduler._check_atomic_group_labels(
487 [self.label4.id, self.label5.id],
488 atomic_hqe))
showard89f84db2009-03-12 20:39:13 +0000489
490
491 def test_HostScheduler_get_host_atomic_group_id(self):
showard6157c632009-07-06 20:19:31 +0000492 job = self._create_job(metahosts=[self.label6.id])
493 queue_entry = monitor_db.HostQueueEntry.fetch(
showard8cc058f2009-09-08 16:26:33 +0000494 where='job_id=%d' % job.id)[0]
showard89f84db2009-03-12 20:39:13 +0000495 # Indirectly initialize the internal state of the host scheduler.
496 self._dispatcher._refresh_pending_queue_entries()
497
498 # Test the host scheduler
499 host_scheduler = self._dispatcher._host_scheduler
showard6157c632009-07-06 20:19:31 +0000500
501 # Two labels each in a different atomic group. This should log an
502 # error and continue.
503 orig_logging_error = logging.error
504 def mock_logging_error(message, *args):
505 mock_logging_error._num_calls += 1
506 # Test the logging call itself, we just wrapped it to count it.
507 orig_logging_error(message, *args)
508 mock_logging_error._num_calls = 0
509 self.god.stub_with(logging, 'error', mock_logging_error)
510 self.assertNotEquals(None, host_scheduler._get_host_atomic_group_id(
511 [self.label4.id, self.label8.id], queue_entry))
512 self.assertTrue(mock_logging_error._num_calls > 0)
513 self.god.unstub(logging, 'error')
514
515 # Two labels both in the same atomic group, this should not raise an
516 # error, it will merely cause the job to schedule on the intersection.
517 self.assertEquals(1, host_scheduler._get_host_atomic_group_id(
518 [self.label4.id, self.label5.id]))
519
520 self.assertEquals(None, host_scheduler._get_host_atomic_group_id([]))
521 self.assertEquals(None, host_scheduler._get_host_atomic_group_id(
showard89f84db2009-03-12 20:39:13 +0000522 [self.label3.id, self.label7.id, self.label6.id]))
showard6157c632009-07-06 20:19:31 +0000523 self.assertEquals(1, host_scheduler._get_host_atomic_group_id(
showard89f84db2009-03-12 20:39:13 +0000524 [self.label4.id, self.label7.id, self.label6.id]))
showard6157c632009-07-06 20:19:31 +0000525 self.assertEquals(1, host_scheduler._get_host_atomic_group_id(
showard89f84db2009-03-12 20:39:13 +0000526 [self.label7.id, self.label5.id]))
527
528
529 def test_atomic_group_hosts_blocked_from_non_atomic_jobs(self):
530 # Create a job scheduled to run on label6.
531 self._create_job(metahosts=[self.label6.id])
532 self._dispatcher._schedule_new_jobs()
533 # label6 only has hosts that are in atomic groups associated with it,
534 # there should be no scheduling.
535 self._check_for_extra_schedulings()
536
537
538 def test_atomic_group_hosts_blocked_from_non_atomic_jobs_explicit(self):
539 # Create a job scheduled to run on label5. This is an atomic group
540 # label but this job does not request atomic group scheduling.
541 self._create_job(metahosts=[self.label5.id])
542 self._dispatcher._schedule_new_jobs()
543 # label6 only has hosts that are in atomic groups associated with it,
544 # there should be no scheduling.
545 self._check_for_extra_schedulings()
546
547
548 def test_atomic_group_scheduling_basics(self):
549 # Create jobs scheduled to run on an atomic group.
550 job_a = self._create_job(synchronous=True, metahosts=[self.label4.id],
551 atomic_group=1)
552 job_b = self._create_job(synchronous=True, metahosts=[self.label5.id],
553 atomic_group=1)
554 self._dispatcher._schedule_new_jobs()
555 # atomic_group.max_number_of_machines was 2 so we should run on 2.
556 self._assert_job_scheduled_on_number_of(job_a.id, (5, 6, 7), 2)
557 self._assert_job_scheduled_on(job_b.id, 8) # label5
558 self._assert_job_scheduled_on(job_b.id, 9) # label5
559 self._check_for_extra_schedulings()
560
561 # The three host label4 atomic group still has one host available.
562 # That means a job with a synch_count of 1 asking to be scheduled on
563 # the atomic group can still use the final machine.
564 #
565 # This may seem like a somewhat odd use case. It allows the use of an
566 # atomic group as a set of machines to run smaller jobs within (a set
567 # of hosts configured for use in network tests with eachother perhaps?)
568 onehost_job = self._create_job(atomic_group=1)
569 self._dispatcher._schedule_new_jobs()
570 self._assert_job_scheduled_on_number_of(onehost_job.id, (5, 6, 7), 1)
571 self._check_for_extra_schedulings()
572
573 # No more atomic groups have hosts available, no more jobs should
574 # be scheduled.
575 self._create_job(atomic_group=1)
576 self._dispatcher._schedule_new_jobs()
577 self._check_for_extra_schedulings()
578
579
580 def test_atomic_group_scheduling_obeys_acls(self):
581 # Request scheduling on a specific atomic label but be denied by ACLs.
582 self._do_query('DELETE FROM acl_groups_hosts WHERE host_id in (8,9)')
583 job = self._create_job(metahosts=[self.label5.id], atomic_group=1)
584 self._dispatcher._schedule_new_jobs()
585 self._check_for_extra_schedulings()
586
587
588 def test_atomic_group_scheduling_dependency_label_exclude(self):
589 # A dependency label that matches no hosts in the atomic group.
590 job_a = self._create_job(atomic_group=1)
591 job_a.dependency_labels.add(self.label3)
592 self._dispatcher._schedule_new_jobs()
593 self._check_for_extra_schedulings()
594
595
596 def test_atomic_group_scheduling_metahost_dependency_label_exclude(self):
597 # A metahost and dependency label that excludes too many hosts.
598 job_b = self._create_job(synchronous=True, metahosts=[self.label4.id],
599 atomic_group=1)
600 job_b.dependency_labels.add(self.label7)
601 self._dispatcher._schedule_new_jobs()
602 self._check_for_extra_schedulings()
603
604
605 def test_atomic_group_scheduling_dependency_label_match(self):
606 # A dependency label that exists on enough atomic group hosts in only
607 # one of the two atomic group labels.
608 job_c = self._create_job(synchronous=True, atomic_group=1)
609 job_c.dependency_labels.add(self.label7)
610 self._dispatcher._schedule_new_jobs()
611 self._assert_job_scheduled_on_number_of(job_c.id, (8, 9), 2)
612 self._check_for_extra_schedulings()
613
614
615 def test_atomic_group_scheduling_no_metahost(self):
616 # Force it to schedule on the other group for a reliable test.
617 self._do_query('UPDATE hosts SET invalid=1 WHERE id=9')
618 # An atomic job without a metahost.
619 job = self._create_job(synchronous=True, atomic_group=1)
620 self._dispatcher._schedule_new_jobs()
621 self._assert_job_scheduled_on_number_of(job.id, (5, 6, 7), 2)
622 self._check_for_extra_schedulings()
623
624
625 def test_atomic_group_scheduling_partial_group(self):
626 # Make one host in labels[3] unavailable so that there are only two
627 # hosts left in the group.
628 self._do_query('UPDATE hosts SET status="Repair Failed" WHERE id=5')
629 job = self._create_job(synchronous=True, metahosts=[self.label4.id],
630 atomic_group=1)
631 self._dispatcher._schedule_new_jobs()
632 # Verify that it was scheduled on the 2 ready hosts in that group.
633 self._assert_job_scheduled_on(job.id, 6)
634 self._assert_job_scheduled_on(job.id, 7)
635 self._check_for_extra_schedulings()
636
637
638 def test_atomic_group_scheduling_not_enough_available(self):
639 # Mark some hosts in each atomic group label as not usable.
640 # One host running, another invalid in the first group label.
641 self._do_query('UPDATE hosts SET status="Running" WHERE id=5')
642 self._do_query('UPDATE hosts SET invalid=1 WHERE id=6')
643 # One host invalid in the second group label.
644 self._do_query('UPDATE hosts SET invalid=1 WHERE id=9')
645 # Nothing to schedule when no group label has enough (2) good hosts..
646 self._create_job(atomic_group=1, synchronous=True)
647 self._dispatcher._schedule_new_jobs()
648 # There are not enough hosts in either atomic group,
649 # No more scheduling should occur.
650 self._check_for_extra_schedulings()
651
652 # Now create an atomic job that has a synch count of 1. It should
653 # schedule on exactly one of the hosts.
654 onehost_job = self._create_job(atomic_group=1)
655 self._dispatcher._schedule_new_jobs()
656 self._assert_job_scheduled_on_number_of(onehost_job.id, (7, 8), 1)
657
658
659 def test_atomic_group_scheduling_no_valid_hosts(self):
660 self._do_query('UPDATE hosts SET invalid=1 WHERE id in (8,9)')
661 self._create_job(synchronous=True, metahosts=[self.label5.id],
662 atomic_group=1)
663 self._dispatcher._schedule_new_jobs()
664 # no hosts in the selected group and label are valid. no schedulings.
665 self._check_for_extra_schedulings()
666
667
668 def test_atomic_group_scheduling_metahost_works(self):
669 # Test that atomic group scheduling also obeys metahosts.
670 self._create_job(metahosts=[0], atomic_group=1)
671 self._dispatcher._schedule_new_jobs()
672 # There are no atomic group hosts that also have that metahost.
673 self._check_for_extra_schedulings()
674
675 job_b = self._create_job(metahosts=[self.label5.id], atomic_group=1)
676 self._dispatcher._schedule_new_jobs()
677 self._assert_job_scheduled_on(job_b.id, 8)
678 self._assert_job_scheduled_on(job_b.id, 9)
679 self._check_for_extra_schedulings()
680
681
682 def test_atomic_group_skips_ineligible_hosts(self):
683 # Test hosts marked ineligible for this job are not eligible.
684 # How would this ever happen anyways?
685 job = self._create_job(metahosts=[self.label4.id], atomic_group=1)
686 models.IneligibleHostQueue.objects.create(job=job, host_id=5)
687 models.IneligibleHostQueue.objects.create(job=job, host_id=6)
688 models.IneligibleHostQueue.objects.create(job=job, host_id=7)
689 self._dispatcher._schedule_new_jobs()
690 # No scheduling should occur as all desired hosts were ineligible.
691 self._check_for_extra_schedulings()
692
693
694 def test_atomic_group_scheduling_fail(self):
695 # If synch_count is > the atomic group number of machines, the job
696 # should be aborted immediately.
697 model_job = self._create_job(synchronous=True, atomic_group=1)
698 model_job.synch_count = 4
699 model_job.save()
700 job = monitor_db.Job(id=model_job.id)
701 self._dispatcher._schedule_new_jobs()
702 self._check_for_extra_schedulings()
703 queue_entries = job.get_host_queue_entries()
704 self.assertEqual(1, len(queue_entries))
705 self.assertEqual(queue_entries[0].status,
706 models.HostQueueEntry.Status.ABORTED)
707
708
showard205fd602009-03-21 00:17:35 +0000709 def test_atomic_group_no_labels_no_scheduling(self):
710 # Never schedule on atomic groups marked invalid.
711 job = self._create_job(metahosts=[self.label5.id], synchronous=True,
712 atomic_group=1)
713 # Deleting an atomic group via the frontend marks it invalid and
714 # removes all label references to the group. The job now references
715 # an invalid atomic group with no labels associated with it.
716 self.label5.atomic_group.invalid = True
717 self.label5.atomic_group.save()
718 self.label5.atomic_group = None
719 self.label5.save()
720
721 self._dispatcher._schedule_new_jobs()
722 self._check_for_extra_schedulings()
723
724
showard89f84db2009-03-12 20:39:13 +0000725 def test_schedule_directly_on_atomic_group_host_fail(self):
726 # Scheduling a job directly on hosts in an atomic group must
727 # fail to avoid users inadvertently holding up the use of an
728 # entire atomic group by using the machines individually.
729 job = self._create_job(hosts=[5])
730 self._dispatcher._schedule_new_jobs()
731 self._check_for_extra_schedulings()
732
733
734 def test_schedule_directly_on_atomic_group_host(self):
735 # Scheduling a job directly on one host in an atomic group will
736 # work when the atomic group is listed on the HQE in addition
737 # to the host (assuming the sync count is 1).
738 job = self._create_job(hosts=[5], atomic_group=1)
739 self._dispatcher._schedule_new_jobs()
740 self._assert_job_scheduled_on(job.id, 5)
741 self._check_for_extra_schedulings()
742
743
744 def test_schedule_directly_on_atomic_group_hosts_sync2(self):
745 job = self._create_job(hosts=[5,8], atomic_group=1, synchronous=True)
746 self._dispatcher._schedule_new_jobs()
747 self._assert_job_scheduled_on(job.id, 5)
748 self._assert_job_scheduled_on(job.id, 8)
749 self._check_for_extra_schedulings()
750
751
752 def test_schedule_directly_on_atomic_group_hosts_wrong_group(self):
753 job = self._create_job(hosts=[5,8], atomic_group=2, synchronous=True)
754 self._dispatcher._schedule_new_jobs()
755 self._check_for_extra_schedulings()
756
757
showard56193bb2008-08-13 20:07:41 +0000758 def test_only_schedule_queued_entries(self):
759 self._create_job(metahosts=[1])
760 self._update_hqe(set='active=1, host_id=2')
761 self._dispatcher._schedule_new_jobs()
762 self._check_for_extra_schedulings()
763
764
showardfa8629c2008-11-04 16:51:23 +0000765 def test_no_ready_hosts(self):
766 self._create_job(hosts=[1])
767 self._do_query('UPDATE hosts SET status="Repair Failed"')
768 self._dispatcher._schedule_new_jobs()
769 self._check_for_extra_schedulings()
770
771
showardb2e2c322008-10-14 17:33:55 +0000772class DispatcherThrottlingTest(BaseSchedulerTest):
showard4c5374f2008-09-04 17:02:56 +0000773 """
774 Test that the dispatcher throttles:
775 * total number of running processes
776 * number of processes started per cycle
777 """
778 _MAX_RUNNING = 3
779 _MAX_STARTED = 2
780
781 def setUp(self):
782 super(DispatcherThrottlingTest, self).setUp()
showard324bf812009-01-20 23:23:38 +0000783 scheduler_config.config.max_processes_per_drone = self._MAX_RUNNING
showardd1ee1dd2009-01-07 21:33:08 +0000784 scheduler_config.config.max_processes_started_per_cycle = (
785 self._MAX_STARTED)
showard4c5374f2008-09-04 17:02:56 +0000786
showard9bb960b2009-11-19 01:02:11 +0000787 def fake_max_runnable_processes(fake_self, username):
showardd1195652009-12-08 22:21:02 +0000788 running = sum(agent.task.num_processes
showard324bf812009-01-20 23:23:38 +0000789 for agent in self._agents
showard8cc058f2009-09-08 16:26:33 +0000790 if agent.started and not agent.is_done())
showard324bf812009-01-20 23:23:38 +0000791 return self._MAX_RUNNING - running
792 self.god.stub_with(drone_manager.DroneManager, 'max_runnable_processes',
793 fake_max_runnable_processes)
showard2fa51692009-01-13 23:48:08 +0000794
showard4c5374f2008-09-04 17:02:56 +0000795
showard4c5374f2008-09-04 17:02:56 +0000796 def _setup_some_agents(self, num_agents):
showard170873e2009-01-07 00:22:26 +0000797 self._agents = [DummyAgent() for i in xrange(num_agents)]
showard4c5374f2008-09-04 17:02:56 +0000798 self._dispatcher._agents = list(self._agents)
799
800
801 def _run_a_few_cycles(self):
802 for i in xrange(4):
803 self._dispatcher._handle_agents()
804
805
806 def _assert_agents_started(self, indexes, is_started=True):
807 for i in indexes:
showard8cc058f2009-09-08 16:26:33 +0000808 self.assert_(self._agents[i].started == is_started,
showard4c5374f2008-09-04 17:02:56 +0000809 'Agent %d %sstarted' %
810 (i, is_started and 'not ' or ''))
811
812
813 def _assert_agents_not_started(self, indexes):
814 self._assert_agents_started(indexes, False)
815
816
817 def test_throttle_total(self):
818 self._setup_some_agents(4)
819 self._run_a_few_cycles()
820 self._assert_agents_started([0, 1, 2])
821 self._assert_agents_not_started([3])
822
823
824 def test_throttle_per_cycle(self):
825 self._setup_some_agents(3)
826 self._dispatcher._handle_agents()
827 self._assert_agents_started([0, 1])
828 self._assert_agents_not_started([2])
829
830
831 def test_throttle_with_synchronous(self):
832 self._setup_some_agents(2)
showardd1195652009-12-08 22:21:02 +0000833 self._agents[0].task.num_processes = 3
showard4c5374f2008-09-04 17:02:56 +0000834 self._run_a_few_cycles()
835 self._assert_agents_started([0])
836 self._assert_agents_not_started([1])
837
838
839 def test_large_agent_starvation(self):
840 """
841 Ensure large agents don't get starved by lower-priority agents.
842 """
843 self._setup_some_agents(3)
showardd1195652009-12-08 22:21:02 +0000844 self._agents[1].task.num_processes = 3
showard4c5374f2008-09-04 17:02:56 +0000845 self._run_a_few_cycles()
846 self._assert_agents_started([0])
847 self._assert_agents_not_started([1, 2])
848
849 self._agents[0].set_done(True)
850 self._run_a_few_cycles()
851 self._assert_agents_started([1])
852 self._assert_agents_not_started([2])
853
854
855 def test_zero_process_agent(self):
856 self._setup_some_agents(5)
showardd1195652009-12-08 22:21:02 +0000857 self._agents[4].task.num_processes = 0
showard4c5374f2008-09-04 17:02:56 +0000858 self._run_a_few_cycles()
859 self._assert_agents_started([0, 1, 2, 4])
860 self._assert_agents_not_started([3])
861
862
jadmanski3d161b02008-06-06 15:43:36 +0000863class PidfileRunMonitorTest(unittest.TestCase):
showard170873e2009-01-07 00:22:26 +0000864 execution_tag = 'test_tag'
jadmanski0afbb632008-06-06 21:10:57 +0000865 pid = 12345
showard170873e2009-01-07 00:22:26 +0000866 process = drone_manager.Process('myhost', pid)
showard21baa452008-10-21 00:08:39 +0000867 num_tests_failed = 1
jadmanski3d161b02008-06-06 15:43:36 +0000868
jadmanski0afbb632008-06-06 21:10:57 +0000869 def setUp(self):
870 self.god = mock.mock_god()
showard170873e2009-01-07 00:22:26 +0000871 self.mock_drone_manager = self.god.create_mock_class(
872 drone_manager.DroneManager, 'drone_manager')
873 self.god.stub_with(monitor_db, '_drone_manager',
874 self.mock_drone_manager)
875 self.god.stub_function(email_manager.manager, 'enqueue_notify_email')
showardec6a3b92009-09-25 20:29:13 +0000876 self.god.stub_with(monitor_db, '_get_pidfile_timeout_secs',
877 self._mock_get_pidfile_timeout_secs)
showard170873e2009-01-07 00:22:26 +0000878
879 self.pidfile_id = object()
880
showardd3dc1992009-04-22 21:01:40 +0000881 (self.mock_drone_manager.get_pidfile_id_from
882 .expect_call(self.execution_tag,
883 pidfile_name=monitor_db._AUTOSERV_PID_FILE)
884 .and_return(self.pidfile_id))
showard170873e2009-01-07 00:22:26 +0000885
886 self.monitor = monitor_db.PidfileRunMonitor()
887 self.monitor.attach_to_existing_process(self.execution_tag)
jadmanski3d161b02008-06-06 15:43:36 +0000888
jadmanski0afbb632008-06-06 21:10:57 +0000889 def tearDown(self):
890 self.god.unstub_all()
jadmanski3d161b02008-06-06 15:43:36 +0000891
892
showardec6a3b92009-09-25 20:29:13 +0000893 def _mock_get_pidfile_timeout_secs(self):
894 return 300
895
896
showard170873e2009-01-07 00:22:26 +0000897 def setup_pidfile(self, pid=None, exit_code=None, tests_failed=None,
898 use_second_read=False):
899 contents = drone_manager.PidfileContents()
900 if pid is not None:
901 contents.process = drone_manager.Process('myhost', pid)
902 contents.exit_status = exit_code
903 contents.num_tests_failed = tests_failed
904 self.mock_drone_manager.get_pidfile_contents.expect_call(
905 self.pidfile_id, use_second_read=use_second_read).and_return(
906 contents)
907
908
jadmanski0afbb632008-06-06 21:10:57 +0000909 def set_not_yet_run(self):
showard170873e2009-01-07 00:22:26 +0000910 self.setup_pidfile()
jadmanski3d161b02008-06-06 15:43:36 +0000911
912
showard3dd6b882008-10-27 19:21:39 +0000913 def set_empty_pidfile(self):
showard170873e2009-01-07 00:22:26 +0000914 self.setup_pidfile()
showard3dd6b882008-10-27 19:21:39 +0000915
916
showard170873e2009-01-07 00:22:26 +0000917 def set_running(self, use_second_read=False):
918 self.setup_pidfile(self.pid, use_second_read=use_second_read)
jadmanski3d161b02008-06-06 15:43:36 +0000919
920
showard170873e2009-01-07 00:22:26 +0000921 def set_complete(self, error_code, use_second_read=False):
922 self.setup_pidfile(self.pid, error_code, self.num_tests_failed,
923 use_second_read=use_second_read)
924
925
926 def _check_monitor(self, expected_pid, expected_exit_status,
927 expected_num_tests_failed):
928 if expected_pid is None:
929 self.assertEquals(self.monitor._state.process, None)
930 else:
931 self.assertEquals(self.monitor._state.process.pid, expected_pid)
932 self.assertEquals(self.monitor._state.exit_status, expected_exit_status)
933 self.assertEquals(self.monitor._state.num_tests_failed,
934 expected_num_tests_failed)
935
936
937 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +0000938
939
showard21baa452008-10-21 00:08:39 +0000940 def _test_read_pidfile_helper(self, expected_pid, expected_exit_status,
941 expected_num_tests_failed):
942 self.monitor._read_pidfile()
showard170873e2009-01-07 00:22:26 +0000943 self._check_monitor(expected_pid, expected_exit_status,
944 expected_num_tests_failed)
jadmanski3d161b02008-06-06 15:43:36 +0000945
946
showard21baa452008-10-21 00:08:39 +0000947 def _get_expected_tests_failed(self, expected_exit_status):
948 if expected_exit_status is None:
949 expected_tests_failed = None
950 else:
951 expected_tests_failed = self.num_tests_failed
952 return expected_tests_failed
953
954
jadmanski0afbb632008-06-06 21:10:57 +0000955 def test_read_pidfile(self):
956 self.set_not_yet_run()
showard21baa452008-10-21 00:08:39 +0000957 self._test_read_pidfile_helper(None, None, None)
jadmanski3d161b02008-06-06 15:43:36 +0000958
showard3dd6b882008-10-27 19:21:39 +0000959 self.set_empty_pidfile()
960 self._test_read_pidfile_helper(None, None, None)
961
jadmanski0afbb632008-06-06 21:10:57 +0000962 self.set_running()
showard21baa452008-10-21 00:08:39 +0000963 self._test_read_pidfile_helper(self.pid, None, None)
jadmanski3d161b02008-06-06 15:43:36 +0000964
jadmanski0afbb632008-06-06 21:10:57 +0000965 self.set_complete(123)
showard21baa452008-10-21 00:08:39 +0000966 self._test_read_pidfile_helper(self.pid, 123, self.num_tests_failed)
jadmanski3d161b02008-06-06 15:43:36 +0000967
968
jadmanski0afbb632008-06-06 21:10:57 +0000969 def test_read_pidfile_error(self):
showard170873e2009-01-07 00:22:26 +0000970 self.mock_drone_manager.get_pidfile_contents.expect_call(
971 self.pidfile_id, use_second_read=False).and_return(
972 drone_manager.InvalidPidfile('error'))
973 self.assertRaises(monitor_db.PidfileRunMonitor._PidfileException,
showard21baa452008-10-21 00:08:39 +0000974 self.monitor._read_pidfile)
jadmanski0afbb632008-06-06 21:10:57 +0000975 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +0000976
977
showard170873e2009-01-07 00:22:26 +0000978 def setup_is_running(self, is_running):
979 self.mock_drone_manager.is_process_running.expect_call(
980 self.process).and_return(is_running)
jadmanski3d161b02008-06-06 15:43:36 +0000981
982
showard21baa452008-10-21 00:08:39 +0000983 def _test_get_pidfile_info_helper(self, expected_pid, expected_exit_status,
984 expected_num_tests_failed):
985 self.monitor._get_pidfile_info()
showard170873e2009-01-07 00:22:26 +0000986 self._check_monitor(expected_pid, expected_exit_status,
987 expected_num_tests_failed)
jadmanski3d161b02008-06-06 15:43:36 +0000988
989
jadmanski0afbb632008-06-06 21:10:57 +0000990 def test_get_pidfile_info(self):
showard21baa452008-10-21 00:08:39 +0000991 """
992 normal cases for get_pidfile_info
993 """
jadmanski0afbb632008-06-06 21:10:57 +0000994 # running
995 self.set_running()
showard170873e2009-01-07 00:22:26 +0000996 self.setup_is_running(True)
showard21baa452008-10-21 00:08:39 +0000997 self._test_get_pidfile_info_helper(self.pid, None, None)
jadmanski3d161b02008-06-06 15:43:36 +0000998
jadmanski0afbb632008-06-06 21:10:57 +0000999 # exited during check
1000 self.set_running()
showard170873e2009-01-07 00:22:26 +00001001 self.setup_is_running(False)
1002 self.set_complete(123, use_second_read=True) # pidfile gets read again
showard21baa452008-10-21 00:08:39 +00001003 self._test_get_pidfile_info_helper(self.pid, 123, self.num_tests_failed)
jadmanski3d161b02008-06-06 15:43:36 +00001004
jadmanski0afbb632008-06-06 21:10:57 +00001005 # completed
1006 self.set_complete(123)
showard21baa452008-10-21 00:08:39 +00001007 self._test_get_pidfile_info_helper(self.pid, 123, self.num_tests_failed)
jadmanski3d161b02008-06-06 15:43:36 +00001008
1009
jadmanski0afbb632008-06-06 21:10:57 +00001010 def test_get_pidfile_info_running_no_proc(self):
showard21baa452008-10-21 00:08:39 +00001011 """
1012 pidfile shows process running, but no proc exists
1013 """
jadmanski0afbb632008-06-06 21:10:57 +00001014 # running but no proc
1015 self.set_running()
showard170873e2009-01-07 00:22:26 +00001016 self.setup_is_running(False)
1017 self.set_running(use_second_read=True)
1018 email_manager.manager.enqueue_notify_email.expect_call(
jadmanski0afbb632008-06-06 21:10:57 +00001019 mock.is_string_comparator(), mock.is_string_comparator())
showard21baa452008-10-21 00:08:39 +00001020 self._test_get_pidfile_info_helper(self.pid, 1, 0)
jadmanski0afbb632008-06-06 21:10:57 +00001021 self.assertTrue(self.monitor.lost_process)
jadmanski3d161b02008-06-06 15:43:36 +00001022
1023
jadmanski0afbb632008-06-06 21:10:57 +00001024 def test_get_pidfile_info_not_yet_run(self):
showard21baa452008-10-21 00:08:39 +00001025 """
1026 pidfile hasn't been written yet
1027 """
jadmanski0afbb632008-06-06 21:10:57 +00001028 self.set_not_yet_run()
showard21baa452008-10-21 00:08:39 +00001029 self._test_get_pidfile_info_helper(None, None, None)
jadmanski3d161b02008-06-06 15:43:36 +00001030
jadmanski3d161b02008-06-06 15:43:36 +00001031
showard170873e2009-01-07 00:22:26 +00001032 def test_process_failed_to_write_pidfile(self):
jadmanski0afbb632008-06-06 21:10:57 +00001033 self.set_not_yet_run()
showard170873e2009-01-07 00:22:26 +00001034 email_manager.manager.enqueue_notify_email.expect_call(
1035 mock.is_string_comparator(), mock.is_string_comparator())
showardec6a3b92009-09-25 20:29:13 +00001036 self.monitor._start_time = (time.time() -
1037 monitor_db._get_pidfile_timeout_secs() - 1)
showard35162b02009-03-03 02:17:30 +00001038 self._test_get_pidfile_info_helper(None, 1, 0)
1039 self.assertTrue(self.monitor.lost_process)
jadmanski3d161b02008-06-06 15:43:36 +00001040
1041
1042class AgentTest(unittest.TestCase):
jadmanski0afbb632008-06-06 21:10:57 +00001043 def setUp(self):
1044 self.god = mock.mock_god()
showard6b733412009-04-27 20:09:18 +00001045 self._dispatcher = self.god.create_mock_class(monitor_db.Dispatcher,
1046 'dispatcher')
jadmanski3d161b02008-06-06 15:43:36 +00001047
1048
jadmanski0afbb632008-06-06 21:10:57 +00001049 def tearDown(self):
1050 self.god.unstub_all()
jadmanski3d161b02008-06-06 15:43:36 +00001051
1052
showard170873e2009-01-07 00:22:26 +00001053 def _create_mock_task(self, name):
1054 task = self.god.create_mock_class(monitor_db.AgentTask, name)
showard418785b2009-11-23 20:19:59 +00001055 task.num_processes = 1
showard6b733412009-04-27 20:09:18 +00001056 _set_host_and_qe_ids(task)
showard170873e2009-01-07 00:22:26 +00001057 return task
1058
showard8cc058f2009-09-08 16:26:33 +00001059 def _create_agent(self, task):
1060 agent = monitor_db.Agent(task)
showard6b733412009-04-27 20:09:18 +00001061 agent.dispatcher = self._dispatcher
1062 return agent
1063
1064
1065 def _finish_agent(self, agent):
1066 while not agent.is_done():
1067 agent.tick()
1068
showard170873e2009-01-07 00:22:26 +00001069
showard8cc058f2009-09-08 16:26:33 +00001070 def test_agent_abort(self):
1071 task = self._create_mock_task('task')
1072 task.poll.expect_call()
1073 task.is_done.expect_call().and_return(False)
1074 task.abort.expect_call()
1075 task.aborted = True
jadmanski3d161b02008-06-06 15:43:36 +00001076
showard8cc058f2009-09-08 16:26:33 +00001077 agent = self._create_agent(task)
showard6b733412009-04-27 20:09:18 +00001078 agent.tick()
1079 agent.abort()
1080 self._finish_agent(agent)
1081 self.god.check_playback()
1082
1083
showard08a36412009-05-05 01:01:13 +00001084 def _test_agent_abort_before_started_helper(self, ignore_abort=False):
showard20f9bdd2009-04-29 19:48:33 +00001085 task = self._create_mock_task('task')
showard08a36412009-05-05 01:01:13 +00001086 task.abort.expect_call()
1087 if ignore_abort:
1088 task.aborted = False
1089 task.poll.expect_call()
1090 task.is_done.expect_call().and_return(True)
showard08a36412009-05-05 01:01:13 +00001091 task.success = True
1092 else:
1093 task.aborted = True
1094
showard8cc058f2009-09-08 16:26:33 +00001095 agent = self._create_agent(task)
showard20f9bdd2009-04-29 19:48:33 +00001096 agent.abort()
showard20f9bdd2009-04-29 19:48:33 +00001097 self._finish_agent(agent)
1098 self.god.check_playback()
1099
1100
showard08a36412009-05-05 01:01:13 +00001101 def test_agent_abort_before_started(self):
1102 self._test_agent_abort_before_started_helper()
1103 self._test_agent_abort_before_started_helper(True)
1104
1105
showard77182562009-06-10 00:16:05 +00001106class DelayedCallTaskTest(unittest.TestCase):
1107 def setUp(self):
1108 self.god = mock.mock_god()
1109
1110
1111 def tearDown(self):
1112 self.god.unstub_all()
1113
1114
1115 def test_delayed_call(self):
mbligh1ef218d2009-08-03 16:57:56 +00001116 test_time = self.god.create_mock_function('time')
showard77182562009-06-10 00:16:05 +00001117 test_time.expect_call().and_return(33)
1118 test_time.expect_call().and_return(34.01)
1119 test_time.expect_call().and_return(34.99)
1120 test_time.expect_call().and_return(35.01)
1121 def test_callback():
1122 test_callback.calls += 1
1123 test_callback.calls = 0
1124 delay_task = monitor_db.DelayedCallTask(
1125 delay_seconds=2, callback=test_callback,
1126 now_func=test_time) # time 33
1127 self.assertEqual(35, delay_task.end_time)
showard418785b2009-11-23 20:19:59 +00001128 agent = monitor_db.Agent(delay_task)
showard8cc058f2009-09-08 16:26:33 +00001129 self.assert_(not agent.started)
showard77182562009-06-10 00:16:05 +00001130 agent.tick() # activates the task and polls it once, time 34.01
1131 self.assertEqual(0, test_callback.calls, "callback called early")
1132 agent.tick() # time 34.99
1133 self.assertEqual(0, test_callback.calls, "callback called early")
1134 agent.tick() # time 35.01
1135 self.assertEqual(1, test_callback.calls)
1136 self.assert_(agent.is_done())
1137 self.assert_(delay_task.is_done())
1138 self.assert_(delay_task.success)
1139 self.assert_(not delay_task.aborted)
1140 self.god.check_playback()
1141
1142
1143 def test_delayed_call_abort(self):
1144 delay_task = monitor_db.DelayedCallTask(
1145 delay_seconds=987654, callback=lambda : None)
showard418785b2009-11-23 20:19:59 +00001146 agent = monitor_db.Agent(delay_task)
showard77182562009-06-10 00:16:05 +00001147 agent.abort()
1148 agent.tick()
1149 self.assert_(agent.is_done())
1150 self.assert_(delay_task.aborted)
1151 self.assert_(delay_task.is_done())
1152 self.assert_(not delay_task.success)
1153 self.god.check_playback()
1154
1155
showard54c1ea92009-05-20 00:32:58 +00001156class HostTest(BaseSchedulerTest):
1157 def test_cmp_for_sort(self):
1158 expected_order = [
1159 'alice', 'Host1', 'host2', 'host3', 'host09', 'HOST010',
1160 'host10', 'host11', 'yolkfolk']
1161 hostname_idx = list(monitor_db.Host._fields).index('hostname')
1162 row = [None] * len(monitor_db.Host._fields)
1163 hosts = []
1164 for hostname in expected_order:
1165 row[hostname_idx] = hostname
1166 hosts.append(monitor_db.Host(row=row, new_record=True))
1167
1168 host1 = hosts[expected_order.index('Host1')]
1169 host010 = hosts[expected_order.index('HOST010')]
1170 host10 = hosts[expected_order.index('host10')]
1171 host3 = hosts[expected_order.index('host3')]
1172 alice = hosts[expected_order.index('alice')]
1173 self.assertEqual(0, monitor_db.Host.cmp_for_sort(host10, host10))
1174 self.assertEqual(1, monitor_db.Host.cmp_for_sort(host10, host010))
1175 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host010, host10))
1176 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host1, host10))
1177 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host1, host010))
1178 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host3, host10))
1179 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host3, host010))
1180 self.assertEqual(1, monitor_db.Host.cmp_for_sort(host3, host1))
1181 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host1, host3))
1182 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(alice, host3))
1183 self.assertEqual(1, monitor_db.Host.cmp_for_sort(host3, alice))
1184 self.assertEqual(0, monitor_db.Host.cmp_for_sort(alice, alice))
1185
1186 hosts.sort(cmp=monitor_db.Host.cmp_for_sort)
1187 self.assertEqual(expected_order, [h.hostname for h in hosts])
1188
1189 hosts.reverse()
1190 hosts.sort(cmp=monitor_db.Host.cmp_for_sort)
1191 self.assertEqual(expected_order, [h.hostname for h in hosts])
1192
1193
showardf1ae3542009-05-11 19:26:02 +00001194class HostQueueEntryTest(BaseSchedulerTest):
1195 def _create_hqe(self, dependency_labels=(), **create_job_kwargs):
1196 job = self._create_job(**create_job_kwargs)
1197 for label in dependency_labels:
1198 job.dependency_labels.add(label)
1199 hqes = list(monitor_db.HostQueueEntry.fetch(where='job_id=%d' % job.id))
1200 self.assertEqual(1, len(hqes))
1201 return hqes[0]
1202
showard77182562009-06-10 00:16:05 +00001203
showardf1ae3542009-05-11 19:26:02 +00001204 def _check_hqe_labels(self, hqe, expected_labels):
1205 expected_labels = set(expected_labels)
1206 label_names = set(label.name for label in hqe.get_labels())
1207 self.assertEqual(expected_labels, label_names)
1208
showard77182562009-06-10 00:16:05 +00001209
showardf1ae3542009-05-11 19:26:02 +00001210 def test_get_labels_empty(self):
1211 hqe = self._create_hqe(hosts=[1])
1212 labels = list(hqe.get_labels())
1213 self.assertEqual([], labels)
1214
showard77182562009-06-10 00:16:05 +00001215
showardf1ae3542009-05-11 19:26:02 +00001216 def test_get_labels_metahost(self):
1217 hqe = self._create_hqe(metahosts=[2])
1218 self._check_hqe_labels(hqe, ['label2'])
1219
showard77182562009-06-10 00:16:05 +00001220
showardf1ae3542009-05-11 19:26:02 +00001221 def test_get_labels_dependancies(self):
1222 hqe = self._create_hqe(dependency_labels=(self.label3, self.label4),
1223 metahosts=[1])
1224 self._check_hqe_labels(hqe, ['label1', 'label3', 'label4'])
1225
1226
showardb2e2c322008-10-14 17:33:55 +00001227class JobTest(BaseSchedulerTest):
showard2bab8f42008-11-12 18:15:22 +00001228 def setUp(self):
1229 super(JobTest, self).setUp()
showard170873e2009-01-07 00:22:26 +00001230 self.god.stub_with(
1231 drone_manager.DroneManager, 'attach_file_to_execution',
1232 mock.mock_function('attach_file_to_execution',
1233 default_return_val='/test/path/tmp/foo'))
showard2bab8f42008-11-12 18:15:22 +00001234
showard8cc058f2009-09-08 16:26:33 +00001235 def _mock_create(**kwargs):
1236 task = models.SpecialTask(**kwargs)
1237 task.save()
1238 self._task = task
1239 self.god.stub_with(models.SpecialTask.objects, 'create', _mock_create)
1240
showard2bab8f42008-11-12 18:15:22 +00001241
showard77182562009-06-10 00:16:05 +00001242 def _test_pre_job_tasks_helper(self):
1243 """
showard8cc058f2009-09-08 16:26:33 +00001244 Calls HQE._do_schedule_pre_job_tasks() and returns the created special
1245 task
showard77182562009-06-10 00:16:05 +00001246 """
showard8cc058f2009-09-08 16:26:33 +00001247 self._task = None
1248 queue_entry = monitor_db.HostQueueEntry.fetch('id = 1')[0]
1249 queue_entry._do_schedule_pre_job_tasks()
1250 return self._task
showard2bab8f42008-11-12 18:15:22 +00001251
1252
showarde58e3f82008-11-20 19:04:59 +00001253 def _test_run_helper(self, expect_agent=True, expect_starting=False,
1254 expect_pending=False):
1255 if expect_starting:
1256 expected_status = models.HostQueueEntry.Status.STARTING
1257 elif expect_pending:
1258 expected_status = models.HostQueueEntry.Status.PENDING
1259 else:
1260 expected_status = models.HostQueueEntry.Status.VERIFYING
showard8cc058f2009-09-08 16:26:33 +00001261 job = monitor_db.Job.fetch('id = 1')[0]
1262 queue_entry = monitor_db.HostQueueEntry.fetch('id = 1')[0]
showard77182562009-06-10 00:16:05 +00001263 assert queue_entry.job is job
showard8cc058f2009-09-08 16:26:33 +00001264 job.run_if_ready(queue_entry)
showardb2e2c322008-10-14 17:33:55 +00001265
showard2bab8f42008-11-12 18:15:22 +00001266 self.god.check_playback()
showard8cc058f2009-09-08 16:26:33 +00001267
1268 self._dispatcher._schedule_delay_tasks()
1269 self._dispatcher._schedule_running_host_queue_entries()
1270 agent = self._dispatcher._agents[0]
1271
showard77182562009-06-10 00:16:05 +00001272 actual_status = models.HostQueueEntry.smart_get(1).status
1273 self.assertEquals(expected_status, actual_status)
showard2bab8f42008-11-12 18:15:22 +00001274
showard9976ce92008-10-15 20:28:13 +00001275 if not expect_agent:
1276 self.assertEquals(agent, None)
1277 return
1278
showardb2e2c322008-10-14 17:33:55 +00001279 self.assert_(isinstance(agent, monitor_db.Agent))
showard8cc058f2009-09-08 16:26:33 +00001280 self.assert_(agent.task)
1281 return agent.task
showardc9ae1782009-01-30 01:42:37 +00001282
1283
showard8375ce02009-10-12 20:35:13 +00001284 def test_schedule_running_host_queue_entries_fail(self):
1285 self._create_job(hosts=[2])
1286 self._update_hqe("status='%s', execution_subdir=''" %
1287 models.HostQueueEntry.Status.PENDING)
1288 job = monitor_db.Job.fetch('id = 1')[0]
1289 queue_entry = monitor_db.HostQueueEntry.fetch('id = 1')[0]
1290 assert queue_entry.job is job
1291 job.run_if_ready(queue_entry)
1292 self.assertEqual(queue_entry.status,
1293 models.HostQueueEntry.Status.STARTING)
1294 self.assert_(queue_entry.execution_subdir)
1295 self.god.check_playback()
1296
1297 class dummy_test_agent(object):
1298 task = 'dummy_test_agent'
1299 self._dispatcher._register_agent_for_ids(
1300 self._dispatcher._host_agents, [queue_entry.host.id],
1301 dummy_test_agent)
1302
1303 # Attempted to schedule on a host that already has an agent.
1304 self.assertRaises(monitor_db.SchedulerError,
1305 self._dispatcher._schedule_running_host_queue_entries)
1306
1307
showardd07a5f32009-12-07 19:36:20 +00001308 def test_job_request_abort(self):
1309 django_job = self._create_job(hosts=[5, 6], atomic_group=1)
1310 job = monitor_db.Job(django_job.id)
1311 job.request_abort()
1312 django_hqes = list(models.HostQueueEntry.objects.filter(job=job.id))
1313 for hqe in django_hqes:
1314 self.assertTrue(hqe.aborted)
1315
1316
showard77182562009-06-10 00:16:05 +00001317 def test_run_if_ready_delays(self):
1318 # Also tests Job.run_with_ready_delay() on atomic group jobs.
1319 django_job = self._create_job(hosts=[5, 6], atomic_group=1)
1320 job = monitor_db.Job(django_job.id)
1321 self.assertEqual(1, job.synch_count)
1322 django_hqes = list(models.HostQueueEntry.objects.filter(job=job.id))
1323 self.assertEqual(2, len(django_hqes))
1324 self.assertEqual(2, django_hqes[0].atomic_group.max_number_of_machines)
1325
1326 def set_hqe_status(django_hqe, status):
1327 django_hqe.status = status
1328 django_hqe.save()
1329 monitor_db.HostQueueEntry(django_hqe.id).host.set_status(status)
1330
1331 # An initial state, our synch_count is 1
1332 set_hqe_status(django_hqes[0], models.HostQueueEntry.Status.VERIFYING)
1333 set_hqe_status(django_hqes[1], models.HostQueueEntry.Status.PENDING)
1334
1335 # So that we don't depend on the config file value during the test.
1336 self.assert_(scheduler_config.config
1337 .secs_to_wait_for_atomic_group_hosts is not None)
1338 self.god.stub_with(scheduler_config.config,
1339 'secs_to_wait_for_atomic_group_hosts', 123456)
1340
1341 # Get the pending one as a monitor_db.HostQueueEntry object.
showard8cc058f2009-09-08 16:26:33 +00001342 hqe = monitor_db.HostQueueEntry(django_hqes[1].id)
showard77182562009-06-10 00:16:05 +00001343 self.assert_(not job._delay_ready_task)
1344 self.assertTrue(job.is_ready())
1345
1346 # Ready with one pending, one verifying and an atomic group should
1347 # result in a DelayCallTask to re-check if we're ready a while later.
showard8cc058f2009-09-08 16:26:33 +00001348 job.run_if_ready(hqe)
1349 self.assertEquals('Waiting', hqe.status)
1350 self._dispatcher._schedule_delay_tasks()
1351 self.assertEquals('Pending', hqe.status)
1352 agent = self._dispatcher._agents[0]
showard77182562009-06-10 00:16:05 +00001353 self.assert_(job._delay_ready_task)
1354 self.assert_(isinstance(agent, monitor_db.Agent))
showard8cc058f2009-09-08 16:26:33 +00001355 self.assert_(agent.task)
1356 delay_task = agent.task
1357 self.assert_(isinstance(delay_task, monitor_db.DelayedCallTask))
showard77182562009-06-10 00:16:05 +00001358 self.assert_(not delay_task.is_done())
1359
showard8cc058f2009-09-08 16:26:33 +00001360 self.god.stub_function(delay_task, 'abort')
1361
showard77182562009-06-10 00:16:05 +00001362 self.god.stub_function(job, 'run')
1363
showardd2014822009-10-12 20:26:58 +00001364 self.god.stub_function(job, '_pending_count')
showardd07a5f32009-12-07 19:36:20 +00001365 self.god.stub_with(job, 'synch_count', 9)
1366 self.god.stub_function(job, 'request_abort')
showardd2014822009-10-12 20:26:58 +00001367
showard77182562009-06-10 00:16:05 +00001368 # Test that the DelayedCallTask's callback queued up above does the
showardd2014822009-10-12 20:26:58 +00001369 # correct thing and does not call run if there are not enough hosts
1370 # in pending after the delay.
showardd2014822009-10-12 20:26:58 +00001371 job._pending_count.expect_call().and_return(0)
showardd07a5f32009-12-07 19:36:20 +00001372 job.request_abort.expect_call()
showardd2014822009-10-12 20:26:58 +00001373 delay_task._callback()
1374 self.god.check_playback()
1375
1376 # Test that the DelayedCallTask's callback queued up above does the
1377 # correct thing and returns the Agent returned by job.run() if
1378 # there are still enough hosts pending after the delay.
showardd07a5f32009-12-07 19:36:20 +00001379 job.synch_count = 4
showardd2014822009-10-12 20:26:58 +00001380 job._pending_count.expect_call().and_return(4)
showard8cc058f2009-09-08 16:26:33 +00001381 job.run.expect_call(hqe)
1382 delay_task._callback()
1383 self.god.check_playback()
showard77182562009-06-10 00:16:05 +00001384
showardd2014822009-10-12 20:26:58 +00001385 job._pending_count.expect_call().and_return(4)
1386
showard77182562009-06-10 00:16:05 +00001387 # Adjust the delay deadline so that enough time has passed.
1388 job._delay_ready_task.end_time = time.time() - 111111
showard8cc058f2009-09-08 16:26:33 +00001389 job.run.expect_call(hqe)
showard77182562009-06-10 00:16:05 +00001390 # ...the delay_expired condition should cause us to call run()
showard8cc058f2009-09-08 16:26:33 +00001391 self._dispatcher._handle_agents()
1392 self.god.check_playback()
1393 delay_task.success = False
showard77182562009-06-10 00:16:05 +00001394
1395 # Adjust the delay deadline back so that enough time has not passed.
1396 job._delay_ready_task.end_time = time.time() + 111111
showard8cc058f2009-09-08 16:26:33 +00001397 self._dispatcher._handle_agents()
1398 self.god.check_playback()
showard77182562009-06-10 00:16:05 +00001399
showard77182562009-06-10 00:16:05 +00001400 # Now max_number_of_machines HQEs are in pending state. Remaining
1401 # delay will now be ignored.
showard8cc058f2009-09-08 16:26:33 +00001402 other_hqe = monitor_db.HostQueueEntry(django_hqes[0].id)
1403 self.god.unstub(job, 'run')
showardd2014822009-10-12 20:26:58 +00001404 self.god.unstub(job, '_pending_count')
showardd07a5f32009-12-07 19:36:20 +00001405 self.god.unstub(job, 'synch_count')
1406 self.god.unstub(job, 'request_abort')
showard77182562009-06-10 00:16:05 +00001407 # ...the over_max_threshold test should cause us to call run()
showard8cc058f2009-09-08 16:26:33 +00001408 delay_task.abort.expect_call()
1409 other_hqe.on_pending()
1410 self.assertEquals('Starting', other_hqe.status)
1411 self.assertEquals('Starting', hqe.status)
1412 self.god.stub_function(job, 'run')
1413 self.god.unstub(delay_task, 'abort')
showard77182562009-06-10 00:16:05 +00001414
showard8cc058f2009-09-08 16:26:33 +00001415 hqe.set_status('Pending')
1416 other_hqe.set_status('Pending')
showard708b3522009-08-20 23:26:15 +00001417 # Now we're not over the max for the atomic group. But all assigned
1418 # hosts are in pending state. over_max_threshold should make us run().
showard8cc058f2009-09-08 16:26:33 +00001419 hqe.atomic_group.max_number_of_machines += 1
1420 hqe.atomic_group.save()
1421 job.run.expect_call(hqe)
1422 hqe.on_pending()
1423 self.god.check_playback()
1424 hqe.atomic_group.max_number_of_machines -= 1
1425 hqe.atomic_group.save()
showard708b3522009-08-20 23:26:15 +00001426
showard77182562009-06-10 00:16:05 +00001427 other_hqe = monitor_db.HostQueueEntry(django_hqes[0].id)
showard8cc058f2009-09-08 16:26:33 +00001428 self.assertTrue(hqe.job is other_hqe.job)
showard77182562009-06-10 00:16:05 +00001429 # DBObject classes should reuse instances so these should be the same.
1430 self.assertEqual(job, other_hqe.job)
showard8cc058f2009-09-08 16:26:33 +00001431 self.assertEqual(other_hqe.job, hqe.job)
showard77182562009-06-10 00:16:05 +00001432 # Be sure our delay was not lost during the other_hqe construction.
showard8cc058f2009-09-08 16:26:33 +00001433 self.assertEqual(job._delay_ready_task, delay_task)
showard77182562009-06-10 00:16:05 +00001434 self.assert_(job._delay_ready_task)
1435 self.assertFalse(job._delay_ready_task.is_done())
1436 self.assertFalse(job._delay_ready_task.aborted)
1437
1438 # We want the real run() to be called below.
1439 self.god.unstub(job, 'run')
1440
1441 # We pass in the other HQE this time the same way it would happen
1442 # for real when one host finishes verifying and enters pending.
showard8cc058f2009-09-08 16:26:33 +00001443 job.run_if_ready(other_hqe)
showard77182562009-06-10 00:16:05 +00001444
1445 # The delayed task must be aborted by the actual run() call above.
1446 self.assertTrue(job._delay_ready_task.aborted)
1447 self.assertFalse(job._delay_ready_task.success)
1448 self.assertTrue(job._delay_ready_task.is_done())
1449
1450 # Check that job run() and _finish_run() were called by the above:
showard8cc058f2009-09-08 16:26:33 +00001451 self._dispatcher._schedule_running_host_queue_entries()
1452 agent = self._dispatcher._agents[0]
1453 self.assert_(agent.task)
1454 task = agent.task
1455 self.assert_(isinstance(task, monitor_db.QueueTask))
showard77182562009-06-10 00:16:05 +00001456 # Requery these hqes in order to verify the status from the DB.
1457 django_hqes = list(models.HostQueueEntry.objects.filter(job=job.id))
1458 for entry in django_hqes:
1459 self.assertEqual(models.HostQueueEntry.Status.STARTING,
1460 entry.status)
1461
1462 # We're already running, but more calls to run_with_ready_delay can
1463 # continue to come in due to straggler hosts enter Pending. Make
1464 # sure we don't do anything.
showard8cc058f2009-09-08 16:26:33 +00001465 self.god.stub_function(job, 'run')
1466 job.run_with_ready_delay(hqe)
1467 self.god.check_playback()
1468 self.god.unstub(job, 'run')
showard77182562009-06-10 00:16:05 +00001469
1470
1471 def test__atomic_and_has_started__on_atomic(self):
1472 self._create_job(hosts=[5, 6], atomic_group=1)
showard8cc058f2009-09-08 16:26:33 +00001473 job = monitor_db.Job.fetch('id = 1')[0]
showard77182562009-06-10 00:16:05 +00001474 self.assertFalse(job._atomic_and_has_started())
showardaf8b4ca2009-06-16 18:47:26 +00001475
showard77182562009-06-10 00:16:05 +00001476 self._update_hqe("status='Pending'")
1477 self.assertFalse(job._atomic_and_has_started())
1478 self._update_hqe("status='Verifying'")
1479 self.assertFalse(job._atomic_and_has_started())
showardaf8b4ca2009-06-16 18:47:26 +00001480 self.assertFalse(job._atomic_and_has_started())
1481 self._update_hqe("status='Failed'")
1482 self.assertFalse(job._atomic_and_has_started())
1483 self._update_hqe("status='Stopped'")
1484 self.assertFalse(job._atomic_and_has_started())
1485
showard77182562009-06-10 00:16:05 +00001486 self._update_hqe("status='Starting'")
1487 self.assertTrue(job._atomic_and_has_started())
1488 self._update_hqe("status='Completed'")
1489 self.assertTrue(job._atomic_and_has_started())
1490 self._update_hqe("status='Aborted'")
showard77182562009-06-10 00:16:05 +00001491
1492
1493 def test__atomic_and_has_started__not_atomic(self):
1494 self._create_job(hosts=[1, 2])
showard8cc058f2009-09-08 16:26:33 +00001495 job = monitor_db.Job.fetch('id = 1')[0]
showard77182562009-06-10 00:16:05 +00001496 self.assertFalse(job._atomic_and_has_started())
1497 self._update_hqe("status='Starting'")
1498 self.assertFalse(job._atomic_and_has_started())
1499
1500
showard8cc058f2009-09-08 16:26:33 +00001501 def _check_special_task(self, task, task_type, queue_entry_id=None):
1502 self.assertEquals(task.task, task_type)
1503 self.assertEquals(task.host.id, 1)
1504 if queue_entry_id:
1505 self.assertEquals(task.queue_entry.id, queue_entry_id)
1506
1507
showardb2e2c322008-10-14 17:33:55 +00001508 def test_run_asynchronous(self):
1509 self._create_job(hosts=[1, 2])
1510
showard8cc058f2009-09-08 16:26:33 +00001511 task = self._test_pre_job_tasks_helper()
showardb2e2c322008-10-14 17:33:55 +00001512
showard8cc058f2009-09-08 16:26:33 +00001513 self._check_special_task(task, models.SpecialTask.Task.VERIFY, 1)
showardb2e2c322008-10-14 17:33:55 +00001514
showardb2e2c322008-10-14 17:33:55 +00001515
showard9976ce92008-10-15 20:28:13 +00001516 def test_run_asynchronous_skip_verify(self):
1517 job = self._create_job(hosts=[1, 2])
1518 job.run_verify = False
1519 job.save()
1520
showard8cc058f2009-09-08 16:26:33 +00001521 task = self._test_pre_job_tasks_helper()
showard9976ce92008-10-15 20:28:13 +00001522
showard8cc058f2009-09-08 16:26:33 +00001523 self.assertEquals(task, None)
showard9976ce92008-10-15 20:28:13 +00001524
1525
showardb2e2c322008-10-14 17:33:55 +00001526 def test_run_synchronous_verify(self):
1527 self._create_job(hosts=[1, 2], synchronous=True)
1528
showard8cc058f2009-09-08 16:26:33 +00001529 task = self._test_pre_job_tasks_helper()
1530
1531 self._check_special_task(task, models.SpecialTask.Task.VERIFY, 1)
showardb2e2c322008-10-14 17:33:55 +00001532
1533
showard9976ce92008-10-15 20:28:13 +00001534 def test_run_synchronous_skip_verify(self):
1535 job = self._create_job(hosts=[1, 2], synchronous=True)
1536 job.run_verify = False
1537 job.save()
1538
showard8cc058f2009-09-08 16:26:33 +00001539 task = self._test_pre_job_tasks_helper()
1540
1541 self.assertEquals(task, None)
showard9976ce92008-10-15 20:28:13 +00001542
1543
showardb2e2c322008-10-14 17:33:55 +00001544 def test_run_synchronous_ready(self):
1545 self._create_job(hosts=[1, 2], synchronous=True)
showardd9ac4452009-02-07 02:04:37 +00001546 self._update_hqe("status='Pending', execution_subdir=''")
showardb2e2c322008-10-14 17:33:55 +00001547
showard8cc058f2009-09-08 16:26:33 +00001548 queue_task = self._test_run_helper(expect_starting=True)
showardb2e2c322008-10-14 17:33:55 +00001549
1550 self.assert_(isinstance(queue_task, monitor_db.QueueTask))
1551 self.assertEquals(queue_task.job.id, 1)
1552 hqe_ids = [hqe.id for hqe in queue_task.queue_entries]
1553 self.assertEquals(hqe_ids, [1, 2])
1554
1555
showard77182562009-06-10 00:16:05 +00001556 def test_run_atomic_group_already_started(self):
1557 self._create_job(hosts=[5, 6], atomic_group=1, synchronous=True)
1558 self._update_hqe("status='Starting', execution_subdir=''")
1559
showard8cc058f2009-09-08 16:26:33 +00001560 job = monitor_db.Job.fetch('id = 1')[0]
1561 queue_entry = monitor_db.HostQueueEntry.fetch('id = 1')[0]
showard77182562009-06-10 00:16:05 +00001562 assert queue_entry.job is job
1563 self.assertEqual(None, job.run(queue_entry))
1564
1565 self.god.check_playback()
1566
1567
showardf1ae3542009-05-11 19:26:02 +00001568 def test_run_synchronous_atomic_group_ready(self):
1569 self._create_job(hosts=[5, 6], atomic_group=1, synchronous=True)
1570 self._update_hqe("status='Pending', execution_subdir=''")
1571
showard8cc058f2009-09-08 16:26:33 +00001572 queue_task = self._test_run_helper(expect_starting=True)
showardf1ae3542009-05-11 19:26:02 +00001573
1574 self.assert_(isinstance(queue_task, monitor_db.QueueTask))
showard77182562009-06-10 00:16:05 +00001575 # Atomic group jobs that do not depend on a specific label in the
1576 # atomic group will use the atomic group name as their group name.
showardd1195652009-12-08 22:21:02 +00001577 self.assertEquals(queue_task.queue_entries[0].get_group_name(),
1578 'atomic1')
showardf1ae3542009-05-11 19:26:02 +00001579
1580
1581 def test_run_synchronous_atomic_group_with_label_ready(self):
1582 job = self._create_job(hosts=[5, 6], atomic_group=1, synchronous=True)
1583 job.dependency_labels.add(self.label4)
1584 self._update_hqe("status='Pending', execution_subdir=''")
1585
showard8cc058f2009-09-08 16:26:33 +00001586 queue_task = self._test_run_helper(expect_starting=True)
showardf1ae3542009-05-11 19:26:02 +00001587
1588 self.assert_(isinstance(queue_task, monitor_db.QueueTask))
1589 # Atomic group jobs that also specify a label in the atomic group
1590 # will use the label name as their group name.
showardd1195652009-12-08 22:21:02 +00001591 self.assertEquals(queue_task.queue_entries[0].get_group_name(),
1592 'label4')
showardf1ae3542009-05-11 19:26:02 +00001593
1594
showard21baa452008-10-21 00:08:39 +00001595 def test_reboot_before_always(self):
1596 job = self._create_job(hosts=[1])
showard0fc38302008-10-23 00:44:07 +00001597 job.reboot_before = models.RebootBefore.ALWAYS
showard21baa452008-10-21 00:08:39 +00001598 job.save()
1599
showard8cc058f2009-09-08 16:26:33 +00001600 task = self._test_pre_job_tasks_helper()
1601
1602 self._check_special_task(task, models.SpecialTask.Task.CLEANUP)
showard21baa452008-10-21 00:08:39 +00001603
1604
1605 def _test_reboot_before_if_dirty_helper(self, expect_reboot):
1606 job = self._create_job(hosts=[1])
showard0fc38302008-10-23 00:44:07 +00001607 job.reboot_before = models.RebootBefore.IF_DIRTY
showard21baa452008-10-21 00:08:39 +00001608 job.save()
1609
showard8cc058f2009-09-08 16:26:33 +00001610 task = self._test_pre_job_tasks_helper()
showard21baa452008-10-21 00:08:39 +00001611 if expect_reboot:
showard8cc058f2009-09-08 16:26:33 +00001612 task_type = models.SpecialTask.Task.CLEANUP
1613 else:
1614 task_type = models.SpecialTask.Task.VERIFY
1615 self._check_special_task(task, task_type)
showard21baa452008-10-21 00:08:39 +00001616
showard77182562009-06-10 00:16:05 +00001617
showard21baa452008-10-21 00:08:39 +00001618 def test_reboot_before_if_dirty(self):
1619 models.Host.smart_get(1).update_object(dirty=True)
1620 self._test_reboot_before_if_dirty_helper(True)
1621
1622
1623 def test_reboot_before_not_dirty(self):
1624 models.Host.smart_get(1).update_object(dirty=False)
1625 self._test_reboot_before_if_dirty_helper(False)
1626
1627
showardf1ae3542009-05-11 19:26:02 +00001628 def test_next_group_name(self):
1629 django_job = self._create_job(metahosts=[1])
1630 job = monitor_db.Job(id=django_job.id)
1631 self.assertEqual('group0', job._next_group_name())
1632
1633 for hqe in django_job.hostqueueentry_set.filter():
1634 hqe.execution_subdir = 'my_rack.group0'
1635 hqe.save()
1636 self.assertEqual('my_rack.group1', job._next_group_name('my/rack'))
1637
1638
1639class TopLevelFunctionsTest(unittest.TestCase):
mblighe7d9c602009-07-02 19:02:33 +00001640 def setUp(self):
1641 self.god = mock.mock_god()
1642
1643
1644 def tearDown(self):
1645 self.god.unstub_all()
1646
1647
showardf1ae3542009-05-11 19:26:02 +00001648 def test_autoserv_command_line(self):
1649 machines = 'abcd12,efgh34'
showardf1ae3542009-05-11 19:26:02 +00001650 extra_args = ['-Z', 'hello']
1651 expected_command_line = [monitor_db._autoserv_path, '-p',
showarded2afea2009-07-07 20:54:07 +00001652 '-m', machines, '-r',
1653 drone_manager.WORKING_DIRECTORY]
showardf1ae3542009-05-11 19:26:02 +00001654
showarded2afea2009-07-07 20:54:07 +00001655 command_line = monitor_db._autoserv_command_line(machines, extra_args)
showarde9c69362009-06-30 01:58:03 +00001656 self.assertEqual(expected_command_line + ['--verbose'] + extra_args,
1657 command_line)
showardf1ae3542009-05-11 19:26:02 +00001658
1659 class FakeJob(object):
1660 owner = 'Bob'
1661 name = 'fake job name'
mblighe7d9c602009-07-02 19:02:33 +00001662 id = 1337
1663
1664 class FakeHQE(object):
1665 job = FakeJob
showardf1ae3542009-05-11 19:26:02 +00001666
1667 command_line = monitor_db._autoserv_command_line(
showarded2afea2009-07-07 20:54:07 +00001668 machines, extra_args=[], queue_entry=FakeHQE, verbose=False)
showardf1ae3542009-05-11 19:26:02 +00001669 self.assertEqual(expected_command_line +
1670 ['-u', FakeJob.owner, '-l', FakeJob.name],
1671 command_line)
1672
showard21baa452008-10-21 00:08:39 +00001673
showardce38e0c2008-05-29 19:36:16 +00001674if __name__ == '__main__':
jadmanski0afbb632008-06-06 21:10:57 +00001675 unittest.main()