blob: bf5014c0980aaef2a5401da0fba639d4de38536e [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
showardce38e0c2008-05-29 19:36:16 +00004import common
mbligh8bcd23a2009-02-03 19:14:06 +00005import MySQLdb
showard364fe862008-10-17 02:01:16 +00006from autotest_lib.frontend import setup_django_environment
showardb6d16622009-05-26 19:35:29 +00007from autotest_lib.frontend.afe import frontend_test_utils
jadmanskifb7cfb12008-07-09 14:13:21 +00008from autotest_lib.client.common_lib import global_config, host_protections
jadmanski3d161b02008-06-06 15:43:36 +00009from autotest_lib.client.common_lib.test_utils import mock
showard442e71e2008-10-06 10:05:20 +000010from autotest_lib.database import database_connection, migrate
showard21baa452008-10-21 00:08:39 +000011from autotest_lib.frontend import thread_local
showardb1e51872008-10-07 11:08:18 +000012from autotest_lib.frontend.afe import models
showard170873e2009-01-07 00:22:26 +000013from autotest_lib.scheduler import monitor_db, drone_manager, email_manager
showardd1ee1dd2009-01-07 21:33:08 +000014from autotest_lib.scheduler import scheduler_config
showardce38e0c2008-05-29 19:36:16 +000015
16_DEBUG = False
17
showarda3c58572009-03-12 20:36:59 +000018
showard170873e2009-01-07 00:22:26 +000019class DummyAgent(object):
20 _is_running = False
21 _is_done = False
22 num_processes = 1
23 host_ids = []
24 queue_entry_ids = []
25
26 def is_running(self):
27 return self._is_running
28
29
30 def tick(self):
31 self._is_running = True
32
33
34 def is_done(self):
35 return self._is_done
36
37
38 def set_done(self, done):
39 self._is_done = done
40 self._is_running = not done
showard04c82c52008-05-29 19:38:12 +000041
showard56193bb2008-08-13 20:07:41 +000042
43class IsRow(mock.argument_comparator):
44 def __init__(self, row_id):
45 self.row_id = row_id
showardce38e0c2008-05-29 19:36:16 +000046
47
showard56193bb2008-08-13 20:07:41 +000048 def is_satisfied_by(self, parameter):
49 return list(parameter)[0] == self.row_id
50
51
52 def __str__(self):
53 return 'row with id %s' % self.row_id
54
55
showardd3dc1992009-04-22 21:01:40 +000056class IsAgentWithTask(mock.argument_comparator):
57 def __init__(self, task):
58 self._task = task
59
60
61 def is_satisfied_by(self, parameter):
62 if not isinstance(parameter, monitor_db.Agent):
63 return False
64 tasks = list(parameter.queue.queue)
65 if len(tasks) != 1:
66 return False
67 return tasks[0] == self._task
68
69
showard6b733412009-04-27 20:09:18 +000070def _set_host_and_qe_ids(agent_or_task, id_list=None):
71 if id_list is None:
72 id_list = []
73 agent_or_task.host_ids = agent_or_task.queue_entry_ids = id_list
74
75
showardb6d16622009-05-26 19:35:29 +000076class BaseSchedulerTest(unittest.TestCase,
77 frontend_test_utils.FrontendTestMixin):
showard50c0e712008-09-22 16:20:37 +000078 _config_section = 'AUTOTEST_WEB'
showardce38e0c2008-05-29 19:36:16 +000079
jadmanski0afbb632008-06-06 21:10:57 +000080 def _do_query(self, sql):
showardb1e51872008-10-07 11:08:18 +000081 self._database.execute(sql)
showardce38e0c2008-05-29 19:36:16 +000082
83
showardb6d16622009-05-26 19:35:29 +000084 def _set_monitor_stubs(self):
85 # Clear the instance cache as this is a brand new database.
86 monitor_db.DBObject._clear_instance_cache()
showardce38e0c2008-05-29 19:36:16 +000087
showardb1e51872008-10-07 11:08:18 +000088 self._database = (
89 database_connection.DatabaseConnection.get_test_database(
90 self._test_db_file))
91 self._database.connect()
92 self._database.debug = _DEBUG
showardce38e0c2008-05-29 19:36:16 +000093
showardb1e51872008-10-07 11:08:18 +000094 monitor_db._db = self._database
showard170873e2009-01-07 00:22:26 +000095 monitor_db._drone_manager._results_dir = '/test/path'
96 monitor_db._drone_manager._temporary_directory = '/test/path/tmp'
showard56193bb2008-08-13 20:07:41 +000097
98
showard56193bb2008-08-13 20:07:41 +000099 def setUp(self):
showardb6d16622009-05-26 19:35:29 +0000100 self._frontend_common_setup()
showard56193bb2008-08-13 20:07:41 +0000101 self._set_monitor_stubs()
102 self._dispatcher = monitor_db.Dispatcher()
showardce38e0c2008-05-29 19:36:16 +0000103
104
showard56193bb2008-08-13 20:07:41 +0000105 def tearDown(self):
showardb6d16622009-05-26 19:35:29 +0000106 self._database.disconnect()
107 self._frontend_common_teardown()
showardce38e0c2008-05-29 19:36:16 +0000108
109
showard56193bb2008-08-13 20:07:41 +0000110 def _update_hqe(self, set, where=''):
111 query = 'UPDATE host_queue_entries SET ' + set
112 if where:
113 query += ' WHERE ' + where
114 self._do_query(query)
115
116
showarda3c58572009-03-12 20:36:59 +0000117class DBObjectTest(BaseSchedulerTest):
118 # It may seem odd to subclass BaseSchedulerTest for this but it saves us
119 # duplicating some setup work for what we want to test.
120
121
122 def test_compare_fields_in_row(self):
123 host = monitor_db.Host(id=1)
124 fields = list(host._fields)
125 row_data = [getattr(host, fieldname) for fieldname in fields]
126 self.assertEqual({}, host._compare_fields_in_row(row_data))
127 row_data[fields.index('hostname')] = 'spam'
128 self.assertEqual({'hostname': ('host1', 'spam')},
129 host._compare_fields_in_row(row_data))
130 row_data[fields.index('id')] = 23
131 self.assertEqual({'hostname': ('host1', 'spam'), 'id': (1, 23)},
132 host._compare_fields_in_row(row_data))
133
134
135 def test_always_query(self):
136 host_a = monitor_db.Host(id=2)
137 self.assertEqual(host_a.hostname, 'host2')
138 self._do_query('UPDATE hosts SET hostname="host2-updated" WHERE id=2')
139 host_b = monitor_db.Host(id=2, always_query=True)
140 self.assert_(host_a is host_b, 'Cached instance not returned.')
141 self.assertEqual(host_a.hostname, 'host2-updated',
142 'Database was not re-queried')
143
144 # If either of these are called, a query was made when it shouldn't be.
145 host_a._compare_fields_in_row = lambda _: self.fail('eek! a query!')
showard12f3e322009-05-13 21:27:42 +0000146 host_a._update_fields_from_row = host_a._compare_fields_in_row
showarda3c58572009-03-12 20:36:59 +0000147 host_c = monitor_db.Host(id=2, always_query=False)
148 self.assert_(host_a is host_c, 'Cached instance not returned')
149
150
151 def test_delete(self):
152 host = monitor_db.Host(id=3)
153 host.delete()
154 host = self.assertRaises(monitor_db.DBError, monitor_db.Host, id=3,
155 always_query=False)
156 host = self.assertRaises(monitor_db.DBError, monitor_db.Host, id=3,
157 always_query=True)
158
showard76e29d12009-04-15 21:53:10 +0000159 def test_save(self):
160 # Dummy Job to avoid creating a one in the HostQueueEntry __init__.
161 class MockJob(object):
162 def __init__(self, id):
163 pass
164 def tag(self):
165 return 'MockJob'
166 self.god.stub_with(monitor_db, 'Job', MockJob)
167 hqe = monitor_db.HostQueueEntry(
168 new_record=True,
showard12f3e322009-05-13 21:27:42 +0000169 row=[0, 1, 2, 'Queued', None, 0, 0, 0, '.', None, False, None])
showard76e29d12009-04-15 21:53:10 +0000170 hqe.save()
171 new_id = hqe.id
172 # Force a re-query and verify that the correct data was stored.
173 monitor_db.DBObject._clear_instance_cache()
174 hqe = monitor_db.HostQueueEntry(id=new_id)
175 self.assertEqual(hqe.id, new_id)
176 self.assertEqual(hqe.job_id, 1)
177 self.assertEqual(hqe.host_id, 2)
178 self.assertEqual(hqe.status, 'Queued')
179 self.assertEqual(hqe.meta_host, None)
180 self.assertEqual(hqe.active, False)
181 self.assertEqual(hqe.complete, False)
182 self.assertEqual(hqe.deleted, False)
183 self.assertEqual(hqe.execution_subdir, '.')
184 self.assertEqual(hqe.atomic_group_id, None)
showard12f3e322009-05-13 21:27:42 +0000185 self.assertEqual(hqe.started_on, None)
showarda3c58572009-03-12 20:36:59 +0000186
187
showardb2e2c322008-10-14 17:33:55 +0000188class DispatcherSchedulingTest(BaseSchedulerTest):
showard56193bb2008-08-13 20:07:41 +0000189 _jobs_scheduled = []
190
showard89f84db2009-03-12 20:39:13 +0000191
192 def tearDown(self):
193 super(DispatcherSchedulingTest, self).tearDown()
194
195
showard56193bb2008-08-13 20:07:41 +0000196 def _set_monitor_stubs(self):
197 super(DispatcherSchedulingTest, self)._set_monitor_stubs()
showard89f84db2009-03-12 20:39:13 +0000198
showard77182562009-06-10 00:16:05 +0000199 def hqe__do_run_pre_job_tasks_stub(queue_entry):
200 """Return a test dummy. Called by HostQueueEntry.run()."""
201 self._record_job_scheduled(queue_entry.job.id, queue_entry.host.id)
showard89f84db2009-03-12 20:39:13 +0000202 queue_entry.set_status('Starting')
showard170873e2009-01-07 00:22:26 +0000203 return DummyAgent()
showard89f84db2009-03-12 20:39:13 +0000204
showard77182562009-06-10 00:16:05 +0000205 self.god.stub_with(monitor_db.HostQueueEntry, '_do_run_pre_job_tasks',
206 hqe__do_run_pre_job_tasks_stub)
showard89f84db2009-03-12 20:39:13 +0000207
208 def hqe_queue_log_record_stub(self, log_line):
209 """No-Op to avoid calls down to the _drone_manager during tests."""
210
211 self.god.stub_with(monitor_db.HostQueueEntry, 'queue_log_record',
212 hqe_queue_log_record_stub)
showard56193bb2008-08-13 20:07:41 +0000213
214
215 def _record_job_scheduled(self, job_id, host_id):
216 record = (job_id, host_id)
217 self.assert_(record not in self._jobs_scheduled,
218 'Job %d scheduled on host %d twice' %
219 (job_id, host_id))
220 self._jobs_scheduled.append(record)
221
222
223 def _assert_job_scheduled_on(self, job_id, host_id):
224 record = (job_id, host_id)
225 self.assert_(record in self._jobs_scheduled,
226 'Job %d not scheduled on host %d as expected\n'
227 'Jobs scheduled: %s' %
228 (job_id, host_id, self._jobs_scheduled))
229 self._jobs_scheduled.remove(record)
230
231
showard89f84db2009-03-12 20:39:13 +0000232 def _assert_job_scheduled_on_number_of(self, job_id, host_ids, number):
233 """Assert job was scheduled on exactly number hosts out of a set."""
234 found = []
235 for host_id in host_ids:
236 record = (job_id, host_id)
237 if record in self._jobs_scheduled:
238 found.append(record)
239 self._jobs_scheduled.remove(record)
240 if len(found) < number:
241 self.fail('Job %d scheduled on fewer than %d hosts in %s.\n'
242 'Jobs scheduled: %s' % (job_id, number, host_ids, found))
243 elif len(found) > number:
244 self.fail('Job %d scheduled on more than %d hosts in %s.\n'
245 'Jobs scheduled: %s' % (job_id, number, host_ids, found))
246
247
showard56193bb2008-08-13 20:07:41 +0000248 def _check_for_extra_schedulings(self):
249 if len(self._jobs_scheduled) != 0:
250 self.fail('Extra jobs scheduled: ' +
251 str(self._jobs_scheduled))
252
253
jadmanski0afbb632008-06-06 21:10:57 +0000254 def _convert_jobs_to_metahosts(self, *job_ids):
255 sql_tuple = '(' + ','.join(str(i) for i in job_ids) + ')'
256 self._do_query('UPDATE host_queue_entries SET '
257 'meta_host=host_id, host_id=NULL '
258 'WHERE job_id IN ' + sql_tuple)
showardce38e0c2008-05-29 19:36:16 +0000259
260
jadmanski0afbb632008-06-06 21:10:57 +0000261 def _lock_host(self, host_id):
262 self._do_query('UPDATE hosts SET locked=1 WHERE id=' +
263 str(host_id))
showardce38e0c2008-05-29 19:36:16 +0000264
265
jadmanski0afbb632008-06-06 21:10:57 +0000266 def setUp(self):
showard56193bb2008-08-13 20:07:41 +0000267 super(DispatcherSchedulingTest, self).setUp()
jadmanski0afbb632008-06-06 21:10:57 +0000268 self._jobs_scheduled = []
showardce38e0c2008-05-29 19:36:16 +0000269
270
jadmanski0afbb632008-06-06 21:10:57 +0000271 def _test_basic_scheduling_helper(self, use_metahosts):
272 'Basic nonmetahost scheduling'
273 self._create_job_simple([1], use_metahosts)
274 self._create_job_simple([2], use_metahosts)
275 self._dispatcher._schedule_new_jobs()
276 self._assert_job_scheduled_on(1, 1)
277 self._assert_job_scheduled_on(2, 2)
278 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000279
280
jadmanski0afbb632008-06-06 21:10:57 +0000281 def _test_priorities_helper(self, use_metahosts):
282 'Test prioritization ordering'
283 self._create_job_simple([1], use_metahosts)
284 self._create_job_simple([2], use_metahosts)
285 self._create_job_simple([1,2], use_metahosts)
286 self._create_job_simple([1], use_metahosts, priority=1)
287 self._dispatcher._schedule_new_jobs()
288 self._assert_job_scheduled_on(4, 1) # higher priority
289 self._assert_job_scheduled_on(2, 2) # earlier job over later
290 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000291
292
jadmanski0afbb632008-06-06 21:10:57 +0000293 def _test_hosts_ready_helper(self, use_metahosts):
294 """
295 Only hosts that are status=Ready, unlocked and not invalid get
296 scheduled.
297 """
298 self._create_job_simple([1], use_metahosts)
299 self._do_query('UPDATE hosts SET status="Running" WHERE id=1')
300 self._dispatcher._schedule_new_jobs()
301 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000302
jadmanski0afbb632008-06-06 21:10:57 +0000303 self._do_query('UPDATE hosts SET status="Ready", locked=1 '
304 'WHERE id=1')
305 self._dispatcher._schedule_new_jobs()
306 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000307
jadmanski0afbb632008-06-06 21:10:57 +0000308 self._do_query('UPDATE hosts SET locked=0, invalid=1 '
309 'WHERE id=1')
310 self._dispatcher._schedule_new_jobs()
showard5df2b192008-07-03 19:51:57 +0000311 if not use_metahosts:
312 self._assert_job_scheduled_on(1, 1)
jadmanski0afbb632008-06-06 21:10:57 +0000313 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000314
315
jadmanski0afbb632008-06-06 21:10:57 +0000316 def _test_hosts_idle_helper(self, use_metahosts):
317 'Only idle hosts get scheduled'
showard2bab8f42008-11-12 18:15:22 +0000318 self._create_job(hosts=[1], active=True)
jadmanski0afbb632008-06-06 21:10:57 +0000319 self._create_job_simple([1], use_metahosts)
320 self._dispatcher._schedule_new_jobs()
321 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000322
323
showard63a34772008-08-18 19:32:50 +0000324 def _test_obey_ACLs_helper(self, use_metahosts):
325 self._do_query('DELETE FROM acl_groups_hosts WHERE host_id=1')
326 self._create_job_simple([1], use_metahosts)
327 self._dispatcher._schedule_new_jobs()
328 self._check_for_extra_schedulings()
329
330
jadmanski0afbb632008-06-06 21:10:57 +0000331 def test_basic_scheduling(self):
332 self._test_basic_scheduling_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000333
334
jadmanski0afbb632008-06-06 21:10:57 +0000335 def test_priorities(self):
336 self._test_priorities_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000337
338
jadmanski0afbb632008-06-06 21:10:57 +0000339 def test_hosts_ready(self):
340 self._test_hosts_ready_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000341
342
jadmanski0afbb632008-06-06 21:10:57 +0000343 def test_hosts_idle(self):
344 self._test_hosts_idle_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000345
346
showard63a34772008-08-18 19:32:50 +0000347 def test_obey_ACLs(self):
348 self._test_obey_ACLs_helper(False)
349
350
showard2924b0a2009-06-18 23:16:15 +0000351 def test_one_time_hosts_ignore_ACLs(self):
352 self._do_query('DELETE FROM acl_groups_hosts WHERE host_id=1')
353 self._do_query('UPDATE hosts SET invalid=1 WHERE id=1')
354 self._create_job_simple([1])
355 self._dispatcher._schedule_new_jobs()
356 self._assert_job_scheduled_on(1, 1)
357 self._check_for_extra_schedulings()
358
359
showard63a34772008-08-18 19:32:50 +0000360 def test_non_metahost_on_invalid_host(self):
361 """
362 Non-metahost entries can get scheduled on invalid hosts (this is how
363 one-time hosts work).
364 """
365 self._do_query('UPDATE hosts SET invalid=1')
366 self._test_basic_scheduling_helper(False)
367
368
jadmanski0afbb632008-06-06 21:10:57 +0000369 def test_metahost_scheduling(self):
showard63a34772008-08-18 19:32:50 +0000370 """
371 Basic metahost scheduling
372 """
jadmanski0afbb632008-06-06 21:10:57 +0000373 self._test_basic_scheduling_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000374
375
jadmanski0afbb632008-06-06 21:10:57 +0000376 def test_metahost_priorities(self):
377 self._test_priorities_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000378
379
jadmanski0afbb632008-06-06 21:10:57 +0000380 def test_metahost_hosts_ready(self):
381 self._test_hosts_ready_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000382
383
jadmanski0afbb632008-06-06 21:10:57 +0000384 def test_metahost_hosts_idle(self):
385 self._test_hosts_idle_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000386
387
showard63a34772008-08-18 19:32:50 +0000388 def test_metahost_obey_ACLs(self):
389 self._test_obey_ACLs_helper(True)
390
391
showard89f84db2009-03-12 20:39:13 +0000392 def _setup_test_only_if_needed_labels(self):
showardade14e22009-01-26 22:38:32 +0000393 # apply only_if_needed label3 to host1
showard89f84db2009-03-12 20:39:13 +0000394 models.Host.smart_get('host1').labels.add(self.label3)
395 return self._create_job_simple([1], use_metahost=True)
showardade14e22009-01-26 22:38:32 +0000396
showard89f84db2009-03-12 20:39:13 +0000397
398 def test_only_if_needed_labels_avoids_host(self):
399 job = self._setup_test_only_if_needed_labels()
showardade14e22009-01-26 22:38:32 +0000400 # if the job doesn't depend on label3, there should be no scheduling
401 self._dispatcher._schedule_new_jobs()
402 self._check_for_extra_schedulings()
403
showard89f84db2009-03-12 20:39:13 +0000404
405 def test_only_if_needed_labels_schedules(self):
406 job = self._setup_test_only_if_needed_labels()
407 job.dependency_labels.add(self.label3)
showardade14e22009-01-26 22:38:32 +0000408 self._dispatcher._schedule_new_jobs()
409 self._assert_job_scheduled_on(1, 1)
410 self._check_for_extra_schedulings()
411
showard89f84db2009-03-12 20:39:13 +0000412
413 def test_only_if_needed_labels_via_metahost(self):
414 job = self._setup_test_only_if_needed_labels()
415 job.dependency_labels.add(self.label3)
showardade14e22009-01-26 22:38:32 +0000416 # should also work if the metahost is the only_if_needed label
417 self._do_query('DELETE FROM jobs_dependency_labels')
418 self._create_job(metahosts=[3])
419 self._dispatcher._schedule_new_jobs()
420 self._assert_job_scheduled_on(2, 1)
421 self._check_for_extra_schedulings()
showard989f25d2008-10-01 11:38:11 +0000422
423
jadmanski0afbb632008-06-06 21:10:57 +0000424 def test_nonmetahost_over_metahost(self):
425 """
426 Non-metahost entries should take priority over metahost entries
427 for the same host
428 """
429 self._create_job(metahosts=[1])
430 self._create_job(hosts=[1])
431 self._dispatcher._schedule_new_jobs()
432 self._assert_job_scheduled_on(2, 1)
433 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000434
435
jadmanski0afbb632008-06-06 21:10:57 +0000436 def test_metahosts_obey_blocks(self):
437 """
438 Metahosts can't get scheduled on hosts already scheduled for
439 that job.
440 """
441 self._create_job(metahosts=[1], hosts=[1])
442 # make the nonmetahost entry complete, so the metahost can try
443 # to get scheduled
showard56193bb2008-08-13 20:07:41 +0000444 self._update_hqe(set='complete = 1', where='host_id=1')
jadmanski0afbb632008-06-06 21:10:57 +0000445 self._dispatcher._schedule_new_jobs()
446 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000447
448
showard89f84db2009-03-12 20:39:13 +0000449 # TODO(gps): These should probably live in their own TestCase class
450 # specific to testing HostScheduler methods directly. It was convenient
451 # to put it here for now to share existing test environment setup code.
452 def test_HostScheduler_check_atomic_group_labels(self):
453 normal_job = self._create_job(metahosts=[0])
454 atomic_job = self._create_job(atomic_group=1)
455 # Indirectly initialize the internal state of the host scheduler.
456 self._dispatcher._refresh_pending_queue_entries()
457
458 atomic_hqe = monitor_db.HostQueueEntry(id=atomic_job.id)
459 normal_hqe = monitor_db.HostQueueEntry(id=normal_job.id)
460
461 host_scheduler = self._dispatcher._host_scheduler
462 self.assertTrue(host_scheduler._check_atomic_group_labels(
463 [self.label4.id], atomic_hqe))
464 self.assertFalse(host_scheduler._check_atomic_group_labels(
465 [self.label4.id], normal_hqe))
466 self.assertFalse(host_scheduler._check_atomic_group_labels(
467 [self.label5.id, self.label6.id, self.label7.id], normal_hqe))
468 self.assertTrue(host_scheduler._check_atomic_group_labels(
469 [self.label4.id, self.label6.id], atomic_hqe))
470 self.assertRaises(monitor_db.SchedulerError,
471 host_scheduler._check_atomic_group_labels,
472 [self.label4.id, self.label5.id],
473 atomic_hqe)
474
475
476 def test_HostScheduler_get_host_atomic_group_id(self):
477 self._create_job(metahosts=[self.label6.id])
478 # Indirectly initialize the internal state of the host scheduler.
479 self._dispatcher._refresh_pending_queue_entries()
480
481 # Test the host scheduler
482 host_scheduler = self._dispatcher._host_scheduler
483 self.assertRaises(monitor_db.SchedulerError,
484 host_scheduler._get_host_atomic_group_id,
485 [self.label4.id, self.label5.id])
486 self.assertEqual(None, host_scheduler._get_host_atomic_group_id([]))
487 self.assertEqual(None, host_scheduler._get_host_atomic_group_id(
488 [self.label3.id, self.label7.id, self.label6.id]))
489 self.assertEqual(1, host_scheduler._get_host_atomic_group_id(
490 [self.label4.id, self.label7.id, self.label6.id]))
491 self.assertEqual(1, host_scheduler._get_host_atomic_group_id(
492 [self.label7.id, self.label5.id]))
493
494
495 def test_atomic_group_hosts_blocked_from_non_atomic_jobs(self):
496 # Create a job scheduled to run on label6.
497 self._create_job(metahosts=[self.label6.id])
498 self._dispatcher._schedule_new_jobs()
499 # label6 only has hosts that are in atomic groups associated with it,
500 # there should be no scheduling.
501 self._check_for_extra_schedulings()
502
503
504 def test_atomic_group_hosts_blocked_from_non_atomic_jobs_explicit(self):
505 # Create a job scheduled to run on label5. This is an atomic group
506 # label but this job does not request atomic group scheduling.
507 self._create_job(metahosts=[self.label5.id])
508 self._dispatcher._schedule_new_jobs()
509 # label6 only has hosts that are in atomic groups associated with it,
510 # there should be no scheduling.
511 self._check_for_extra_schedulings()
512
513
514 def test_atomic_group_scheduling_basics(self):
515 # Create jobs scheduled to run on an atomic group.
516 job_a = self._create_job(synchronous=True, metahosts=[self.label4.id],
517 atomic_group=1)
518 job_b = self._create_job(synchronous=True, metahosts=[self.label5.id],
519 atomic_group=1)
520 self._dispatcher._schedule_new_jobs()
521 # atomic_group.max_number_of_machines was 2 so we should run on 2.
522 self._assert_job_scheduled_on_number_of(job_a.id, (5, 6, 7), 2)
523 self._assert_job_scheduled_on(job_b.id, 8) # label5
524 self._assert_job_scheduled_on(job_b.id, 9) # label5
525 self._check_for_extra_schedulings()
526
527 # The three host label4 atomic group still has one host available.
528 # That means a job with a synch_count of 1 asking to be scheduled on
529 # the atomic group can still use the final machine.
530 #
531 # This may seem like a somewhat odd use case. It allows the use of an
532 # atomic group as a set of machines to run smaller jobs within (a set
533 # of hosts configured for use in network tests with eachother perhaps?)
534 onehost_job = self._create_job(atomic_group=1)
535 self._dispatcher._schedule_new_jobs()
536 self._assert_job_scheduled_on_number_of(onehost_job.id, (5, 6, 7), 1)
537 self._check_for_extra_schedulings()
538
539 # No more atomic groups have hosts available, no more jobs should
540 # be scheduled.
541 self._create_job(atomic_group=1)
542 self._dispatcher._schedule_new_jobs()
543 self._check_for_extra_schedulings()
544
545
546 def test_atomic_group_scheduling_obeys_acls(self):
547 # Request scheduling on a specific atomic label but be denied by ACLs.
548 self._do_query('DELETE FROM acl_groups_hosts WHERE host_id in (8,9)')
549 job = self._create_job(metahosts=[self.label5.id], atomic_group=1)
550 self._dispatcher._schedule_new_jobs()
551 self._check_for_extra_schedulings()
552
553
554 def test_atomic_group_scheduling_dependency_label_exclude(self):
555 # A dependency label that matches no hosts in the atomic group.
556 job_a = self._create_job(atomic_group=1)
557 job_a.dependency_labels.add(self.label3)
558 self._dispatcher._schedule_new_jobs()
559 self._check_for_extra_schedulings()
560
561
562 def test_atomic_group_scheduling_metahost_dependency_label_exclude(self):
563 # A metahost and dependency label that excludes too many hosts.
564 job_b = self._create_job(synchronous=True, metahosts=[self.label4.id],
565 atomic_group=1)
566 job_b.dependency_labels.add(self.label7)
567 self._dispatcher._schedule_new_jobs()
568 self._check_for_extra_schedulings()
569
570
571 def test_atomic_group_scheduling_dependency_label_match(self):
572 # A dependency label that exists on enough atomic group hosts in only
573 # one of the two atomic group labels.
574 job_c = self._create_job(synchronous=True, atomic_group=1)
575 job_c.dependency_labels.add(self.label7)
576 self._dispatcher._schedule_new_jobs()
577 self._assert_job_scheduled_on_number_of(job_c.id, (8, 9), 2)
578 self._check_for_extra_schedulings()
579
580
581 def test_atomic_group_scheduling_no_metahost(self):
582 # Force it to schedule on the other group for a reliable test.
583 self._do_query('UPDATE hosts SET invalid=1 WHERE id=9')
584 # An atomic job without a metahost.
585 job = self._create_job(synchronous=True, atomic_group=1)
586 self._dispatcher._schedule_new_jobs()
587 self._assert_job_scheduled_on_number_of(job.id, (5, 6, 7), 2)
588 self._check_for_extra_schedulings()
589
590
591 def test_atomic_group_scheduling_partial_group(self):
592 # Make one host in labels[3] unavailable so that there are only two
593 # hosts left in the group.
594 self._do_query('UPDATE hosts SET status="Repair Failed" WHERE id=5')
595 job = self._create_job(synchronous=True, metahosts=[self.label4.id],
596 atomic_group=1)
597 self._dispatcher._schedule_new_jobs()
598 # Verify that it was scheduled on the 2 ready hosts in that group.
599 self._assert_job_scheduled_on(job.id, 6)
600 self._assert_job_scheduled_on(job.id, 7)
601 self._check_for_extra_schedulings()
602
603
604 def test_atomic_group_scheduling_not_enough_available(self):
605 # Mark some hosts in each atomic group label as not usable.
606 # One host running, another invalid in the first group label.
607 self._do_query('UPDATE hosts SET status="Running" WHERE id=5')
608 self._do_query('UPDATE hosts SET invalid=1 WHERE id=6')
609 # One host invalid in the second group label.
610 self._do_query('UPDATE hosts SET invalid=1 WHERE id=9')
611 # Nothing to schedule when no group label has enough (2) good hosts..
612 self._create_job(atomic_group=1, synchronous=True)
613 self._dispatcher._schedule_new_jobs()
614 # There are not enough hosts in either atomic group,
615 # No more scheduling should occur.
616 self._check_for_extra_schedulings()
617
618 # Now create an atomic job that has a synch count of 1. It should
619 # schedule on exactly one of the hosts.
620 onehost_job = self._create_job(atomic_group=1)
621 self._dispatcher._schedule_new_jobs()
622 self._assert_job_scheduled_on_number_of(onehost_job.id, (7, 8), 1)
623
624
625 def test_atomic_group_scheduling_no_valid_hosts(self):
626 self._do_query('UPDATE hosts SET invalid=1 WHERE id in (8,9)')
627 self._create_job(synchronous=True, metahosts=[self.label5.id],
628 atomic_group=1)
629 self._dispatcher._schedule_new_jobs()
630 # no hosts in the selected group and label are valid. no schedulings.
631 self._check_for_extra_schedulings()
632
633
634 def test_atomic_group_scheduling_metahost_works(self):
635 # Test that atomic group scheduling also obeys metahosts.
636 self._create_job(metahosts=[0], atomic_group=1)
637 self._dispatcher._schedule_new_jobs()
638 # There are no atomic group hosts that also have that metahost.
639 self._check_for_extra_schedulings()
640
641 job_b = self._create_job(metahosts=[self.label5.id], atomic_group=1)
642 self._dispatcher._schedule_new_jobs()
643 self._assert_job_scheduled_on(job_b.id, 8)
644 self._assert_job_scheduled_on(job_b.id, 9)
645 self._check_for_extra_schedulings()
646
647
648 def test_atomic_group_skips_ineligible_hosts(self):
649 # Test hosts marked ineligible for this job are not eligible.
650 # How would this ever happen anyways?
651 job = self._create_job(metahosts=[self.label4.id], atomic_group=1)
652 models.IneligibleHostQueue.objects.create(job=job, host_id=5)
653 models.IneligibleHostQueue.objects.create(job=job, host_id=6)
654 models.IneligibleHostQueue.objects.create(job=job, host_id=7)
655 self._dispatcher._schedule_new_jobs()
656 # No scheduling should occur as all desired hosts were ineligible.
657 self._check_for_extra_schedulings()
658
659
660 def test_atomic_group_scheduling_fail(self):
661 # If synch_count is > the atomic group number of machines, the job
662 # should be aborted immediately.
663 model_job = self._create_job(synchronous=True, atomic_group=1)
664 model_job.synch_count = 4
665 model_job.save()
666 job = monitor_db.Job(id=model_job.id)
667 self._dispatcher._schedule_new_jobs()
668 self._check_for_extra_schedulings()
669 queue_entries = job.get_host_queue_entries()
670 self.assertEqual(1, len(queue_entries))
671 self.assertEqual(queue_entries[0].status,
672 models.HostQueueEntry.Status.ABORTED)
673
674
showard205fd602009-03-21 00:17:35 +0000675 def test_atomic_group_no_labels_no_scheduling(self):
676 # Never schedule on atomic groups marked invalid.
677 job = self._create_job(metahosts=[self.label5.id], synchronous=True,
678 atomic_group=1)
679 # Deleting an atomic group via the frontend marks it invalid and
680 # removes all label references to the group. The job now references
681 # an invalid atomic group with no labels associated with it.
682 self.label5.atomic_group.invalid = True
683 self.label5.atomic_group.save()
684 self.label5.atomic_group = None
685 self.label5.save()
686
687 self._dispatcher._schedule_new_jobs()
688 self._check_for_extra_schedulings()
689
690
showard89f84db2009-03-12 20:39:13 +0000691 def test_schedule_directly_on_atomic_group_host_fail(self):
692 # Scheduling a job directly on hosts in an atomic group must
693 # fail to avoid users inadvertently holding up the use of an
694 # entire atomic group by using the machines individually.
695 job = self._create_job(hosts=[5])
696 self._dispatcher._schedule_new_jobs()
697 self._check_for_extra_schedulings()
698
699
700 def test_schedule_directly_on_atomic_group_host(self):
701 # Scheduling a job directly on one host in an atomic group will
702 # work when the atomic group is listed on the HQE in addition
703 # to the host (assuming the sync count is 1).
704 job = self._create_job(hosts=[5], atomic_group=1)
705 self._dispatcher._schedule_new_jobs()
706 self._assert_job_scheduled_on(job.id, 5)
707 self._check_for_extra_schedulings()
708
709
710 def test_schedule_directly_on_atomic_group_hosts_sync2(self):
711 job = self._create_job(hosts=[5,8], atomic_group=1, synchronous=True)
712 self._dispatcher._schedule_new_jobs()
713 self._assert_job_scheduled_on(job.id, 5)
714 self._assert_job_scheduled_on(job.id, 8)
715 self._check_for_extra_schedulings()
716
717
718 def test_schedule_directly_on_atomic_group_hosts_wrong_group(self):
719 job = self._create_job(hosts=[5,8], atomic_group=2, synchronous=True)
720 self._dispatcher._schedule_new_jobs()
721 self._check_for_extra_schedulings()
722
723
showard56193bb2008-08-13 20:07:41 +0000724 def test_only_schedule_queued_entries(self):
725 self._create_job(metahosts=[1])
726 self._update_hqe(set='active=1, host_id=2')
727 self._dispatcher._schedule_new_jobs()
728 self._check_for_extra_schedulings()
729
730
showardfa8629c2008-11-04 16:51:23 +0000731 def test_no_ready_hosts(self):
732 self._create_job(hosts=[1])
733 self._do_query('UPDATE hosts SET status="Repair Failed"')
734 self._dispatcher._schedule_new_jobs()
735 self._check_for_extra_schedulings()
736
737
showardb2e2c322008-10-14 17:33:55 +0000738class DispatcherThrottlingTest(BaseSchedulerTest):
showard4c5374f2008-09-04 17:02:56 +0000739 """
740 Test that the dispatcher throttles:
741 * total number of running processes
742 * number of processes started per cycle
743 """
744 _MAX_RUNNING = 3
745 _MAX_STARTED = 2
746
747 def setUp(self):
748 super(DispatcherThrottlingTest, self).setUp()
showard324bf812009-01-20 23:23:38 +0000749 scheduler_config.config.max_processes_per_drone = self._MAX_RUNNING
showardd1ee1dd2009-01-07 21:33:08 +0000750 scheduler_config.config.max_processes_started_per_cycle = (
751 self._MAX_STARTED)
showard4c5374f2008-09-04 17:02:56 +0000752
showard324bf812009-01-20 23:23:38 +0000753 def fake_max_runnable_processes(fake_self):
754 running = sum(agent.num_processes
755 for agent in self._agents
756 if agent.is_running())
757 return self._MAX_RUNNING - running
758 self.god.stub_with(drone_manager.DroneManager, 'max_runnable_processes',
759 fake_max_runnable_processes)
showard2fa51692009-01-13 23:48:08 +0000760
showard4c5374f2008-09-04 17:02:56 +0000761
showard4c5374f2008-09-04 17:02:56 +0000762 def _setup_some_agents(self, num_agents):
showard170873e2009-01-07 00:22:26 +0000763 self._agents = [DummyAgent() for i in xrange(num_agents)]
showard4c5374f2008-09-04 17:02:56 +0000764 self._dispatcher._agents = list(self._agents)
765
766
767 def _run_a_few_cycles(self):
768 for i in xrange(4):
769 self._dispatcher._handle_agents()
770
771
772 def _assert_agents_started(self, indexes, is_started=True):
773 for i in indexes:
774 self.assert_(self._agents[i].is_running() == is_started,
775 'Agent %d %sstarted' %
776 (i, is_started and 'not ' or ''))
777
778
779 def _assert_agents_not_started(self, indexes):
780 self._assert_agents_started(indexes, False)
781
782
783 def test_throttle_total(self):
784 self._setup_some_agents(4)
785 self._run_a_few_cycles()
786 self._assert_agents_started([0, 1, 2])
787 self._assert_agents_not_started([3])
788
789
790 def test_throttle_per_cycle(self):
791 self._setup_some_agents(3)
792 self._dispatcher._handle_agents()
793 self._assert_agents_started([0, 1])
794 self._assert_agents_not_started([2])
795
796
797 def test_throttle_with_synchronous(self):
798 self._setup_some_agents(2)
799 self._agents[0].num_processes = 3
800 self._run_a_few_cycles()
801 self._assert_agents_started([0])
802 self._assert_agents_not_started([1])
803
804
805 def test_large_agent_starvation(self):
806 """
807 Ensure large agents don't get starved by lower-priority agents.
808 """
809 self._setup_some_agents(3)
810 self._agents[1].num_processes = 3
811 self._run_a_few_cycles()
812 self._assert_agents_started([0])
813 self._assert_agents_not_started([1, 2])
814
815 self._agents[0].set_done(True)
816 self._run_a_few_cycles()
817 self._assert_agents_started([1])
818 self._assert_agents_not_started([2])
819
820
821 def test_zero_process_agent(self):
822 self._setup_some_agents(5)
823 self._agents[4].num_processes = 0
824 self._run_a_few_cycles()
825 self._assert_agents_started([0, 1, 2, 4])
826 self._assert_agents_not_started([3])
827
828
showard1be97432008-10-17 15:30:45 +0000829class FindAbortTest(BaseSchedulerTest):
showard56193bb2008-08-13 20:07:41 +0000830 """
showard1be97432008-10-17 15:30:45 +0000831 Test the dispatcher abort functionality.
showard56193bb2008-08-13 20:07:41 +0000832 """
showard170873e2009-01-07 00:22:26 +0000833 def _check_host_agent(self, agent, host_id):
834 self.assert_(isinstance(agent, monitor_db.Agent))
835 tasks = list(agent.queue.queue)
836 self.assertEquals(len(tasks), 2)
837 cleanup, verify = tasks
showard9d9ffd52008-11-09 23:14:35 +0000838
showard45ae8192008-11-05 19:32:53 +0000839 self.assert_(isinstance(cleanup, monitor_db.CleanupTask))
showard170873e2009-01-07 00:22:26 +0000840 self.assertEquals(cleanup.host.id, host_id)
showard1be97432008-10-17 15:30:45 +0000841
842 self.assert_(isinstance(verify, monitor_db.VerifyTask))
showard170873e2009-01-07 00:22:26 +0000843 self.assertEquals(verify.host.id, host_id)
showard56193bb2008-08-13 20:07:41 +0000844
845
showardd3dc1992009-04-22 21:01:40 +0000846 def _check_agents(self, agents):
showard170873e2009-01-07 00:22:26 +0000847 agents = list(agents)
showardd3dc1992009-04-22 21:01:40 +0000848 self.assertEquals(len(agents), 3)
849 self.assertEquals(agents[0], self._agent)
850 self._check_host_agent(agents[1], 1)
851 self._check_host_agent(agents[2], 2)
showard56193bb2008-08-13 20:07:41 +0000852
853
showardd3dc1992009-04-22 21:01:40 +0000854 def _common_setup(self):
showard56193bb2008-08-13 20:07:41 +0000855 self._create_job(hosts=[1, 2])
showardd3dc1992009-04-22 21:01:40 +0000856 self._update_hqe(set='aborted=1')
857 self._agent = self.god.create_mock_class(monitor_db.Agent, 'old_agent')
showard6b733412009-04-27 20:09:18 +0000858 _set_host_and_qe_ids(self._agent, [1, 2])
showardd3dc1992009-04-22 21:01:40 +0000859 self._agent.abort.expect_call()
860 self._agent.abort.expect_call() # gets called once for each HQE
861 self._dispatcher.add_agent(self._agent)
showard56193bb2008-08-13 20:07:41 +0000862
showardd3dc1992009-04-22 21:01:40 +0000863
864 def test_find_aborting(self):
865 self._common_setup()
showard56193bb2008-08-13 20:07:41 +0000866 self._dispatcher._find_aborting()
showard56193bb2008-08-13 20:07:41 +0000867 self.god.check_playback()
868
869
showardd3dc1992009-04-22 21:01:40 +0000870 def test_find_aborting_verifying(self):
871 self._common_setup()
872 self._update_hqe(set='active=1, status="Verifying"')
showard56193bb2008-08-13 20:07:41 +0000873
showard56193bb2008-08-13 20:07:41 +0000874 self._dispatcher._find_aborting()
875
showardd3dc1992009-04-22 21:01:40 +0000876 self._check_agents(self._dispatcher._agents)
showard56193bb2008-08-13 20:07:41 +0000877 self.god.check_playback()
878
879
showard98863972008-10-29 21:14:56 +0000880class JobTimeoutTest(BaseSchedulerTest):
showard2bab8f42008-11-12 18:15:22 +0000881 def _test_synch_start_timeout_helper(self, expect_abort,
882 set_created_on=True, set_active=True,
883 set_acl=True):
showardd1ee1dd2009-01-07 21:33:08 +0000884 scheduler_config.config.synch_job_start_timeout_minutes = 60
showard98863972008-10-29 21:14:56 +0000885 job = self._create_job(hosts=[1, 2])
showard98863972008-10-29 21:14:56 +0000886 if set_active:
887 hqe = job.hostqueueentry_set.filter(host__id=1)[0]
888 hqe.status = 'Pending'
889 hqe.active = 1
890 hqe.save()
891
892 everyone_acl = models.AclGroup.smart_get('Everyone')
893 host1 = models.Host.smart_get(1)
894 if set_acl:
895 everyone_acl.hosts.add(host1)
896 else:
897 everyone_acl.hosts.remove(host1)
898
899 job.created_on = datetime.datetime.now()
900 if set_created_on:
901 job.created_on -= datetime.timedelta(minutes=100)
902 job.save()
903
showard915958d2009-04-22 21:00:58 +0000904 cleanup = self._dispatcher._periodic_cleanup
905 cleanup._abort_jobs_past_synch_start_timeout()
showard98863972008-10-29 21:14:56 +0000906
907 for hqe in job.hostqueueentry_set.all():
showardd3dc1992009-04-22 21:01:40 +0000908 self.assertEquals(hqe.aborted, expect_abort)
showard98863972008-10-29 21:14:56 +0000909
910
911 def test_synch_start_timeout_helper(self):
912 # no abort if any of the condition aren't met
showard2bab8f42008-11-12 18:15:22 +0000913 self._test_synch_start_timeout_helper(False, set_created_on=False)
914 self._test_synch_start_timeout_helper(False, set_active=False)
915 self._test_synch_start_timeout_helper(False, set_acl=False)
showard98863972008-10-29 21:14:56 +0000916 # abort if all conditions are met
showard2bab8f42008-11-12 18:15:22 +0000917 self._test_synch_start_timeout_helper(True)
showard98863972008-10-29 21:14:56 +0000918
919
jadmanski3d161b02008-06-06 15:43:36 +0000920class PidfileRunMonitorTest(unittest.TestCase):
showard170873e2009-01-07 00:22:26 +0000921 execution_tag = 'test_tag'
jadmanski0afbb632008-06-06 21:10:57 +0000922 pid = 12345
showard170873e2009-01-07 00:22:26 +0000923 process = drone_manager.Process('myhost', pid)
showard21baa452008-10-21 00:08:39 +0000924 num_tests_failed = 1
jadmanski3d161b02008-06-06 15:43:36 +0000925
jadmanski0afbb632008-06-06 21:10:57 +0000926 def setUp(self):
927 self.god = mock.mock_god()
showard170873e2009-01-07 00:22:26 +0000928 self.mock_drone_manager = self.god.create_mock_class(
929 drone_manager.DroneManager, 'drone_manager')
930 self.god.stub_with(monitor_db, '_drone_manager',
931 self.mock_drone_manager)
932 self.god.stub_function(email_manager.manager, 'enqueue_notify_email')
933
934 self.pidfile_id = object()
935
showardd3dc1992009-04-22 21:01:40 +0000936 (self.mock_drone_manager.get_pidfile_id_from
937 .expect_call(self.execution_tag,
938 pidfile_name=monitor_db._AUTOSERV_PID_FILE)
939 .and_return(self.pidfile_id))
showard170873e2009-01-07 00:22:26 +0000940 self.mock_drone_manager.register_pidfile.expect_call(self.pidfile_id)
941
942 self.monitor = monitor_db.PidfileRunMonitor()
943 self.monitor.attach_to_existing_process(self.execution_tag)
jadmanski3d161b02008-06-06 15:43:36 +0000944
945
jadmanski0afbb632008-06-06 21:10:57 +0000946 def tearDown(self):
947 self.god.unstub_all()
jadmanski3d161b02008-06-06 15:43:36 +0000948
949
showard170873e2009-01-07 00:22:26 +0000950 def setup_pidfile(self, pid=None, exit_code=None, tests_failed=None,
951 use_second_read=False):
952 contents = drone_manager.PidfileContents()
953 if pid is not None:
954 contents.process = drone_manager.Process('myhost', pid)
955 contents.exit_status = exit_code
956 contents.num_tests_failed = tests_failed
957 self.mock_drone_manager.get_pidfile_contents.expect_call(
958 self.pidfile_id, use_second_read=use_second_read).and_return(
959 contents)
960
961
jadmanski0afbb632008-06-06 21:10:57 +0000962 def set_not_yet_run(self):
showard170873e2009-01-07 00:22:26 +0000963 self.setup_pidfile()
jadmanski3d161b02008-06-06 15:43:36 +0000964
965
showard3dd6b882008-10-27 19:21:39 +0000966 def set_empty_pidfile(self):
showard170873e2009-01-07 00:22:26 +0000967 self.setup_pidfile()
showard3dd6b882008-10-27 19:21:39 +0000968
969
showard170873e2009-01-07 00:22:26 +0000970 def set_running(self, use_second_read=False):
971 self.setup_pidfile(self.pid, use_second_read=use_second_read)
jadmanski3d161b02008-06-06 15:43:36 +0000972
973
showard170873e2009-01-07 00:22:26 +0000974 def set_complete(self, error_code, use_second_read=False):
975 self.setup_pidfile(self.pid, error_code, self.num_tests_failed,
976 use_second_read=use_second_read)
977
978
979 def _check_monitor(self, expected_pid, expected_exit_status,
980 expected_num_tests_failed):
981 if expected_pid is None:
982 self.assertEquals(self.monitor._state.process, None)
983 else:
984 self.assertEquals(self.monitor._state.process.pid, expected_pid)
985 self.assertEquals(self.monitor._state.exit_status, expected_exit_status)
986 self.assertEquals(self.monitor._state.num_tests_failed,
987 expected_num_tests_failed)
988
989
990 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +0000991
992
showard21baa452008-10-21 00:08:39 +0000993 def _test_read_pidfile_helper(self, expected_pid, expected_exit_status,
994 expected_num_tests_failed):
995 self.monitor._read_pidfile()
showard170873e2009-01-07 00:22:26 +0000996 self._check_monitor(expected_pid, expected_exit_status,
997 expected_num_tests_failed)
jadmanski3d161b02008-06-06 15:43:36 +0000998
999
showard21baa452008-10-21 00:08:39 +00001000 def _get_expected_tests_failed(self, expected_exit_status):
1001 if expected_exit_status is None:
1002 expected_tests_failed = None
1003 else:
1004 expected_tests_failed = self.num_tests_failed
1005 return expected_tests_failed
1006
1007
jadmanski0afbb632008-06-06 21:10:57 +00001008 def test_read_pidfile(self):
1009 self.set_not_yet_run()
showard21baa452008-10-21 00:08:39 +00001010 self._test_read_pidfile_helper(None, None, None)
jadmanski3d161b02008-06-06 15:43:36 +00001011
showard3dd6b882008-10-27 19:21:39 +00001012 self.set_empty_pidfile()
1013 self._test_read_pidfile_helper(None, None, None)
1014
jadmanski0afbb632008-06-06 21:10:57 +00001015 self.set_running()
showard21baa452008-10-21 00:08:39 +00001016 self._test_read_pidfile_helper(self.pid, None, None)
jadmanski3d161b02008-06-06 15:43:36 +00001017
jadmanski0afbb632008-06-06 21:10:57 +00001018 self.set_complete(123)
showard21baa452008-10-21 00:08:39 +00001019 self._test_read_pidfile_helper(self.pid, 123, self.num_tests_failed)
jadmanski3d161b02008-06-06 15:43:36 +00001020
1021
jadmanski0afbb632008-06-06 21:10:57 +00001022 def test_read_pidfile_error(self):
showard170873e2009-01-07 00:22:26 +00001023 self.mock_drone_manager.get_pidfile_contents.expect_call(
1024 self.pidfile_id, use_second_read=False).and_return(
1025 drone_manager.InvalidPidfile('error'))
1026 self.assertRaises(monitor_db.PidfileRunMonitor._PidfileException,
showard21baa452008-10-21 00:08:39 +00001027 self.monitor._read_pidfile)
jadmanski0afbb632008-06-06 21:10:57 +00001028 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +00001029
1030
showard170873e2009-01-07 00:22:26 +00001031 def setup_is_running(self, is_running):
1032 self.mock_drone_manager.is_process_running.expect_call(
1033 self.process).and_return(is_running)
jadmanski3d161b02008-06-06 15:43:36 +00001034
1035
showard21baa452008-10-21 00:08:39 +00001036 def _test_get_pidfile_info_helper(self, expected_pid, expected_exit_status,
1037 expected_num_tests_failed):
1038 self.monitor._get_pidfile_info()
showard170873e2009-01-07 00:22:26 +00001039 self._check_monitor(expected_pid, expected_exit_status,
1040 expected_num_tests_failed)
jadmanski3d161b02008-06-06 15:43:36 +00001041
1042
jadmanski0afbb632008-06-06 21:10:57 +00001043 def test_get_pidfile_info(self):
showard21baa452008-10-21 00:08:39 +00001044 """
1045 normal cases for get_pidfile_info
1046 """
jadmanski0afbb632008-06-06 21:10:57 +00001047 # running
1048 self.set_running()
showard170873e2009-01-07 00:22:26 +00001049 self.setup_is_running(True)
showard21baa452008-10-21 00:08:39 +00001050 self._test_get_pidfile_info_helper(self.pid, None, None)
jadmanski3d161b02008-06-06 15:43:36 +00001051
jadmanski0afbb632008-06-06 21:10:57 +00001052 # exited during check
1053 self.set_running()
showard170873e2009-01-07 00:22:26 +00001054 self.setup_is_running(False)
1055 self.set_complete(123, use_second_read=True) # pidfile gets read again
showard21baa452008-10-21 00:08:39 +00001056 self._test_get_pidfile_info_helper(self.pid, 123, self.num_tests_failed)
jadmanski3d161b02008-06-06 15:43:36 +00001057
jadmanski0afbb632008-06-06 21:10:57 +00001058 # completed
1059 self.set_complete(123)
showard21baa452008-10-21 00:08:39 +00001060 self._test_get_pidfile_info_helper(self.pid, 123, self.num_tests_failed)
jadmanski3d161b02008-06-06 15:43:36 +00001061
1062
jadmanski0afbb632008-06-06 21:10:57 +00001063 def test_get_pidfile_info_running_no_proc(self):
showard21baa452008-10-21 00:08:39 +00001064 """
1065 pidfile shows process running, but no proc exists
1066 """
jadmanski0afbb632008-06-06 21:10:57 +00001067 # running but no proc
1068 self.set_running()
showard170873e2009-01-07 00:22:26 +00001069 self.setup_is_running(False)
1070 self.set_running(use_second_read=True)
1071 email_manager.manager.enqueue_notify_email.expect_call(
jadmanski0afbb632008-06-06 21:10:57 +00001072 mock.is_string_comparator(), mock.is_string_comparator())
showard21baa452008-10-21 00:08:39 +00001073 self._test_get_pidfile_info_helper(self.pid, 1, 0)
jadmanski0afbb632008-06-06 21:10:57 +00001074 self.assertTrue(self.monitor.lost_process)
jadmanski3d161b02008-06-06 15:43:36 +00001075
1076
jadmanski0afbb632008-06-06 21:10:57 +00001077 def test_get_pidfile_info_not_yet_run(self):
showard21baa452008-10-21 00:08:39 +00001078 """
1079 pidfile hasn't been written yet
1080 """
jadmanski0afbb632008-06-06 21:10:57 +00001081 self.set_not_yet_run()
showard21baa452008-10-21 00:08:39 +00001082 self._test_get_pidfile_info_helper(None, None, None)
jadmanski3d161b02008-06-06 15:43:36 +00001083
jadmanski3d161b02008-06-06 15:43:36 +00001084
showard170873e2009-01-07 00:22:26 +00001085 def test_process_failed_to_write_pidfile(self):
jadmanski0afbb632008-06-06 21:10:57 +00001086 self.set_not_yet_run()
showard170873e2009-01-07 00:22:26 +00001087 email_manager.manager.enqueue_notify_email.expect_call(
1088 mock.is_string_comparator(), mock.is_string_comparator())
showard170873e2009-01-07 00:22:26 +00001089 self.monitor._start_time = time.time() - monitor_db.PIDFILE_TIMEOUT - 1
showard35162b02009-03-03 02:17:30 +00001090 self._test_get_pidfile_info_helper(None, 1, 0)
1091 self.assertTrue(self.monitor.lost_process)
jadmanski3d161b02008-06-06 15:43:36 +00001092
1093
1094class AgentTest(unittest.TestCase):
jadmanski0afbb632008-06-06 21:10:57 +00001095 def setUp(self):
1096 self.god = mock.mock_god()
showard6b733412009-04-27 20:09:18 +00001097 self._dispatcher = self.god.create_mock_class(monitor_db.Dispatcher,
1098 'dispatcher')
jadmanski3d161b02008-06-06 15:43:36 +00001099
1100
jadmanski0afbb632008-06-06 21:10:57 +00001101 def tearDown(self):
1102 self.god.unstub_all()
jadmanski3d161b02008-06-06 15:43:36 +00001103
1104
showard170873e2009-01-07 00:22:26 +00001105 def _create_mock_task(self, name):
1106 task = self.god.create_mock_class(monitor_db.AgentTask, name)
showard6b733412009-04-27 20:09:18 +00001107 _set_host_and_qe_ids(task)
showard170873e2009-01-07 00:22:26 +00001108 return task
1109
showard6b733412009-04-27 20:09:18 +00001110 def _create_agent(self, tasks):
1111 agent = monitor_db.Agent(tasks)
1112 agent.dispatcher = self._dispatcher
1113 return agent
1114
1115
1116 def _finish_agent(self, agent):
1117 while not agent.is_done():
1118 agent.tick()
1119
showard170873e2009-01-07 00:22:26 +00001120
jadmanski0afbb632008-06-06 21:10:57 +00001121 def test_agent(self):
showard170873e2009-01-07 00:22:26 +00001122 task1 = self._create_mock_task('task1')
1123 task2 = self._create_mock_task('task2')
1124 task3 = self._create_mock_task('task3')
showard08a36412009-05-05 01:01:13 +00001125 task1.poll.expect_call()
jadmanski0afbb632008-06-06 21:10:57 +00001126 task1.is_done.expect_call().and_return(False)
1127 task1.poll.expect_call()
1128 task1.is_done.expect_call().and_return(True)
1129 task1.is_done.expect_call().and_return(True)
1130 task1.success = True
jadmanski3d161b02008-06-06 15:43:36 +00001131
showard08a36412009-05-05 01:01:13 +00001132 task2.poll.expect_call()
jadmanski0afbb632008-06-06 21:10:57 +00001133 task2.is_done.expect_call().and_return(True)
1134 task2.is_done.expect_call().and_return(True)
1135 task2.success = False
1136 task2.failure_tasks = [task3]
jadmanski3d161b02008-06-06 15:43:36 +00001137
showardd3dc1992009-04-22 21:01:40 +00001138 self._dispatcher.add_agent.expect_call(IsAgentWithTask(task3))
jadmanski3d161b02008-06-06 15:43:36 +00001139
showard6b733412009-04-27 20:09:18 +00001140 agent = self._create_agent([task1, task2])
showard6b733412009-04-27 20:09:18 +00001141 self._finish_agent(agent)
jadmanski0afbb632008-06-06 21:10:57 +00001142 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +00001143
1144
showard6b733412009-04-27 20:09:18 +00001145 def _test_agent_abort_helper(self, ignore_abort=False):
1146 task1 = self._create_mock_task('task1')
1147 task2 = self._create_mock_task('task2')
showard6b733412009-04-27 20:09:18 +00001148 task1.poll.expect_call()
1149 task1.is_done.expect_call().and_return(False)
1150 task1.abort.expect_call()
1151 if ignore_abort:
1152 task1.aborted = False # task ignores abort; execution continues
1153
showard08a36412009-05-05 01:01:13 +00001154 task1.poll.expect_call()
showard6b733412009-04-27 20:09:18 +00001155 task1.is_done.expect_call().and_return(True)
1156 task1.is_done.expect_call().and_return(True)
1157 task1.success = True
1158
showard08a36412009-05-05 01:01:13 +00001159 task2.poll.expect_call()
showard6b733412009-04-27 20:09:18 +00001160 task2.is_done.expect_call().and_return(True)
1161 task2.is_done.expect_call().and_return(True)
1162 task2.success = True
1163 else:
showard08a36412009-05-05 01:01:13 +00001164 task1.aborted = True
1165 task2.abort.expect_call()
1166 task2.aborted = True
showard6b733412009-04-27 20:09:18 +00001167
1168 agent = self._create_agent([task1, task2])
showard6b733412009-04-27 20:09:18 +00001169 agent.tick()
1170 agent.abort()
1171 self._finish_agent(agent)
1172 self.god.check_playback()
1173
1174
1175 def test_agent_abort(self):
1176 self._test_agent_abort_helper()
1177 self._test_agent_abort_helper(True)
1178
1179
showard08a36412009-05-05 01:01:13 +00001180 def _test_agent_abort_before_started_helper(self, ignore_abort=False):
showard20f9bdd2009-04-29 19:48:33 +00001181 task = self._create_mock_task('task')
showard08a36412009-05-05 01:01:13 +00001182 task.abort.expect_call()
1183 if ignore_abort:
1184 task.aborted = False
1185 task.poll.expect_call()
1186 task.is_done.expect_call().and_return(True)
1187 task.is_done.expect_call().and_return(True)
1188 task.success = True
1189 else:
1190 task.aborted = True
1191
showard20f9bdd2009-04-29 19:48:33 +00001192 agent = self._create_agent([task])
1193 agent.abort()
showard20f9bdd2009-04-29 19:48:33 +00001194 self._finish_agent(agent)
1195 self.god.check_playback()
1196
1197
showard08a36412009-05-05 01:01:13 +00001198 def test_agent_abort_before_started(self):
1199 self._test_agent_abort_before_started_helper()
1200 self._test_agent_abort_before_started_helper(True)
1201
1202
showard77182562009-06-10 00:16:05 +00001203class DelayedCallTaskTest(unittest.TestCase):
1204 def setUp(self):
1205 self.god = mock.mock_god()
1206
1207
1208 def tearDown(self):
1209 self.god.unstub_all()
1210
1211
1212 def test_delayed_call(self):
1213 test_time = self.god.create_mock_function('time')
1214 test_time.expect_call().and_return(33)
1215 test_time.expect_call().and_return(34.01)
1216 test_time.expect_call().and_return(34.99)
1217 test_time.expect_call().and_return(35.01)
1218 def test_callback():
1219 test_callback.calls += 1
1220 test_callback.calls = 0
1221 delay_task = monitor_db.DelayedCallTask(
1222 delay_seconds=2, callback=test_callback,
1223 now_func=test_time) # time 33
1224 self.assertEqual(35, delay_task.end_time)
1225 agent = monitor_db.Agent([delay_task], num_processes=0)
1226 self.assert_(not agent.active_task)
1227 agent.tick() # activates the task and polls it once, time 34.01
1228 self.assertEqual(0, test_callback.calls, "callback called early")
1229 agent.tick() # time 34.99
1230 self.assertEqual(0, test_callback.calls, "callback called early")
1231 agent.tick() # time 35.01
1232 self.assertEqual(1, test_callback.calls)
1233 self.assert_(agent.is_done())
1234 self.assert_(delay_task.is_done())
1235 self.assert_(delay_task.success)
1236 self.assert_(not delay_task.aborted)
1237 self.god.check_playback()
1238
1239
1240 def test_delayed_call_abort(self):
1241 delay_task = monitor_db.DelayedCallTask(
1242 delay_seconds=987654, callback=lambda : None)
1243 agent = monitor_db.Agent([delay_task], num_processes=0)
1244 agent.abort()
1245 agent.tick()
1246 self.assert_(agent.is_done())
1247 self.assert_(delay_task.aborted)
1248 self.assert_(delay_task.is_done())
1249 self.assert_(not delay_task.success)
1250 self.god.check_playback()
1251
1252
1253
showard184a5e82009-05-29 18:42:20 +00001254class AgentTasksTest(BaseSchedulerTest):
showard170873e2009-01-07 00:22:26 +00001255 TEMP_DIR = '/abspath/tempdir'
showard97aed502008-11-04 02:01:24 +00001256 RESULTS_DIR = '/results/dir'
jadmanski0afbb632008-06-06 21:10:57 +00001257 HOSTNAME = 'myhost'
showard170873e2009-01-07 00:22:26 +00001258 DUMMY_PROCESS = object()
jadmanskifb7cfb12008-07-09 14:13:21 +00001259 HOST_PROTECTION = host_protections.default
showard170873e2009-01-07 00:22:26 +00001260 PIDFILE_ID = object()
showard87ba02a2009-04-20 19:37:32 +00001261 JOB_OWNER = 'test_owner'
1262 JOB_NAME = 'test_job_name'
1263 JOB_AUTOSERV_PARAMS = set(['-u', JOB_OWNER, '-l', JOB_NAME])
jadmanski3d161b02008-06-06 15:43:36 +00001264
jadmanski0afbb632008-06-06 21:10:57 +00001265 def setUp(self):
showard184a5e82009-05-29 18:42:20 +00001266 super(AgentTasksTest, self).setUp()
jadmanski0afbb632008-06-06 21:10:57 +00001267 self.god = mock.mock_god()
showard170873e2009-01-07 00:22:26 +00001268 self.god.stub_with(drone_manager.DroneManager, 'get_temporary_path',
1269 mock.mock_function('get_temporary_path',
1270 default_return_val='tempdir'))
1271 self.god.stub_function(drone_manager.DroneManager,
showard678df4f2009-02-04 21:36:39 +00001272 'copy_results_on_drone')
1273 self.god.stub_function(drone_manager.DroneManager,
showard170873e2009-01-07 00:22:26 +00001274 'copy_to_results_repository')
1275 self.god.stub_function(drone_manager.DroneManager,
1276 'get_pidfile_id_from')
1277
1278 def dummy_absolute_path(self, path):
1279 return '/abspath/' + path
1280 self.god.stub_with(drone_manager.DroneManager, 'absolute_path',
1281 dummy_absolute_path)
1282
1283 self.god.stub_class_method(monitor_db.PidfileRunMonitor, 'run')
1284 self.god.stub_class_method(monitor_db.PidfileRunMonitor, 'exit_code')
1285 self.god.stub_class_method(monitor_db.PidfileRunMonitor, 'get_process')
showard6b733412009-04-27 20:09:18 +00001286 def mock_has_process(unused):
1287 return True
1288 self.god.stub_with(monitor_db.PidfileRunMonitor, 'has_process',
1289 mock_has_process)
jadmanski0afbb632008-06-06 21:10:57 +00001290 self.host = self.god.create_mock_class(monitor_db.Host, 'host')
showard170873e2009-01-07 00:22:26 +00001291 self.host.id = 1
jadmanski0afbb632008-06-06 21:10:57 +00001292 self.host.hostname = self.HOSTNAME
jadmanskifb7cfb12008-07-09 14:13:21 +00001293 self.host.protection = self.HOST_PROTECTION
jadmanski0afbb632008-06-06 21:10:57 +00001294 self.queue_entry = self.god.create_mock_class(
1295 monitor_db.HostQueueEntry, 'queue_entry')
showard97aed502008-11-04 02:01:24 +00001296 self.job = self.god.create_mock_class(monitor_db.Job, 'job')
showard87ba02a2009-04-20 19:37:32 +00001297 self.job.owner = self.JOB_OWNER
1298 self.job.name = self.JOB_NAME
mblighe7d9c602009-07-02 19:02:33 +00001299 self.job.id = 1337
1300 self.job.tag = lambda: 'fake-job-tag'
showard170873e2009-01-07 00:22:26 +00001301 self.queue_entry.id = 1
showard97aed502008-11-04 02:01:24 +00001302 self.queue_entry.job = self.job
jadmanski0afbb632008-06-06 21:10:57 +00001303 self.queue_entry.host = self.host
1304 self.queue_entry.meta_host = None
showardd3dc1992009-04-22 21:01:40 +00001305 self._dispatcher = self.god.create_mock_class(monitor_db.Dispatcher,
1306 'dispatcher')
1307
jadmanski3d161b02008-06-06 15:43:36 +00001308
jadmanski0afbb632008-06-06 21:10:57 +00001309 def tearDown(self):
showard184a5e82009-05-29 18:42:20 +00001310 super(AgentTasksTest, self).tearDown()
jadmanski0afbb632008-06-06 21:10:57 +00001311 self.god.unstub_all()
jadmanski3d161b02008-06-06 15:43:36 +00001312
1313
jadmanski0afbb632008-06-06 21:10:57 +00001314 def run_task(self, task, success):
1315 """
1316 Do essentially what an Agent would do, but protect againt
1317 infinite looping from test errors.
1318 """
1319 if not getattr(task, 'agent', None):
1320 task.agent = object()
jadmanski0afbb632008-06-06 21:10:57 +00001321 count = 0
1322 while not task.is_done():
1323 count += 1
1324 if count > 10:
1325 print 'Task failed to finish'
1326 # in case the playback has clues to why it
1327 # failed
1328 self.god.check_playback()
1329 self.fail()
1330 task.poll()
1331 self.assertEquals(task.success, success)
jadmanski3d161b02008-06-06 15:43:36 +00001332
1333
showard170873e2009-01-07 00:22:26 +00001334 def setup_run_monitor(self, exit_status, copy_log_file=True):
1335 monitor_db.PidfileRunMonitor.run.expect_call(
1336 mock.is_instance_comparator(list),
1337 'tempdir',
1338 nice_level=monitor_db.AUTOSERV_NICE_LEVEL,
showardd3dc1992009-04-22 21:01:40 +00001339 log_file=mock.anything_comparator(),
1340 pidfile_name=monitor_db._AUTOSERV_PID_FILE,
1341 paired_with_pidfile=None)
showard170873e2009-01-07 00:22:26 +00001342 monitor_db.PidfileRunMonitor.exit_code.expect_call()
1343 monitor_db.PidfileRunMonitor.exit_code.expect_call().and_return(
jadmanski0afbb632008-06-06 21:10:57 +00001344 exit_status)
jadmanski3d161b02008-06-06 15:43:36 +00001345
showard170873e2009-01-07 00:22:26 +00001346 if copy_log_file:
1347 self._setup_move_logfile()
1348
1349
showard678df4f2009-02-04 21:36:39 +00001350 def _setup_move_logfile(self, copy_on_drone=False,
1351 include_destination=False):
showard170873e2009-01-07 00:22:26 +00001352 monitor_db.PidfileRunMonitor.get_process.expect_call().and_return(
1353 self.DUMMY_PROCESS)
showard678df4f2009-02-04 21:36:39 +00001354 if copy_on_drone:
1355 self.queue_entry.execution_tag.expect_call().and_return('tag')
1356 drone_manager.DroneManager.copy_results_on_drone.expect_call(
1357 self.DUMMY_PROCESS, source_path=mock.is_string_comparator(),
1358 destination_path=mock.is_string_comparator())
1359 elif include_destination:
showard170873e2009-01-07 00:22:26 +00001360 drone_manager.DroneManager.copy_to_results_repository.expect_call(
1361 self.DUMMY_PROCESS, mock.is_string_comparator(),
1362 destination_path=mock.is_string_comparator())
1363 else:
1364 drone_manager.DroneManager.copy_to_results_repository.expect_call(
1365 self.DUMMY_PROCESS, mock.is_string_comparator())
1366
jadmanski3d161b02008-06-06 15:43:36 +00001367
jadmanski0afbb632008-06-06 21:10:57 +00001368 def _test_repair_task_helper(self, success):
1369 self.host.set_status.expect_call('Repairing')
1370 if success:
1371 self.setup_run_monitor(0)
1372 self.host.set_status.expect_call('Ready')
1373 else:
1374 self.setup_run_monitor(1)
1375 self.host.set_status.expect_call('Repair Failed')
jadmanski3d161b02008-06-06 15:43:36 +00001376
jadmanski0afbb632008-06-06 21:10:57 +00001377 task = monitor_db.RepairTask(self.host)
showard56193bb2008-08-13 20:07:41 +00001378 self.assertEquals(task.failure_tasks, [])
jadmanski0afbb632008-06-06 21:10:57 +00001379 self.run_task(task, success)
jadmanskifb7cfb12008-07-09 14:13:21 +00001380
1381 expected_protection = host_protections.Protection.get_string(
1382 host_protections.default)
mbligh3e0f7e02008-07-28 19:42:01 +00001383 expected_protection = host_protections.Protection.get_attr_name(
1384 expected_protection)
1385
showard170873e2009-01-07 00:22:26 +00001386 self.assertTrue(set(task.cmd) >=
1387 set([monitor_db._autoserv_path, '-p', '-R', '-m',
1388 self.HOSTNAME, '-r', self.TEMP_DIR,
1389 '--host-protection', expected_protection]))
jadmanski0afbb632008-06-06 21:10:57 +00001390 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +00001391
1392
jadmanski0afbb632008-06-06 21:10:57 +00001393 def test_repair_task(self):
1394 self._test_repair_task_helper(True)
1395 self._test_repair_task_helper(False)
jadmanski3d161b02008-06-06 15:43:36 +00001396
1397
showarda1e74b32009-05-12 17:32:04 +00001398 def _test_repair_task_with_queue_entry_helper(self, parse_failed_repair):
showardde634ee2009-01-30 01:44:24 +00001399 self.god.stub_class(monitor_db, 'FinalReparseTask')
1400 self.god.stub_class(monitor_db, 'Agent')
showardd9205182009-04-27 20:09:55 +00001401 self.god.stub_class_method(monitor_db.TaskWithJobKeyvals,
1402 '_write_keyval_after_job')
showardde634ee2009-01-30 01:44:24 +00001403 agent = DummyAgent()
showardd3dc1992009-04-22 21:01:40 +00001404 agent.dispatcher = self._dispatcher
showardde634ee2009-01-30 01:44:24 +00001405
jadmanski0afbb632008-06-06 21:10:57 +00001406 self.host.set_status.expect_call('Repairing')
showarde788ea62008-11-17 21:02:47 +00001407 self.queue_entry.requeue.expect_call()
jadmanski0afbb632008-06-06 21:10:57 +00001408 self.setup_run_monitor(1)
1409 self.host.set_status.expect_call('Repair Failed')
showardccbd6c52009-03-21 00:10:21 +00001410 self.queue_entry.update_from_database.expect_call()
showardde634ee2009-01-30 01:44:24 +00001411 self.queue_entry.set_execution_subdir.expect_call()
showardd9205182009-04-27 20:09:55 +00001412 monitor_db.TaskWithJobKeyvals._write_keyval_after_job.expect_call(
1413 'job_queued', mock.is_instance_comparator(int))
1414 monitor_db.TaskWithJobKeyvals._write_keyval_after_job.expect_call(
1415 'job_finished', mock.is_instance_comparator(int))
showard678df4f2009-02-04 21:36:39 +00001416 self._setup_move_logfile(copy_on_drone=True)
showardde634ee2009-01-30 01:44:24 +00001417 self.queue_entry.execution_tag.expect_call().and_return('tag')
showard678df4f2009-02-04 21:36:39 +00001418 self._setup_move_logfile()
showarda1e74b32009-05-12 17:32:04 +00001419 self.job.parse_failed_repair = parse_failed_repair
1420 if parse_failed_repair:
1421 reparse_task = monitor_db.FinalReparseTask.expect_new(
1422 [self.queue_entry])
1423 reparse_agent = monitor_db.Agent.expect_new([reparse_task],
1424 num_processes=0)
1425 self._dispatcher.add_agent.expect_call(reparse_agent)
showarde788ea62008-11-17 21:02:47 +00001426 self.queue_entry.handle_host_failure.expect_call()
jadmanski3d161b02008-06-06 15:43:36 +00001427
showarde788ea62008-11-17 21:02:47 +00001428 task = monitor_db.RepairTask(self.host, self.queue_entry)
showardde634ee2009-01-30 01:44:24 +00001429 task.agent = agent
showardccbd6c52009-03-21 00:10:21 +00001430 self.queue_entry.status = 'Queued'
showardd9205182009-04-27 20:09:55 +00001431 self.job.created_on = datetime.datetime(2009, 1, 1)
jadmanski0afbb632008-06-06 21:10:57 +00001432 self.run_task(task, False)
showard87ba02a2009-04-20 19:37:32 +00001433 self.assertTrue(set(task.cmd) >= self.JOB_AUTOSERV_PARAMS)
jadmanski0afbb632008-06-06 21:10:57 +00001434 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +00001435
1436
showarda1e74b32009-05-12 17:32:04 +00001437 def test_repair_task_with_queue_entry(self):
1438 self._test_repair_task_with_queue_entry_helper(True)
1439 self._test_repair_task_with_queue_entry_helper(False)
1440
1441
jadmanski0afbb632008-06-06 21:10:57 +00001442 def setup_verify_expects(self, success, use_queue_entry):
1443 if use_queue_entry:
showard8fe93b52008-11-18 17:53:22 +00001444 self.queue_entry.set_status.expect_call('Verifying')
jadmanski0afbb632008-06-06 21:10:57 +00001445 self.host.set_status.expect_call('Verifying')
1446 if success:
1447 self.setup_run_monitor(0)
1448 self.host.set_status.expect_call('Ready')
1449 else:
1450 self.setup_run_monitor(1)
showard8fe93b52008-11-18 17:53:22 +00001451 if use_queue_entry and not self.queue_entry.meta_host:
1452 self.queue_entry.set_execution_subdir.expect_call()
showard170873e2009-01-07 00:22:26 +00001453 self.queue_entry.execution_tag.expect_call().and_return('tag')
1454 self._setup_move_logfile(include_destination=True)
jadmanski3d161b02008-06-06 15:43:36 +00001455
1456
showard56193bb2008-08-13 20:07:41 +00001457 def _check_verify_failure_tasks(self, verify_task):
1458 self.assertEquals(len(verify_task.failure_tasks), 1)
1459 repair_task = verify_task.failure_tasks[0]
1460 self.assert_(isinstance(repair_task, monitor_db.RepairTask))
1461 self.assertEquals(verify_task.host, repair_task.host)
showard8fe93b52008-11-18 17:53:22 +00001462 if verify_task.queue_entry:
showard2fe3f1d2009-07-06 20:19:11 +00001463 self.assertEquals(repair_task.queue_entry,
showardccbd6c52009-03-21 00:10:21 +00001464 verify_task.queue_entry)
showard56193bb2008-08-13 20:07:41 +00001465 else:
showard2fe3f1d2009-07-06 20:19:11 +00001466 self.assertEquals(repair_task.queue_entry, None)
showard56193bb2008-08-13 20:07:41 +00001467
1468
1469 def _test_verify_task_helper(self, success, use_queue_entry=False,
1470 use_meta_host=False):
jadmanski0afbb632008-06-06 21:10:57 +00001471 self.setup_verify_expects(success, use_queue_entry)
jadmanski3d161b02008-06-06 15:43:36 +00001472
jadmanski0afbb632008-06-06 21:10:57 +00001473 if use_queue_entry:
showard170873e2009-01-07 00:22:26 +00001474 task = monitor_db.VerifyTask(queue_entry=self.queue_entry)
jadmanski0afbb632008-06-06 21:10:57 +00001475 else:
1476 task = monitor_db.VerifyTask(host=self.host)
showard56193bb2008-08-13 20:07:41 +00001477 self._check_verify_failure_tasks(task)
jadmanski0afbb632008-06-06 21:10:57 +00001478 self.run_task(task, success)
showard170873e2009-01-07 00:22:26 +00001479 self.assertTrue(set(task.cmd) >=
1480 set([monitor_db._autoserv_path, '-p', '-v', '-m',
1481 self.HOSTNAME, '-r', self.TEMP_DIR]))
showard87ba02a2009-04-20 19:37:32 +00001482 if use_queue_entry:
1483 self.assertTrue(set(task.cmd) >= self.JOB_AUTOSERV_PARAMS)
jadmanski0afbb632008-06-06 21:10:57 +00001484 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +00001485
1486
jadmanski0afbb632008-06-06 21:10:57 +00001487 def test_verify_task_with_host(self):
showard56193bb2008-08-13 20:07:41 +00001488 self._test_verify_task_helper(True)
1489 self._test_verify_task_helper(False)
jadmanski3d161b02008-06-06 15:43:36 +00001490
1491
jadmanski0afbb632008-06-06 21:10:57 +00001492 def test_verify_task_with_queue_entry(self):
showard56193bb2008-08-13 20:07:41 +00001493 self._test_verify_task_helper(True, use_queue_entry=True)
1494 self._test_verify_task_helper(False, use_queue_entry=True)
1495
1496
1497 def test_verify_task_with_metahost(self):
showard8fe93b52008-11-18 17:53:22 +00001498 self.queue_entry.meta_host = 1
1499 self.test_verify_task_with_queue_entry()
jadmanski3d161b02008-06-06 15:43:36 +00001500
1501
showard5add1c82009-05-26 19:27:46 +00001502 def _setup_post_job_task_expects(self, autoserv_success, hqe_status=None,
showard6b733412009-04-27 20:09:18 +00001503 hqe_aborted=False):
showard170873e2009-01-07 00:22:26 +00001504 self.queue_entry.execution_tag.expect_call().and_return('tag')
1505 self.pidfile_monitor = monitor_db.PidfileRunMonitor.expect_new()
1506 self.pidfile_monitor.pidfile_id = self.PIDFILE_ID
1507 self.pidfile_monitor.attach_to_existing_process.expect_call('tag')
1508 if autoserv_success:
1509 code = 0
1510 else:
1511 code = 1
showardd3dc1992009-04-22 21:01:40 +00001512 self.queue_entry.update_from_database.expect_call()
showard6b733412009-04-27 20:09:18 +00001513 self.queue_entry.aborted = hqe_aborted
1514 if not hqe_aborted:
1515 self.pidfile_monitor.exit_code.expect_call().and_return(code)
showard170873e2009-01-07 00:22:26 +00001516
showard5add1c82009-05-26 19:27:46 +00001517 if hqe_status:
1518 self.queue_entry.set_status.expect_call(hqe_status)
showardd3dc1992009-04-22 21:01:40 +00001519
1520
1521 def _setup_pre_parse_expects(self, autoserv_success):
1522 self._setup_post_job_task_expects(autoserv_success, 'Parsing')
showard97aed502008-11-04 02:01:24 +00001523
1524
1525 def _setup_post_parse_expects(self, autoserv_success):
showard97aed502008-11-04 02:01:24 +00001526 if autoserv_success:
showard170873e2009-01-07 00:22:26 +00001527 status = 'Completed'
showard97aed502008-11-04 02:01:24 +00001528 else:
showard170873e2009-01-07 00:22:26 +00001529 status = 'Failed'
showard97aed502008-11-04 02:01:24 +00001530 self.queue_entry.set_status.expect_call(status)
1531
1532
showard5add1c82009-05-26 19:27:46 +00001533 def _expect_execute_run_monitor(self):
1534 self.monitor.exit_code.expect_call()
1535 self.monitor.exit_code.expect_call().and_return(0)
1536 self._expect_copy_results()
1537
1538
showardd3dc1992009-04-22 21:01:40 +00001539 def _setup_post_job_run_monitor(self, pidfile_name):
showard678df4f2009-02-04 21:36:39 +00001540 self.pidfile_monitor.has_process.expect_call().and_return(True)
showard170873e2009-01-07 00:22:26 +00001541 autoserv_pidfile_id = object()
showardd3dc1992009-04-22 21:01:40 +00001542 self.monitor = monitor_db.PidfileRunMonitor.expect_new()
1543 self.monitor.run.expect_call(
showard170873e2009-01-07 00:22:26 +00001544 mock.is_instance_comparator(list),
1545 'tag',
showardd3dc1992009-04-22 21:01:40 +00001546 nice_level=monitor_db.AUTOSERV_NICE_LEVEL,
showard170873e2009-01-07 00:22:26 +00001547 log_file=mock.anything_comparator(),
showardd3dc1992009-04-22 21:01:40 +00001548 pidfile_name=pidfile_name,
showard170873e2009-01-07 00:22:26 +00001549 paired_with_pidfile=self.PIDFILE_ID)
showard5add1c82009-05-26 19:27:46 +00001550 self._expect_execute_run_monitor()
showardd3dc1992009-04-22 21:01:40 +00001551
1552
showard6b733412009-04-27 20:09:18 +00001553 def _expect_copy_results(self, monitor=None, queue_entry=None):
1554 if monitor is None:
1555 monitor = self.monitor
1556 monitor.has_process.expect_call().and_return(True)
1557 if queue_entry:
1558 queue_entry.execution_tag.expect_call().and_return('tag')
1559 monitor.get_process.expect_call().and_return(self.DUMMY_PROCESS)
showard170873e2009-01-07 00:22:26 +00001560 drone_manager.DroneManager.copy_to_results_repository.expect_call(
1561 self.DUMMY_PROCESS, mock.is_string_comparator())
showard97aed502008-11-04 02:01:24 +00001562
showard170873e2009-01-07 00:22:26 +00001563
1564 def _test_final_reparse_task_helper(self, autoserv_success=True):
1565 self._setup_pre_parse_expects(autoserv_success)
showardd3dc1992009-04-22 21:01:40 +00001566 self._setup_post_job_run_monitor(monitor_db._PARSER_PID_FILE)
showard97aed502008-11-04 02:01:24 +00001567 self._setup_post_parse_expects(autoserv_success)
1568
1569 task = monitor_db.FinalReparseTask([self.queue_entry])
1570 self.run_task(task, True)
1571
1572 self.god.check_playback()
showard170873e2009-01-07 00:22:26 +00001573 cmd = [monitor_db._parser_path, '--write-pidfile', '-l', '2', '-r',
mbligh2d7c8bd2009-05-13 20:42:50 +00001574 '-o', '-P', '/abspath/tag']
showard97aed502008-11-04 02:01:24 +00001575 self.assertEquals(task.cmd, cmd)
1576
1577
1578 def test_final_reparse_task(self):
1579 self.god.stub_class(monitor_db, 'PidfileRunMonitor')
1580 self._test_final_reparse_task_helper()
showard97aed502008-11-04 02:01:24 +00001581 self._test_final_reparse_task_helper(autoserv_success=False)
1582
1583
1584 def test_final_reparse_throttling(self):
1585 self.god.stub_class(monitor_db, 'PidfileRunMonitor')
1586 self.god.stub_function(monitor_db.FinalReparseTask,
1587 '_can_run_new_parse')
1588
showard170873e2009-01-07 00:22:26 +00001589 self._setup_pre_parse_expects(True)
showard97aed502008-11-04 02:01:24 +00001590 monitor_db.FinalReparseTask._can_run_new_parse.expect_call().and_return(
1591 False)
1592 monitor_db.FinalReparseTask._can_run_new_parse.expect_call().and_return(
1593 True)
showardd3dc1992009-04-22 21:01:40 +00001594 self._setup_post_job_run_monitor(monitor_db._PARSER_PID_FILE)
showard97aed502008-11-04 02:01:24 +00001595 self._setup_post_parse_expects(True)
1596
1597 task = monitor_db.FinalReparseTask([self.queue_entry])
1598 self.run_task(task, True)
1599 self.god.check_playback()
showard1be97432008-10-17 15:30:45 +00001600
1601
showard5add1c82009-05-26 19:27:46 +00001602 def test_final_reparse_recovery(self):
1603 self.god.stub_class(monitor_db, 'PidfileRunMonitor')
1604 self.monitor = self.god.create_mock_class(monitor_db.PidfileRunMonitor,
1605 'run_monitor')
1606 self._setup_post_job_task_expects(True)
1607 self._expect_execute_run_monitor()
1608 self._setup_post_parse_expects(True)
1609
1610 task = monitor_db.FinalReparseTask([self.queue_entry],
1611 run_monitor=self.monitor)
1612 self.run_task(task, True)
1613 self.god.check_playback()
1614
1615
showard597bfd32009-05-08 18:22:50 +00001616 def _setup_gather_logs_expects(self, autoserv_killed=True,
showard0bbfc212009-04-29 21:06:13 +00001617 hqe_aborted=False):
showardd3dc1992009-04-22 21:01:40 +00001618 self.god.stub_class(monitor_db, 'PidfileRunMonitor')
1619 self.god.stub_class(monitor_db, 'FinalReparseTask')
showard597bfd32009-05-08 18:22:50 +00001620 self._setup_post_job_task_expects(not autoserv_killed, 'Gathering',
showard0bbfc212009-04-29 21:06:13 +00001621 hqe_aborted)
showard597bfd32009-05-08 18:22:50 +00001622 if hqe_aborted:
1623 exit_code = None
1624 elif autoserv_killed:
1625 exit_code = 271
1626 else:
1627 exit_code = 0
1628 self.pidfile_monitor.exit_code.expect_call().and_return(exit_code)
1629 if exit_code != 0:
showard0bbfc212009-04-29 21:06:13 +00001630 self._setup_post_job_run_monitor('.collect_crashinfo_execute')
showardebc0fb72009-05-13 21:28:07 +00001631 self.pidfile_monitor.has_process.expect_call().and_return(True)
showard6b733412009-04-27 20:09:18 +00001632 self._expect_copy_results(monitor=self.pidfile_monitor,
1633 queue_entry=self.queue_entry)
showardd3dc1992009-04-22 21:01:40 +00001634 parse_task = monitor_db.FinalReparseTask.expect_new([self.queue_entry])
showard6b733412009-04-27 20:09:18 +00001635 _set_host_and_qe_ids(parse_task)
showardd3dc1992009-04-22 21:01:40 +00001636 self._dispatcher.add_agent.expect_call(IsAgentWithTask(parse_task))
1637
showardb5626452009-06-30 01:57:28 +00001638 self.pidfile_monitor.num_tests_failed.expect_call().and_return(0)
1639
showardd3dc1992009-04-22 21:01:40 +00001640
showard6b733412009-04-27 20:09:18 +00001641 def _run_gather_logs_task(self):
showardd3dc1992009-04-22 21:01:40 +00001642 task = monitor_db.GatherLogsTask(self.job, [self.queue_entry])
1643 task.agent = DummyAgent()
1644 task.agent.dispatcher = self._dispatcher
1645 self.run_task(task, True)
showardd3dc1992009-04-22 21:01:40 +00001646 self.god.check_playback()
1647
1648
showard6b733412009-04-27 20:09:18 +00001649 def test_gather_logs_task(self):
1650 self._setup_gather_logs_expects()
1651 # no rebooting for this basic test
1652 self.job.reboot_after = models.RebootAfter.NEVER
1653 self.host.set_status.expect_call('Ready')
1654
1655 self._run_gather_logs_task()
1656
1657
showard0bbfc212009-04-29 21:06:13 +00001658 def test_gather_logs_task_successful_autoserv(self):
showard597bfd32009-05-08 18:22:50 +00001659 # When Autoserv exits successfully, no collect_crashinfo stage runs
1660 self._setup_gather_logs_expects(autoserv_killed=False)
showard0bbfc212009-04-29 21:06:13 +00001661 self.job.reboot_after = models.RebootAfter.NEVER
1662 self.host.set_status.expect_call('Ready')
1663
1664 self._run_gather_logs_task()
1665
1666
showard6b733412009-04-27 20:09:18 +00001667 def _setup_gather_task_cleanup_expects(self):
1668 self.god.stub_class(monitor_db, 'CleanupTask')
1669 cleanup_task = monitor_db.CleanupTask.expect_new(host=self.host)
1670 _set_host_and_qe_ids(cleanup_task)
1671 self._dispatcher.add_agent.expect_call(IsAgentWithTask(cleanup_task))
1672
1673
1674 def test_gather_logs_reboot_hosts(self):
1675 self._setup_gather_logs_expects()
1676 self.job.reboot_after = models.RebootAfter.ALWAYS
1677 self._setup_gather_task_cleanup_expects()
1678
1679 self._run_gather_logs_task()
1680
1681
1682 def test_gather_logs_reboot_on_abort(self):
1683 self._setup_gather_logs_expects(hqe_aborted=True)
1684 self.job.reboot_after = models.RebootAfter.NEVER
1685 self._setup_gather_task_cleanup_expects()
1686
1687 self._run_gather_logs_task()
1688
1689
showard45ae8192008-11-05 19:32:53 +00001690 def _test_cleanup_task_helper(self, success, use_queue_entry=False):
showardfa8629c2008-11-04 16:51:23 +00001691 if use_queue_entry:
1692 self.queue_entry.get_host.expect_call().and_return(self.host)
showard45ae8192008-11-05 19:32:53 +00001693 self.host.set_status.expect_call('Cleaning')
showardfa8629c2008-11-04 16:51:23 +00001694 if success:
1695 self.setup_run_monitor(0)
1696 self.host.set_status.expect_call('Ready')
1697 self.host.update_field.expect_call('dirty', 0)
1698 else:
1699 self.setup_run_monitor(1)
showard8fe93b52008-11-18 17:53:22 +00001700 if use_queue_entry and not self.queue_entry.meta_host:
1701 self.queue_entry.set_execution_subdir.expect_call()
showard170873e2009-01-07 00:22:26 +00001702 self.queue_entry.execution_tag.expect_call().and_return('tag')
1703 self._setup_move_logfile(include_destination=True)
showardfa8629c2008-11-04 16:51:23 +00001704
1705 if use_queue_entry:
showard45ae8192008-11-05 19:32:53 +00001706 task = monitor_db.CleanupTask(queue_entry=self.queue_entry)
showardfa8629c2008-11-04 16:51:23 +00001707 else:
showard45ae8192008-11-05 19:32:53 +00001708 task = monitor_db.CleanupTask(host=self.host)
showardfa8629c2008-11-04 16:51:23 +00001709 self.assertEquals(len(task.failure_tasks), 1)
1710 repair_task = task.failure_tasks[0]
1711 self.assert_(isinstance(repair_task, monitor_db.RepairTask))
1712 if use_queue_entry:
showard2fe3f1d2009-07-06 20:19:11 +00001713 self.assertEquals(repair_task.queue_entry, self.queue_entry)
showardfa8629c2008-11-04 16:51:23 +00001714
1715 self.run_task(task, success)
1716
1717 self.god.check_playback()
showard170873e2009-01-07 00:22:26 +00001718 self.assert_(set(task.cmd) >=
1719 set([monitor_db._autoserv_path, '-p', '--cleanup', '-m',
1720 self.HOSTNAME, '-r', self.TEMP_DIR]))
showard87ba02a2009-04-20 19:37:32 +00001721 if use_queue_entry:
1722 self.assertTrue(set(task.cmd) >= self.JOB_AUTOSERV_PARAMS)
showardfa8629c2008-11-04 16:51:23 +00001723
showard45ae8192008-11-05 19:32:53 +00001724 def test_cleanup_task(self):
1725 self._test_cleanup_task_helper(True)
1726 self._test_cleanup_task_helper(False)
showardfa8629c2008-11-04 16:51:23 +00001727
1728
showard45ae8192008-11-05 19:32:53 +00001729 def test_cleanup_task_with_queue_entry(self):
1730 self._test_cleanup_task_helper(False, True)
showardfa8629c2008-11-04 16:51:23 +00001731
1732
showard5add1c82009-05-26 19:27:46 +00001733 def test_recovery_queue_task_aborted_early(self):
1734 # abort a RecoveryQueueTask right after it's created
1735 self.god.stub_class_method(monitor_db.QueueTask, '_log_abort')
1736 self.god.stub_class_method(monitor_db.QueueTask, '_finish_task')
1737 run_monitor = self.god.create_mock_class(monitor_db.PidfileRunMonitor,
1738 'run_monitor')
1739
1740 self.queue_entry.execution_tag.expect_call().and_return('tag')
1741 run_monitor.kill.expect_call()
1742 run_monitor.has_process.expect_call().and_return(True)
1743 monitor_db.QueueTask._log_abort.expect_call()
1744 monitor_db.QueueTask._finish_task.expect_call()
1745
1746 task = monitor_db.RecoveryQueueTask(self.job, [self.queue_entry],
1747 run_monitor)
1748 task.abort()
1749 self.assert_(task.aborted)
1750 self.god.check_playback()
1751
1752
showard54c1ea92009-05-20 00:32:58 +00001753class HostTest(BaseSchedulerTest):
1754 def test_cmp_for_sort(self):
1755 expected_order = [
1756 'alice', 'Host1', 'host2', 'host3', 'host09', 'HOST010',
1757 'host10', 'host11', 'yolkfolk']
1758 hostname_idx = list(monitor_db.Host._fields).index('hostname')
1759 row = [None] * len(monitor_db.Host._fields)
1760 hosts = []
1761 for hostname in expected_order:
1762 row[hostname_idx] = hostname
1763 hosts.append(monitor_db.Host(row=row, new_record=True))
1764
1765 host1 = hosts[expected_order.index('Host1')]
1766 host010 = hosts[expected_order.index('HOST010')]
1767 host10 = hosts[expected_order.index('host10')]
1768 host3 = hosts[expected_order.index('host3')]
1769 alice = hosts[expected_order.index('alice')]
1770 self.assertEqual(0, monitor_db.Host.cmp_for_sort(host10, host10))
1771 self.assertEqual(1, monitor_db.Host.cmp_for_sort(host10, host010))
1772 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host010, host10))
1773 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host1, host10))
1774 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host1, host010))
1775 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host3, host10))
1776 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host3, host010))
1777 self.assertEqual(1, monitor_db.Host.cmp_for_sort(host3, host1))
1778 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(host1, host3))
1779 self.assertEqual(-1, monitor_db.Host.cmp_for_sort(alice, host3))
1780 self.assertEqual(1, monitor_db.Host.cmp_for_sort(host3, alice))
1781 self.assertEqual(0, monitor_db.Host.cmp_for_sort(alice, alice))
1782
1783 hosts.sort(cmp=monitor_db.Host.cmp_for_sort)
1784 self.assertEqual(expected_order, [h.hostname for h in hosts])
1785
1786 hosts.reverse()
1787 hosts.sort(cmp=monitor_db.Host.cmp_for_sort)
1788 self.assertEqual(expected_order, [h.hostname for h in hosts])
1789
1790
showardf1ae3542009-05-11 19:26:02 +00001791class HostQueueEntryTest(BaseSchedulerTest):
1792 def _create_hqe(self, dependency_labels=(), **create_job_kwargs):
1793 job = self._create_job(**create_job_kwargs)
1794 for label in dependency_labels:
1795 job.dependency_labels.add(label)
1796 hqes = list(monitor_db.HostQueueEntry.fetch(where='job_id=%d' % job.id))
1797 self.assertEqual(1, len(hqes))
1798 return hqes[0]
1799
showard77182562009-06-10 00:16:05 +00001800
showardf1ae3542009-05-11 19:26:02 +00001801 def _check_hqe_labels(self, hqe, expected_labels):
1802 expected_labels = set(expected_labels)
1803 label_names = set(label.name for label in hqe.get_labels())
1804 self.assertEqual(expected_labels, label_names)
1805
showard77182562009-06-10 00:16:05 +00001806
showardf1ae3542009-05-11 19:26:02 +00001807 def test_get_labels_empty(self):
1808 hqe = self._create_hqe(hosts=[1])
1809 labels = list(hqe.get_labels())
1810 self.assertEqual([], labels)
1811
showard77182562009-06-10 00:16:05 +00001812
showardf1ae3542009-05-11 19:26:02 +00001813 def test_get_labels_metahost(self):
1814 hqe = self._create_hqe(metahosts=[2])
1815 self._check_hqe_labels(hqe, ['label2'])
1816
showard77182562009-06-10 00:16:05 +00001817
showardf1ae3542009-05-11 19:26:02 +00001818 def test_get_labels_dependancies(self):
1819 hqe = self._create_hqe(dependency_labels=(self.label3, self.label4),
1820 metahosts=[1])
1821 self._check_hqe_labels(hqe, ['label1', 'label3', 'label4'])
1822
1823
showardb2e2c322008-10-14 17:33:55 +00001824class JobTest(BaseSchedulerTest):
showard2bab8f42008-11-12 18:15:22 +00001825 def setUp(self):
1826 super(JobTest, self).setUp()
showard170873e2009-01-07 00:22:26 +00001827 self.god.stub_with(
1828 drone_manager.DroneManager, 'attach_file_to_execution',
1829 mock.mock_function('attach_file_to_execution',
1830 default_return_val='/test/path/tmp/foo'))
showard2bab8f42008-11-12 18:15:22 +00001831
1832
showard77182562009-06-10 00:16:05 +00001833 def _test_pre_job_tasks_helper(self):
1834 """
1835 Calls HQE._do_pre_run_job_tasks() and returns the task list after
1836 confirming that the last task is the SetEntryPendingTask.
1837 """
1838 queue_entry = monitor_db.HostQueueEntry.fetch('id = 1').next()
1839 pre_job_agent = queue_entry._do_run_pre_job_tasks()
1840 self.assert_(isinstance(pre_job_agent, monitor_db.Agent))
1841 pre_job_tasks = list(pre_job_agent.queue.queue)
1842 self.assertTrue(isinstance(pre_job_tasks[-1],
1843 monitor_db.SetEntryPendingTask))
1844
1845 return pre_job_tasks
showard2bab8f42008-11-12 18:15:22 +00001846
1847
showarde58e3f82008-11-20 19:04:59 +00001848 def _test_run_helper(self, expect_agent=True, expect_starting=False,
1849 expect_pending=False):
1850 if expect_starting:
1851 expected_status = models.HostQueueEntry.Status.STARTING
1852 elif expect_pending:
1853 expected_status = models.HostQueueEntry.Status.PENDING
1854 else:
1855 expected_status = models.HostQueueEntry.Status.VERIFYING
showardb2e2c322008-10-14 17:33:55 +00001856 job = monitor_db.Job.fetch('id = 1').next()
1857 queue_entry = monitor_db.HostQueueEntry.fetch('id = 1').next()
showard77182562009-06-10 00:16:05 +00001858 assert queue_entry.job is job
1859 agent = job.run_if_ready(queue_entry)
showardb2e2c322008-10-14 17:33:55 +00001860
showard2bab8f42008-11-12 18:15:22 +00001861 self.god.check_playback()
showard77182562009-06-10 00:16:05 +00001862 actual_status = models.HostQueueEntry.smart_get(1).status
1863 self.assertEquals(expected_status, actual_status)
showard2bab8f42008-11-12 18:15:22 +00001864
showard9976ce92008-10-15 20:28:13 +00001865 if not expect_agent:
1866 self.assertEquals(agent, None)
1867 return
1868
showardb2e2c322008-10-14 17:33:55 +00001869 self.assert_(isinstance(agent, monitor_db.Agent))
1870 tasks = list(agent.queue.queue)
1871 return tasks
1872
1873
showardc9ae1782009-01-30 01:42:37 +00001874 def _check_verify_task(self, verify_task):
1875 self.assert_(isinstance(verify_task, monitor_db.VerifyTask))
1876 self.assertEquals(verify_task.queue_entry.id, 1)
1877
1878
1879 def _check_pending_task(self, pending_task):
1880 self.assert_(isinstance(pending_task, monitor_db.SetEntryPendingTask))
1881 self.assertEquals(pending_task._queue_entry.id, 1)
1882
1883
showard77182562009-06-10 00:16:05 +00001884 def test_run_if_ready_delays(self):
1885 # Also tests Job.run_with_ready_delay() on atomic group jobs.
1886 django_job = self._create_job(hosts=[5, 6], atomic_group=1)
1887 job = monitor_db.Job(django_job.id)
1888 self.assertEqual(1, job.synch_count)
1889 django_hqes = list(models.HostQueueEntry.objects.filter(job=job.id))
1890 self.assertEqual(2, len(django_hqes))
1891 self.assertEqual(2, django_hqes[0].atomic_group.max_number_of_machines)
1892
1893 def set_hqe_status(django_hqe, status):
1894 django_hqe.status = status
1895 django_hqe.save()
1896 monitor_db.HostQueueEntry(django_hqe.id).host.set_status(status)
1897
1898 # An initial state, our synch_count is 1
1899 set_hqe_status(django_hqes[0], models.HostQueueEntry.Status.VERIFYING)
1900 set_hqe_status(django_hqes[1], models.HostQueueEntry.Status.PENDING)
1901
1902 # So that we don't depend on the config file value during the test.
1903 self.assert_(scheduler_config.config
1904 .secs_to_wait_for_atomic_group_hosts is not None)
1905 self.god.stub_with(scheduler_config.config,
1906 'secs_to_wait_for_atomic_group_hosts', 123456)
1907
1908 # Get the pending one as a monitor_db.HostQueueEntry object.
1909 pending_hqe = monitor_db.HostQueueEntry(django_hqes[1].id)
1910 self.assert_(not job._delay_ready_task)
1911 self.assertTrue(job.is_ready())
1912
1913 # Ready with one pending, one verifying and an atomic group should
1914 # result in a DelayCallTask to re-check if we're ready a while later.
1915 agent = job.run_if_ready(pending_hqe)
1916 self.assert_(job._delay_ready_task)
1917 self.assert_(isinstance(agent, monitor_db.Agent))
1918 tasks = list(agent.queue.queue)
1919 self.assertEqual(1, len(tasks))
1920 self.assert_(isinstance(tasks[0], monitor_db.DelayedCallTask))
1921 delay_task = tasks[0]
1922 self.assert_(not delay_task.is_done())
1923
1924 self.god.stub_function(job, 'run')
1925
1926 # Test that the DelayedCallTask's callback queued up above does the
1927 # correct thing and returns the Agent returned by job.run().
1928 job.run.expect_call(pending_hqe).and_return('Fake Agent')
1929 self.assertEqual('Fake Agent', delay_task._callback())
1930
1931 # A delay already exists, this must do nothing.
1932 self.assertEqual(None, job.run_with_ready_delay(pending_hqe))
1933
1934 # Adjust the delay deadline so that enough time has passed.
1935 job._delay_ready_task.end_time = time.time() - 111111
1936 job.run.expect_call(pending_hqe).and_return('Forty two')
1937 # ...the delay_expired condition should cause us to call run()
1938 self.assertEqual('Forty two', job.run_with_ready_delay(pending_hqe))
1939
1940 # Adjust the delay deadline back so that enough time has not passed.
1941 job._delay_ready_task.end_time = time.time() + 111111
1942 self.assertEqual(None, job.run_with_ready_delay(pending_hqe))
1943
1944 set_hqe_status(django_hqes[0], models.HostQueueEntry.Status.PENDING)
1945 # Now max_number_of_machines HQEs are in pending state. Remaining
1946 # delay will now be ignored.
1947 job.run.expect_call(pending_hqe).and_return('Watermelon')
1948 # ...the over_max_threshold test should cause us to call run()
1949 self.assertEqual('Watermelon', job.run_with_ready_delay(pending_hqe))
1950
1951 other_hqe = monitor_db.HostQueueEntry(django_hqes[0].id)
1952 self.assertTrue(pending_hqe.job is other_hqe.job)
1953 # DBObject classes should reuse instances so these should be the same.
1954 self.assertEqual(job, other_hqe.job)
1955 self.assertEqual(other_hqe.job, pending_hqe.job)
1956 # Be sure our delay was not lost during the other_hqe construction.
1957 self.assert_(job._delay_ready_task)
1958 self.assertFalse(job._delay_ready_task.is_done())
1959 self.assertFalse(job._delay_ready_task.aborted)
1960
1961 # We want the real run() to be called below.
1962 self.god.unstub(job, 'run')
1963
1964 # We pass in the other HQE this time the same way it would happen
1965 # for real when one host finishes verifying and enters pending.
1966 agent = job.run_if_ready(other_hqe)
1967
1968 # The delayed task must be aborted by the actual run() call above.
1969 self.assertTrue(job._delay_ready_task.aborted)
1970 self.assertFalse(job._delay_ready_task.success)
1971 self.assertTrue(job._delay_ready_task.is_done())
1972
1973 # Check that job run() and _finish_run() were called by the above:
1974 tasks = list(agent.queue.queue)
1975 self.assertEqual(1, len(tasks))
1976 self.assert_(isinstance(tasks[0], monitor_db.QueueTask))
1977 # Requery these hqes in order to verify the status from the DB.
1978 django_hqes = list(models.HostQueueEntry.objects.filter(job=job.id))
1979 for entry in django_hqes:
1980 self.assertEqual(models.HostQueueEntry.Status.STARTING,
1981 entry.status)
1982
1983 # We're already running, but more calls to run_with_ready_delay can
1984 # continue to come in due to straggler hosts enter Pending. Make
1985 # sure we don't do anything.
1986 self.assertEqual(None, job.run_with_ready_delay(pending_hqe))
1987
1988
1989 def test__atomic_and_has_started__on_atomic(self):
1990 self._create_job(hosts=[5, 6], atomic_group=1)
1991 job = monitor_db.Job.fetch('id = 1').next()
1992 self.assertFalse(job._atomic_and_has_started())
showardaf8b4ca2009-06-16 18:47:26 +00001993
showard77182562009-06-10 00:16:05 +00001994 self._update_hqe("status='Pending'")
1995 self.assertFalse(job._atomic_and_has_started())
1996 self._update_hqe("status='Verifying'")
1997 self.assertFalse(job._atomic_and_has_started())
showardaf8b4ca2009-06-16 18:47:26 +00001998 self.assertFalse(job._atomic_and_has_started())
1999 self._update_hqe("status='Failed'")
2000 self.assertFalse(job._atomic_and_has_started())
2001 self._update_hqe("status='Stopped'")
2002 self.assertFalse(job._atomic_and_has_started())
2003
showard77182562009-06-10 00:16:05 +00002004 self._update_hqe("status='Starting'")
2005 self.assertTrue(job._atomic_and_has_started())
2006 self._update_hqe("status='Completed'")
2007 self.assertTrue(job._atomic_and_has_started())
2008 self._update_hqe("status='Aborted'")
showard77182562009-06-10 00:16:05 +00002009
2010
2011 def test__atomic_and_has_started__not_atomic(self):
2012 self._create_job(hosts=[1, 2])
2013 job = monitor_db.Job.fetch('id = 1').next()
2014 self.assertFalse(job._atomic_and_has_started())
2015 self._update_hqe("status='Starting'")
2016 self.assertFalse(job._atomic_and_has_started())
2017
2018
showardb2e2c322008-10-14 17:33:55 +00002019 def test_run_asynchronous(self):
2020 self._create_job(hosts=[1, 2])
2021
showard77182562009-06-10 00:16:05 +00002022 tasks = self._test_pre_job_tasks_helper()
showardb2e2c322008-10-14 17:33:55 +00002023
showardc9ae1782009-01-30 01:42:37 +00002024 self.assertEquals(len(tasks), 2)
2025 verify_task, pending_task = tasks
2026 self._check_verify_task(verify_task)
2027 self._check_pending_task(pending_task)
showardb2e2c322008-10-14 17:33:55 +00002028
showardb2e2c322008-10-14 17:33:55 +00002029
showard9976ce92008-10-15 20:28:13 +00002030 def test_run_asynchronous_skip_verify(self):
2031 job = self._create_job(hosts=[1, 2])
2032 job.run_verify = False
2033 job.save()
2034
showard77182562009-06-10 00:16:05 +00002035 tasks = self._test_pre_job_tasks_helper()
showard9976ce92008-10-15 20:28:13 +00002036
2037 self.assertEquals(len(tasks), 1)
showardc9ae1782009-01-30 01:42:37 +00002038 pending_task = tasks[0]
2039 self._check_pending_task(pending_task)
showard9976ce92008-10-15 20:28:13 +00002040
2041
showardb2e2c322008-10-14 17:33:55 +00002042 def test_run_synchronous_verify(self):
2043 self._create_job(hosts=[1, 2], synchronous=True)
2044
showard77182562009-06-10 00:16:05 +00002045 tasks = self._test_pre_job_tasks_helper()
showardc9ae1782009-01-30 01:42:37 +00002046 self.assertEquals(len(tasks), 2)
2047 verify_task, pending_task = tasks
2048 self._check_verify_task(verify_task)
2049 self._check_pending_task(pending_task)
showardb2e2c322008-10-14 17:33:55 +00002050
2051
showard9976ce92008-10-15 20:28:13 +00002052 def test_run_synchronous_skip_verify(self):
2053 job = self._create_job(hosts=[1, 2], synchronous=True)
2054 job.run_verify = False
2055 job.save()
2056
showard77182562009-06-10 00:16:05 +00002057 tasks = self._test_pre_job_tasks_helper()
showardc9ae1782009-01-30 01:42:37 +00002058 self.assertEquals(len(tasks), 1)
2059 self._check_pending_task(tasks[0])
showard9976ce92008-10-15 20:28:13 +00002060
2061
showardb2e2c322008-10-14 17:33:55 +00002062 def test_run_synchronous_ready(self):
2063 self._create_job(hosts=[1, 2], synchronous=True)
showardd9ac4452009-02-07 02:04:37 +00002064 self._update_hqe("status='Pending', execution_subdir=''")
showardb2e2c322008-10-14 17:33:55 +00002065
showarde58e3f82008-11-20 19:04:59 +00002066 tasks = self._test_run_helper(expect_starting=True)
showardb2e2c322008-10-14 17:33:55 +00002067 self.assertEquals(len(tasks), 1)
2068 queue_task = tasks[0]
2069
2070 self.assert_(isinstance(queue_task, monitor_db.QueueTask))
2071 self.assertEquals(queue_task.job.id, 1)
2072 hqe_ids = [hqe.id for hqe in queue_task.queue_entries]
2073 self.assertEquals(hqe_ids, [1, 2])
2074
2075
showard77182562009-06-10 00:16:05 +00002076 def test_run_atomic_group_already_started(self):
2077 self._create_job(hosts=[5, 6], atomic_group=1, synchronous=True)
2078 self._update_hqe("status='Starting', execution_subdir=''")
2079
2080 job = monitor_db.Job.fetch('id = 1').next()
2081 queue_entry = monitor_db.HostQueueEntry.fetch('id = 1').next()
2082 assert queue_entry.job is job
2083 self.assertEqual(None, job.run(queue_entry))
2084
2085 self.god.check_playback()
2086
2087
showardf1ae3542009-05-11 19:26:02 +00002088 def test_run_synchronous_atomic_group_ready(self):
2089 self._create_job(hosts=[5, 6], atomic_group=1, synchronous=True)
2090 self._update_hqe("status='Pending', execution_subdir=''")
2091
2092 tasks = self._test_run_helper(expect_starting=True)
2093 self.assertEquals(len(tasks), 1)
2094 queue_task = tasks[0]
2095
2096 self.assert_(isinstance(queue_task, monitor_db.QueueTask))
showard77182562009-06-10 00:16:05 +00002097 # Atomic group jobs that do not depend on a specific label in the
2098 # atomic group will use the atomic group name as their group name.
showardf1ae3542009-05-11 19:26:02 +00002099 self.assertEquals(queue_task.group_name, 'atomic1')
2100
2101
2102 def test_run_synchronous_atomic_group_with_label_ready(self):
2103 job = self._create_job(hosts=[5, 6], atomic_group=1, synchronous=True)
2104 job.dependency_labels.add(self.label4)
2105 self._update_hqe("status='Pending', execution_subdir=''")
2106
2107 tasks = self._test_run_helper(expect_starting=True)
2108 self.assertEquals(len(tasks), 1)
2109 queue_task = tasks[0]
2110
2111 self.assert_(isinstance(queue_task, monitor_db.QueueTask))
2112 # Atomic group jobs that also specify a label in the atomic group
2113 # will use the label name as their group name.
2114 self.assertEquals(queue_task.group_name, 'label4')
2115
2116
showard21baa452008-10-21 00:08:39 +00002117 def test_reboot_before_always(self):
2118 job = self._create_job(hosts=[1])
showard0fc38302008-10-23 00:44:07 +00002119 job.reboot_before = models.RebootBefore.ALWAYS
showard21baa452008-10-21 00:08:39 +00002120 job.save()
2121
showard77182562009-06-10 00:16:05 +00002122 tasks = self._test_pre_job_tasks_helper()
showardc9ae1782009-01-30 01:42:37 +00002123 self.assertEquals(len(tasks), 3)
showard45ae8192008-11-05 19:32:53 +00002124 cleanup_task = tasks[0]
2125 self.assert_(isinstance(cleanup_task, monitor_db.CleanupTask))
2126 self.assertEquals(cleanup_task.host.id, 1)
showard21baa452008-10-21 00:08:39 +00002127
2128
2129 def _test_reboot_before_if_dirty_helper(self, expect_reboot):
2130 job = self._create_job(hosts=[1])
showard0fc38302008-10-23 00:44:07 +00002131 job.reboot_before = models.RebootBefore.IF_DIRTY
showard21baa452008-10-21 00:08:39 +00002132 job.save()
2133
showard77182562009-06-10 00:16:05 +00002134 tasks = self._test_pre_job_tasks_helper()
showardc9ae1782009-01-30 01:42:37 +00002135 self.assertEquals(len(tasks), expect_reboot and 3 or 2)
showard21baa452008-10-21 00:08:39 +00002136 if expect_reboot:
showard45ae8192008-11-05 19:32:53 +00002137 cleanup_task = tasks[0]
2138 self.assert_(isinstance(cleanup_task, monitor_db.CleanupTask))
2139 self.assertEquals(cleanup_task.host.id, 1)
showard21baa452008-10-21 00:08:39 +00002140
showard77182562009-06-10 00:16:05 +00002141
showard21baa452008-10-21 00:08:39 +00002142 def test_reboot_before_if_dirty(self):
2143 models.Host.smart_get(1).update_object(dirty=True)
2144 self._test_reboot_before_if_dirty_helper(True)
2145
2146
2147 def test_reboot_before_not_dirty(self):
2148 models.Host.smart_get(1).update_object(dirty=False)
2149 self._test_reboot_before_if_dirty_helper(False)
2150
2151
showardf1ae3542009-05-11 19:26:02 +00002152 def test_next_group_name(self):
2153 django_job = self._create_job(metahosts=[1])
2154 job = monitor_db.Job(id=django_job.id)
2155 self.assertEqual('group0', job._next_group_name())
2156
2157 for hqe in django_job.hostqueueentry_set.filter():
2158 hqe.execution_subdir = 'my_rack.group0'
2159 hqe.save()
2160 self.assertEqual('my_rack.group1', job._next_group_name('my/rack'))
2161
2162
2163class TopLevelFunctionsTest(unittest.TestCase):
mblighe7d9c602009-07-02 19:02:33 +00002164 def setUp(self):
2165 self.god = mock.mock_god()
2166
2167
2168 def tearDown(self):
2169 self.god.unstub_all()
2170
2171
showardf1ae3542009-05-11 19:26:02 +00002172 def test_autoserv_command_line(self):
2173 machines = 'abcd12,efgh34'
2174 results_dir = '/fake/path'
2175 extra_args = ['-Z', 'hello']
2176 expected_command_line = [monitor_db._autoserv_path, '-p',
2177 '-m', machines, '-r', results_dir]
2178
2179 command_line = monitor_db._autoserv_command_line(
2180 machines, results_dir, extra_args)
showarde9c69362009-06-30 01:58:03 +00002181 self.assertEqual(expected_command_line + ['--verbose'] + extra_args,
2182 command_line)
showardf1ae3542009-05-11 19:26:02 +00002183
2184 class FakeJob(object):
2185 owner = 'Bob'
2186 name = 'fake job name'
mblighe7d9c602009-07-02 19:02:33 +00002187 id = 1337
2188
2189 class FakeHQE(object):
2190 job = FakeJob
showardf1ae3542009-05-11 19:26:02 +00002191
2192 command_line = monitor_db._autoserv_command_line(
mblighe7d9c602009-07-02 19:02:33 +00002193 machines, results_dir, extra_args=[], queue_entry=FakeHQE,
showarde9c69362009-06-30 01:58:03 +00002194 verbose=False)
showardf1ae3542009-05-11 19:26:02 +00002195 self.assertEqual(expected_command_line +
2196 ['-u', FakeJob.owner, '-l', FakeJob.name],
2197 command_line)
2198
showard21baa452008-10-21 00:08:39 +00002199
showardce38e0c2008-05-29 19:36:16 +00002200if __name__ == '__main__':
jadmanski0afbb632008-06-06 21:10:57 +00002201 unittest.main()