blob: f4cfa83ab3eb0d4dc9bbe98a7d3fc5b9bcc7587b [file] [log] [blame]
showardce38e0c2008-05-29 19:36:16 +00001#!/usr/bin/python
2
jadmanski3d161b02008-06-06 15:43:36 +00003import unittest, time, subprocess, os, StringIO, tempfile
showardce38e0c2008-05-29 19:36:16 +00004import MySQLdb
5import common
jadmanskifb7cfb12008-07-09 14:13:21 +00006from autotest_lib.client.common_lib import global_config, host_protections
jadmanski3d161b02008-06-06 15:43:36 +00007from autotest_lib.client.common_lib.test_utils import mock
showard442e71e2008-10-06 10:05:20 +00008from autotest_lib.database import database_connection, migrate
showardb1e51872008-10-07 11:08:18 +00009from autotest_lib.scheduler import monitor_db
jadmanski3d161b02008-06-06 15:43:36 +000010
showardb1e51872008-10-07 11:08:18 +000011from autotest_lib.frontend import django_test_utils
12django_test_utils.setup_test_environ()
13from autotest_lib.frontend.afe import models
showardce38e0c2008-05-29 19:36:16 +000014
15_DEBUG = False
16
showard04c82c52008-05-29 19:38:12 +000017class Dummy(object):
jadmanski0afbb632008-06-06 21:10:57 +000018 'Dummy object that can have attribute assigned to it'
showard04c82c52008-05-29 19:38:12 +000019
showard56193bb2008-08-13 20:07:41 +000020
21class IsRow(mock.argument_comparator):
22 def __init__(self, row_id):
23 self.row_id = row_id
showardce38e0c2008-05-29 19:36:16 +000024
25
showard56193bb2008-08-13 20:07:41 +000026 def is_satisfied_by(self, parameter):
27 return list(parameter)[0] == self.row_id
28
29
30 def __str__(self):
31 return 'row with id %s' % self.row_id
32
33
34class BaseDispatcherTest(unittest.TestCase):
showard50c0e712008-09-22 16:20:37 +000035 _config_section = 'AUTOTEST_WEB'
showardb1e51872008-10-07 11:08:18 +000036 _test_db_initialized = False
showardce38e0c2008-05-29 19:36:16 +000037
jadmanski0afbb632008-06-06 21:10:57 +000038 def _do_query(self, sql):
showardb1e51872008-10-07 11:08:18 +000039 self._database.execute(sql)
showardce38e0c2008-05-29 19:36:16 +000040
41
showardb1e51872008-10-07 11:08:18 +000042 @classmethod
43 def _initialize_test_db(cls):
44 if cls._test_db_initialized:
45 return
46 temp_fd, cls._test_db_file = tempfile.mkstemp(suffix='.monitor_test')
47 os.close(temp_fd)
48 django_test_utils.set_test_database(cls._test_db_file)
49 django_test_utils.run_syncdb()
50 cls._test_db_backup = django_test_utils.backup_test_database()
51 cls._test_db_initialized = True
showardce38e0c2008-05-29 19:36:16 +000052
53
showard50c0e712008-09-22 16:20:37 +000054 def _open_test_db(self):
showardb1e51872008-10-07 11:08:18 +000055 self._initialize_test_db()
56 django_test_utils.restore_test_database(self._test_db_backup)
57 self._database = (
58 database_connection.DatabaseConnection.get_test_database(
59 self._test_db_file))
60 self._database.connect()
61 self._database.debug = _DEBUG
showardce38e0c2008-05-29 19:36:16 +000062
63
jadmanski0afbb632008-06-06 21:10:57 +000064 def _close_test_db(self):
showardb1e51872008-10-07 11:08:18 +000065 self._database.disconnect()
showardce38e0c2008-05-29 19:36:16 +000066
67
showard56193bb2008-08-13 20:07:41 +000068 def _set_monitor_stubs(self):
showardb1e51872008-10-07 11:08:18 +000069 monitor_db._db = self._database
showard56193bb2008-08-13 20:07:41 +000070
71
jadmanski0afbb632008-06-06 21:10:57 +000072 def _fill_in_test_data(self):
showardb1e51872008-10-07 11:08:18 +000073 user = models.User.objects.create(login='my_user')
74 acl_group = models.AclGroup.objects.create(name='my_acl')
75 acl_group.users.add(user)
76
77 hosts = [models.Host.objects.create(hostname=hostname) for hostname in
78 ('host1', 'host2', 'host3', 'host4')]
79 acl_group.hosts = hosts
80
81 labels = [models.Label.objects.create(name=name) for name in
82 ('label1', 'label2', 'label3')]
83 labels[2].only_if_needed = True
84 labels[2].save()
85 hosts[0].labels.add(labels[0])
86 hosts[1].labels.add(labels[1])
showardce38e0c2008-05-29 19:36:16 +000087
88
showard56193bb2008-08-13 20:07:41 +000089 def setUp(self):
90 self.god = mock.mock_god()
showard50c0e712008-09-22 16:20:37 +000091 self._open_test_db()
showard56193bb2008-08-13 20:07:41 +000092 self._fill_in_test_data()
93 self._set_monitor_stubs()
94 self._dispatcher = monitor_db.Dispatcher()
showardce38e0c2008-05-29 19:36:16 +000095
96
showard56193bb2008-08-13 20:07:41 +000097 def tearDown(self):
98 self._close_test_db()
99 self.god.unstub_all()
showardce38e0c2008-05-29 19:36:16 +0000100
101
showard4c5374f2008-09-04 17:02:56 +0000102 def _create_job(self, hosts=[], metahosts=[], priority=0, active=0,
103 synchronous=False):
104 synch_type = synchronous and 2 or 1
showardb1e51872008-10-07 11:08:18 +0000105 job = models.Job.objects.create(name='test', owner='my_user',
106 priority=priority,
107 synch_type=synch_type)
jadmanski0afbb632008-06-06 21:10:57 +0000108 for host_id in hosts:
showardb1e51872008-10-07 11:08:18 +0000109 models.HostQueueEntry.objects.create(job=job, priority=priority,
110 host_id=host_id, active=active)
111 models.IneligibleHostQueue.objects.create(job=job, host_id=host_id)
jadmanski0afbb632008-06-06 21:10:57 +0000112 for label_id in metahosts:
showardb1e51872008-10-07 11:08:18 +0000113 models.HostQueueEntry.objects.create(job=job, priority=priority,
114 meta_host_id=label_id,
115 active=active)
116 return job
showardce38e0c2008-05-29 19:36:16 +0000117
118
jadmanski0afbb632008-06-06 21:10:57 +0000119 def _create_job_simple(self, hosts, use_metahost=False,
120 priority=0, active=0):
121 'An alternative interface to _create_job'
122 args = {'hosts' : [], 'metahosts' : []}
123 if use_metahost:
124 args['metahosts'] = hosts
125 else:
126 args['hosts'] = hosts
showardb1e51872008-10-07 11:08:18 +0000127 return self._create_job(priority=priority, active=active, **args)
showardce38e0c2008-05-29 19:36:16 +0000128
129
showard56193bb2008-08-13 20:07:41 +0000130 def _update_hqe(self, set, where=''):
131 query = 'UPDATE host_queue_entries SET ' + set
132 if where:
133 query += ' WHERE ' + where
134 self._do_query(query)
135
136
137class DispatcherSchedulingTest(BaseDispatcherTest):
138 _jobs_scheduled = []
139
140 def _set_monitor_stubs(self):
141 super(DispatcherSchedulingTest, self)._set_monitor_stubs()
142 def run_stub(hqe_self, assigned_host=None):
143 hqe_self.set_status('Starting')
144 if hqe_self.meta_host:
145 host = assigned_host
146 else:
147 host = hqe_self.host
148 self._record_job_scheduled(hqe_self.job.id, host.id)
149 return Dummy()
150 monitor_db.HostQueueEntry.run = run_stub
151
152
153 def _record_job_scheduled(self, job_id, host_id):
154 record = (job_id, host_id)
155 self.assert_(record not in self._jobs_scheduled,
156 'Job %d scheduled on host %d twice' %
157 (job_id, host_id))
158 self._jobs_scheduled.append(record)
159
160
161 def _assert_job_scheduled_on(self, job_id, host_id):
162 record = (job_id, host_id)
163 self.assert_(record in self._jobs_scheduled,
164 'Job %d not scheduled on host %d as expected\n'
165 'Jobs scheduled: %s' %
166 (job_id, host_id, self._jobs_scheduled))
167 self._jobs_scheduled.remove(record)
168
169
170 def _check_for_extra_schedulings(self):
171 if len(self._jobs_scheduled) != 0:
172 self.fail('Extra jobs scheduled: ' +
173 str(self._jobs_scheduled))
174
175
jadmanski0afbb632008-06-06 21:10:57 +0000176 def _convert_jobs_to_metahosts(self, *job_ids):
177 sql_tuple = '(' + ','.join(str(i) for i in job_ids) + ')'
178 self._do_query('UPDATE host_queue_entries SET '
179 'meta_host=host_id, host_id=NULL '
180 'WHERE job_id IN ' + sql_tuple)
showardce38e0c2008-05-29 19:36:16 +0000181
182
jadmanski0afbb632008-06-06 21:10:57 +0000183 def _lock_host(self, host_id):
184 self._do_query('UPDATE hosts SET locked=1 WHERE id=' +
185 str(host_id))
showardce38e0c2008-05-29 19:36:16 +0000186
187
jadmanski0afbb632008-06-06 21:10:57 +0000188 def setUp(self):
showard56193bb2008-08-13 20:07:41 +0000189 super(DispatcherSchedulingTest, self).setUp()
jadmanski0afbb632008-06-06 21:10:57 +0000190 self._jobs_scheduled = []
showardce38e0c2008-05-29 19:36:16 +0000191
192
jadmanski0afbb632008-06-06 21:10:57 +0000193 def _test_basic_scheduling_helper(self, use_metahosts):
194 'Basic nonmetahost scheduling'
195 self._create_job_simple([1], use_metahosts)
196 self._create_job_simple([2], use_metahosts)
197 self._dispatcher._schedule_new_jobs()
198 self._assert_job_scheduled_on(1, 1)
199 self._assert_job_scheduled_on(2, 2)
200 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000201
202
jadmanski0afbb632008-06-06 21:10:57 +0000203 def _test_priorities_helper(self, use_metahosts):
204 'Test prioritization ordering'
205 self._create_job_simple([1], use_metahosts)
206 self._create_job_simple([2], use_metahosts)
207 self._create_job_simple([1,2], use_metahosts)
208 self._create_job_simple([1], use_metahosts, priority=1)
209 self._dispatcher._schedule_new_jobs()
210 self._assert_job_scheduled_on(4, 1) # higher priority
211 self._assert_job_scheduled_on(2, 2) # earlier job over later
212 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000213
214
jadmanski0afbb632008-06-06 21:10:57 +0000215 def _test_hosts_ready_helper(self, use_metahosts):
216 """
217 Only hosts that are status=Ready, unlocked and not invalid get
218 scheduled.
219 """
220 self._create_job_simple([1], use_metahosts)
221 self._do_query('UPDATE hosts SET status="Running" WHERE id=1')
222 self._dispatcher._schedule_new_jobs()
223 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000224
jadmanski0afbb632008-06-06 21:10:57 +0000225 self._do_query('UPDATE hosts SET status="Ready", locked=1 '
226 'WHERE id=1')
227 self._dispatcher._schedule_new_jobs()
228 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000229
jadmanski0afbb632008-06-06 21:10:57 +0000230 self._do_query('UPDATE hosts SET locked=0, invalid=1 '
231 'WHERE id=1')
232 self._dispatcher._schedule_new_jobs()
showard5df2b192008-07-03 19:51:57 +0000233 if not use_metahosts:
234 self._assert_job_scheduled_on(1, 1)
jadmanski0afbb632008-06-06 21:10:57 +0000235 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000236
237
jadmanski0afbb632008-06-06 21:10:57 +0000238 def _test_hosts_idle_helper(self, use_metahosts):
239 'Only idle hosts get scheduled'
240 self._create_job(hosts=[1], active=1)
241 self._create_job_simple([1], use_metahosts)
242 self._dispatcher._schedule_new_jobs()
243 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000244
245
showard63a34772008-08-18 19:32:50 +0000246 def _test_obey_ACLs_helper(self, use_metahosts):
247 self._do_query('DELETE FROM acl_groups_hosts WHERE host_id=1')
248 self._create_job_simple([1], use_metahosts)
249 self._dispatcher._schedule_new_jobs()
250 self._check_for_extra_schedulings()
251
252
showard989f25d2008-10-01 11:38:11 +0000253 def _test_only_if_needed_labels_helper(self, use_metahosts):
254 # apply only_if_needed label3 to host1
showardb1e51872008-10-07 11:08:18 +0000255 label3 = models.Label.smart_get('label3')
256 models.Host.smart_get('host1').labels.add(label3)
257
258 job = self._create_job_simple([1], use_metahosts)
showard989f25d2008-10-01 11:38:11 +0000259 # if the job doesn't depend on label3, there should be no scheduling
260 self._dispatcher._schedule_new_jobs()
261 self._check_for_extra_schedulings()
262
263 # now make the job depend on label3
showardb1e51872008-10-07 11:08:18 +0000264 job.dependency_labels.add(label3)
showard989f25d2008-10-01 11:38:11 +0000265 self._dispatcher._schedule_new_jobs()
266 self._assert_job_scheduled_on(1, 1)
267 self._check_for_extra_schedulings()
268
269 if use_metahosts:
270 # should also work if the metahost is the only_if_needed label
271 self._do_query('DELETE FROM jobs_dependency_labels')
272 self._create_job(metahosts=[3])
273 self._dispatcher._schedule_new_jobs()
274 self._assert_job_scheduled_on(2, 1)
275 self._check_for_extra_schedulings()
276
277
jadmanski0afbb632008-06-06 21:10:57 +0000278 def test_basic_scheduling(self):
279 self._test_basic_scheduling_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000280
281
jadmanski0afbb632008-06-06 21:10:57 +0000282 def test_priorities(self):
283 self._test_priorities_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000284
285
jadmanski0afbb632008-06-06 21:10:57 +0000286 def test_hosts_ready(self):
287 self._test_hosts_ready_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000288
289
jadmanski0afbb632008-06-06 21:10:57 +0000290 def test_hosts_idle(self):
291 self._test_hosts_idle_helper(False)
showardce38e0c2008-05-29 19:36:16 +0000292
293
showard63a34772008-08-18 19:32:50 +0000294 def test_obey_ACLs(self):
295 self._test_obey_ACLs_helper(False)
296
297
showard989f25d2008-10-01 11:38:11 +0000298 def test_only_if_needed_labels(self):
299 self._test_only_if_needed_labels_helper(False)
300
301
showard63a34772008-08-18 19:32:50 +0000302 def test_non_metahost_on_invalid_host(self):
303 """
304 Non-metahost entries can get scheduled on invalid hosts (this is how
305 one-time hosts work).
306 """
307 self._do_query('UPDATE hosts SET invalid=1')
308 self._test_basic_scheduling_helper(False)
309
310
jadmanski0afbb632008-06-06 21:10:57 +0000311 def test_metahost_scheduling(self):
showard63a34772008-08-18 19:32:50 +0000312 """
313 Basic metahost scheduling
314 """
jadmanski0afbb632008-06-06 21:10:57 +0000315 self._test_basic_scheduling_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000316
317
jadmanski0afbb632008-06-06 21:10:57 +0000318 def test_metahost_priorities(self):
319 self._test_priorities_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000320
321
jadmanski0afbb632008-06-06 21:10:57 +0000322 def test_metahost_hosts_ready(self):
323 self._test_hosts_ready_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000324
325
jadmanski0afbb632008-06-06 21:10:57 +0000326 def test_metahost_hosts_idle(self):
327 self._test_hosts_idle_helper(True)
showardce38e0c2008-05-29 19:36:16 +0000328
329
showard63a34772008-08-18 19:32:50 +0000330 def test_metahost_obey_ACLs(self):
331 self._test_obey_ACLs_helper(True)
332
333
showard989f25d2008-10-01 11:38:11 +0000334 def test_metahost_only_if_needed_labels(self):
335 self._test_only_if_needed_labels_helper(True)
336
337
jadmanski0afbb632008-06-06 21:10:57 +0000338 def test_nonmetahost_over_metahost(self):
339 """
340 Non-metahost entries should take priority over metahost entries
341 for the same host
342 """
343 self._create_job(metahosts=[1])
344 self._create_job(hosts=[1])
345 self._dispatcher._schedule_new_jobs()
346 self._assert_job_scheduled_on(2, 1)
347 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000348
349
jadmanski0afbb632008-06-06 21:10:57 +0000350 def test_metahosts_obey_blocks(self):
351 """
352 Metahosts can't get scheduled on hosts already scheduled for
353 that job.
354 """
355 self._create_job(metahosts=[1], hosts=[1])
356 # make the nonmetahost entry complete, so the metahost can try
357 # to get scheduled
showard56193bb2008-08-13 20:07:41 +0000358 self._update_hqe(set='complete = 1', where='host_id=1')
jadmanski0afbb632008-06-06 21:10:57 +0000359 self._dispatcher._schedule_new_jobs()
360 self._check_for_extra_schedulings()
showardce38e0c2008-05-29 19:36:16 +0000361
362
showard56193bb2008-08-13 20:07:41 +0000363 def test_only_schedule_queued_entries(self):
364 self._create_job(metahosts=[1])
365 self._update_hqe(set='active=1, host_id=2')
366 self._dispatcher._schedule_new_jobs()
367 self._check_for_extra_schedulings()
368
369
showard4c5374f2008-09-04 17:02:56 +0000370class DispatcherThrottlingTest(BaseDispatcherTest):
371 """
372 Test that the dispatcher throttles:
373 * total number of running processes
374 * number of processes started per cycle
375 """
376 _MAX_RUNNING = 3
377 _MAX_STARTED = 2
378
379 def setUp(self):
380 super(DispatcherThrottlingTest, self).setUp()
381 self._dispatcher.max_running_processes = self._MAX_RUNNING
382 self._dispatcher.max_processes_started_per_cycle = self._MAX_STARTED
383
384
385 class DummyAgent(object):
386 _is_running = False
387 _is_done = False
388 num_processes = 1
389
390 def is_running(self):
391 return self._is_running
392
393
394 def tick(self):
395 self._is_running = True
396
397
398 def is_done(self):
399 return self._is_done
400
401
402 def set_done(self, done):
403 self._is_done = done
404 self._is_running = not done
405
406
407 def _setup_some_agents(self, num_agents):
408 self._agents = [self.DummyAgent() for i in xrange(num_agents)]
409 self._dispatcher._agents = list(self._agents)
410
411
412 def _run_a_few_cycles(self):
413 for i in xrange(4):
414 self._dispatcher._handle_agents()
415
416
417 def _assert_agents_started(self, indexes, is_started=True):
418 for i in indexes:
419 self.assert_(self._agents[i].is_running() == is_started,
420 'Agent %d %sstarted' %
421 (i, is_started and 'not ' or ''))
422
423
424 def _assert_agents_not_started(self, indexes):
425 self._assert_agents_started(indexes, False)
426
427
428 def test_throttle_total(self):
429 self._setup_some_agents(4)
430 self._run_a_few_cycles()
431 self._assert_agents_started([0, 1, 2])
432 self._assert_agents_not_started([3])
433
434
435 def test_throttle_per_cycle(self):
436 self._setup_some_agents(3)
437 self._dispatcher._handle_agents()
438 self._assert_agents_started([0, 1])
439 self._assert_agents_not_started([2])
440
441
442 def test_throttle_with_synchronous(self):
443 self._setup_some_agents(2)
444 self._agents[0].num_processes = 3
445 self._run_a_few_cycles()
446 self._assert_agents_started([0])
447 self._assert_agents_not_started([1])
448
449
450 def test_large_agent_starvation(self):
451 """
452 Ensure large agents don't get starved by lower-priority agents.
453 """
454 self._setup_some_agents(3)
455 self._agents[1].num_processes = 3
456 self._run_a_few_cycles()
457 self._assert_agents_started([0])
458 self._assert_agents_not_started([1, 2])
459
460 self._agents[0].set_done(True)
461 self._run_a_few_cycles()
462 self._assert_agents_started([1])
463 self._assert_agents_not_started([2])
464
465
466 def test_zero_process_agent(self):
467 self._setup_some_agents(5)
468 self._agents[4].num_processes = 0
469 self._run_a_few_cycles()
470 self._assert_agents_started([0, 1, 2, 4])
471 self._assert_agents_not_started([3])
472
473
showard56193bb2008-08-13 20:07:41 +0000474class AbortTest(BaseDispatcherTest):
475 """
476 Test both the dispatcher abort functionality and AbortTask.
477 """
478 def setUp(self):
479 super(AbortTest, self).setUp()
480 self.god.stub_class(monitor_db, 'RebootTask')
481 self.god.stub_class(monitor_db, 'VerifyTask')
482 self.god.stub_class(monitor_db, 'AbortTask')
483 self.god.stub_class(monitor_db, 'HostQueueEntry')
484 self.god.stub_class(monitor_db, 'Agent')
485
486
487 def _setup_queue_entries(self, host_id, hqe_id):
488 host = monitor_db.Host(id=host_id)
489 self.god.stub_function(host, 'set_status')
490 hqe = monitor_db.HostQueueEntry.expect_new(row=IsRow(hqe_id))
491 hqe.id = hqe_id
492 return host, hqe
493
494
495 def _setup_abort_expects(self, host, hqe, abort_agent=None):
496 hqe.get_host.expect_call().and_return(host)
497 reboot_task = monitor_db.RebootTask.expect_new(host)
498 verify_task = monitor_db.VerifyTask.expect_new(host=host)
499 if abort_agent:
500 abort_task = monitor_db.AbortTask.expect_new(hqe, [abort_agent])
501 tasks = [mock.is_instance_comparator(monitor_db.AbortTask)]
502 else:
503 hqe.set_status.expect_call('Aborted')
504 host.set_status.expect_call('Rebooting')
505 tasks = []
506 tasks += [reboot_task, verify_task]
507 agent = monitor_db.Agent.expect_new(tasks=tasks,
508 queue_entry_ids=[hqe.id])
509 agent.queue_entry_ids = [hqe.id]
510 return agent
511
512
513 def test_find_aborting_inactive(self):
514 self._create_job(hosts=[1, 2])
515 self._update_hqe(set='status="Abort"')
516
517 host1, hqe1 = self._setup_queue_entries(1, 1)
518 host2, hqe2 = self._setup_queue_entries(2, 2)
519 agent1 = self._setup_abort_expects(host1, hqe1)
520 agent2 = self._setup_abort_expects(host2, hqe2)
521
522 self._dispatcher._find_aborting()
523
524 self.assertEquals(self._dispatcher._agents, [agent1, agent2])
525 self.god.check_playback()
526
527
528 def test_find_aborting_active(self):
529 self._create_job(hosts=[1, 2])
530 self._update_hqe(set='status="Abort", active=1')
531 # have to make an Agent for the active HQEs
532 task = self.god.create_mock_class(monitor_db.QueueTask, 'QueueTask')
533 agent = self.god.create_mock_class(monitor_db.Agent, 'OldAgent')
534 agent.queue_entry_ids = [1, 2]
535 self._dispatcher.add_agent(agent)
536
537 host1, hqe1 = self._setup_queue_entries(1, 1)
538 host2, hqe2 = self._setup_queue_entries(2, 2)
539 agent1 = self._setup_abort_expects(host1, hqe1, abort_agent=agent)
540 agent2 = self._setup_abort_expects(host2, hqe2)
541
542 self._dispatcher._find_aborting()
543
544 self.assertEquals(self._dispatcher._agents, [agent1, agent2])
545 self.god.check_playback()
546
547
jadmanski3d161b02008-06-06 15:43:36 +0000548class PidfileRunMonitorTest(unittest.TestCase):
jadmanski0afbb632008-06-06 21:10:57 +0000549 results_dir = '/test/path'
550 pidfile_path = os.path.join(results_dir, monitor_db.AUTOSERV_PID_FILE)
551 pid = 12345
552 args = ('nice -n 10 autoserv -P 123-myuser/myhost -p -n '
553 '-r ' + results_dir + ' -b -u myuser -l my-job-name '
554 '-m myhost /tmp/filejx43Zi -c')
555 bad_args = args.replace(results_dir, '/random/results/dir')
jadmanski3d161b02008-06-06 15:43:36 +0000556
jadmanski0afbb632008-06-06 21:10:57 +0000557 def setUp(self):
558 self.god = mock.mock_god()
559 self.god.stub_function(monitor_db, 'open')
560 self.god.stub_function(os.path, 'exists')
561 self.god.stub_function(monitor_db.email_manager,
562 'enqueue_notify_email')
563 self.monitor = monitor_db.PidfileRunMonitor(self.results_dir)
jadmanski3d161b02008-06-06 15:43:36 +0000564
565
jadmanski0afbb632008-06-06 21:10:57 +0000566 def tearDown(self):
567 self.god.unstub_all()
jadmanski3d161b02008-06-06 15:43:36 +0000568
569
jadmanski0afbb632008-06-06 21:10:57 +0000570 def set_not_yet_run(self):
571 os.path.exists.expect_call(self.pidfile_path).and_return(False)
jadmanski3d161b02008-06-06 15:43:36 +0000572
573
jadmanski0afbb632008-06-06 21:10:57 +0000574 def setup_pidfile(self, pidfile_contents):
575 os.path.exists.expect_call(self.pidfile_path).and_return(True)
576 pidfile = StringIO.StringIO(pidfile_contents)
577 monitor_db.open.expect_call(
578 self.pidfile_path, 'r').and_return(pidfile)
jadmanski3d161b02008-06-06 15:43:36 +0000579
580
jadmanski0afbb632008-06-06 21:10:57 +0000581 def set_running(self):
582 self.setup_pidfile(str(self.pid) + '\n')
jadmanski3d161b02008-06-06 15:43:36 +0000583
584
jadmanski0afbb632008-06-06 21:10:57 +0000585 def set_complete(self, error_code):
586 self.setup_pidfile(str(self.pid) + '\n' +
587 str(error_code) + '\n')
jadmanski3d161b02008-06-06 15:43:36 +0000588
589
jadmanski0afbb632008-06-06 21:10:57 +0000590 def _test_read_pidfile_helper(self, expected_pid, expected_exit_status):
591 pid, exit_status = self.monitor.read_pidfile()
592 self.assertEquals(pid, expected_pid)
593 self.assertEquals(exit_status, expected_exit_status)
594 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +0000595
596
jadmanski0afbb632008-06-06 21:10:57 +0000597 def test_read_pidfile(self):
598 self.set_not_yet_run()
599 self._test_read_pidfile_helper(None, None)
jadmanski3d161b02008-06-06 15:43:36 +0000600
jadmanski0afbb632008-06-06 21:10:57 +0000601 self.set_running()
602 self._test_read_pidfile_helper(self.pid, None)
jadmanski3d161b02008-06-06 15:43:36 +0000603
jadmanski0afbb632008-06-06 21:10:57 +0000604 self.set_complete(123)
605 self._test_read_pidfile_helper(self.pid, 123)
jadmanski3d161b02008-06-06 15:43:36 +0000606
607
jadmanski0afbb632008-06-06 21:10:57 +0000608 def test_read_pidfile_error(self):
609 self.setup_pidfile('asdf')
610 self.assertRaises(monitor_db.PidfileException,
611 self.monitor.read_pidfile)
612 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +0000613
614
jadmanski0afbb632008-06-06 21:10:57 +0000615 def setup_proc_cmdline(self, args):
616 proc_cmdline = args.replace(' ', '\x00')
617 proc_file = StringIO.StringIO(proc_cmdline)
618 monitor_db.open.expect_call(
619 '/proc/%d/cmdline' % self.pid, 'r').and_return(proc_file)
jadmanski3d161b02008-06-06 15:43:36 +0000620
621
jadmanski0afbb632008-06-06 21:10:57 +0000622 def setup_find_autoservs(self, process_dict):
623 self.god.stub_class_method(monitor_db.Dispatcher,
624 'find_autoservs')
625 monitor_db.Dispatcher.find_autoservs.expect_call().and_return(
626 process_dict)
jadmanski3d161b02008-06-06 15:43:36 +0000627
628
jadmanski0afbb632008-06-06 21:10:57 +0000629 def _test_get_pidfile_info_helper(self, expected_pid,
630 expected_exit_status):
631 pid, exit_status = self.monitor.get_pidfile_info()
632 self.assertEquals(pid, expected_pid)
633 self.assertEquals(exit_status, expected_exit_status)
634 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +0000635
636
jadmanski0afbb632008-06-06 21:10:57 +0000637 def test_get_pidfile_info(self):
638 'normal cases for get_pidfile_info'
639 # running
640 self.set_running()
641 self.setup_proc_cmdline(self.args)
642 self._test_get_pidfile_info_helper(self.pid, None)
jadmanski3d161b02008-06-06 15:43:36 +0000643
jadmanski0afbb632008-06-06 21:10:57 +0000644 # exited during check
645 self.set_running()
646 monitor_db.open.expect_call(
647 '/proc/%d/cmdline' % self.pid, 'r').and_raises(IOError)
648 self.set_complete(123) # pidfile gets read again
649 self._test_get_pidfile_info_helper(self.pid, 123)
jadmanski3d161b02008-06-06 15:43:36 +0000650
jadmanski0afbb632008-06-06 21:10:57 +0000651 # completed
652 self.set_complete(123)
653 self._test_get_pidfile_info_helper(self.pid, 123)
jadmanski3d161b02008-06-06 15:43:36 +0000654
655
jadmanski0afbb632008-06-06 21:10:57 +0000656 def test_get_pidfile_info_running_no_proc(self):
657 'pidfile shows process running, but no proc exists'
658 # running but no proc
659 self.set_running()
660 monitor_db.open.expect_call(
661 '/proc/%d/cmdline' % self.pid, 'r').and_raises(IOError)
662 self.set_running()
663 monitor_db.email_manager.enqueue_notify_email.expect_call(
664 mock.is_string_comparator(), mock.is_string_comparator())
665 self._test_get_pidfile_info_helper(self.pid, 1)
666 self.assertTrue(self.monitor.lost_process)
jadmanski3d161b02008-06-06 15:43:36 +0000667
668
jadmanski0afbb632008-06-06 21:10:57 +0000669 def test_get_pidfile_info_not_yet_run(self):
670 "pidfile hasn't been written yet"
671 # process not running
672 self.set_not_yet_run()
673 self.setup_find_autoservs({})
674 self._test_get_pidfile_info_helper(None, None)
jadmanski3d161b02008-06-06 15:43:36 +0000675
jadmanski0afbb632008-06-06 21:10:57 +0000676 # process running
677 self.set_not_yet_run()
678 self.setup_find_autoservs({self.pid : self.args})
679 self._test_get_pidfile_info_helper(None, None)
jadmanski3d161b02008-06-06 15:43:36 +0000680
jadmanski0afbb632008-06-06 21:10:57 +0000681 # another process running under same pid
682 self.set_not_yet_run()
683 self.setup_find_autoservs({self.pid : self.bad_args})
684 self._test_get_pidfile_info_helper(None, None)
jadmanski3d161b02008-06-06 15:43:36 +0000685
686
687class AgentTest(unittest.TestCase):
jadmanski0afbb632008-06-06 21:10:57 +0000688 def setUp(self):
689 self.god = mock.mock_god()
jadmanski3d161b02008-06-06 15:43:36 +0000690
691
jadmanski0afbb632008-06-06 21:10:57 +0000692 def tearDown(self):
693 self.god.unstub_all()
jadmanski3d161b02008-06-06 15:43:36 +0000694
695
jadmanski0afbb632008-06-06 21:10:57 +0000696 def test_agent(self):
697 task1 = self.god.create_mock_class(monitor_db.AgentTask,
698 'task1')
699 task2 = self.god.create_mock_class(monitor_db.AgentTask,
700 'task2')
701 task3 = self.god.create_mock_class(monitor_db.AgentTask,
702 'task3')
jadmanski3d161b02008-06-06 15:43:36 +0000703
jadmanski0afbb632008-06-06 21:10:57 +0000704 task1.start.expect_call()
705 task1.is_done.expect_call().and_return(False)
706 task1.poll.expect_call()
707 task1.is_done.expect_call().and_return(True)
708 task1.is_done.expect_call().and_return(True)
709 task1.success = True
jadmanski3d161b02008-06-06 15:43:36 +0000710
jadmanski0afbb632008-06-06 21:10:57 +0000711 task2.start.expect_call()
712 task2.is_done.expect_call().and_return(True)
713 task2.is_done.expect_call().and_return(True)
714 task2.success = False
715 task2.failure_tasks = [task3]
jadmanski3d161b02008-06-06 15:43:36 +0000716
jadmanski0afbb632008-06-06 21:10:57 +0000717 task3.start.expect_call()
718 task3.is_done.expect_call().and_return(True)
719 task3.is_done.expect_call().and_return(True)
720 task3.success = True
jadmanski3d161b02008-06-06 15:43:36 +0000721
jadmanski0afbb632008-06-06 21:10:57 +0000722 agent = monitor_db.Agent([task1, task2])
723 agent.dispatcher = object()
724 agent.start()
725 while not agent.is_done():
726 agent.tick()
727 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +0000728
729
730class AgentTasksTest(unittest.TestCase):
jadmanski0afbb632008-06-06 21:10:57 +0000731 TEMP_DIR = '/temp/dir'
732 HOSTNAME = 'myhost'
jadmanskifb7cfb12008-07-09 14:13:21 +0000733 HOST_PROTECTION = host_protections.default
jadmanski3d161b02008-06-06 15:43:36 +0000734
jadmanski0afbb632008-06-06 21:10:57 +0000735 def setUp(self):
736 self.god = mock.mock_god()
737 self.god.stub_with(tempfile, 'mkdtemp',
738 mock.mock_function('mkdtemp', self.TEMP_DIR))
739 self.god.stub_class_method(monitor_db.RunMonitor, 'run')
740 self.god.stub_class_method(monitor_db.RunMonitor, 'exit_code')
741 self.host = self.god.create_mock_class(monitor_db.Host, 'host')
742 self.host.hostname = self.HOSTNAME
jadmanskifb7cfb12008-07-09 14:13:21 +0000743 self.host.protection = self.HOST_PROTECTION
jadmanski0afbb632008-06-06 21:10:57 +0000744 self.queue_entry = self.god.create_mock_class(
745 monitor_db.HostQueueEntry, 'queue_entry')
746 self.queue_entry.host = self.host
747 self.queue_entry.meta_host = None
jadmanski3d161b02008-06-06 15:43:36 +0000748
749
jadmanski0afbb632008-06-06 21:10:57 +0000750 def tearDown(self):
751 self.god.unstub_all()
jadmanski3d161b02008-06-06 15:43:36 +0000752
753
jadmanski0afbb632008-06-06 21:10:57 +0000754 def run_task(self, task, success):
755 """
756 Do essentially what an Agent would do, but protect againt
757 infinite looping from test errors.
758 """
759 if not getattr(task, 'agent', None):
760 task.agent = object()
761 task.start()
762 count = 0
763 while not task.is_done():
764 count += 1
765 if count > 10:
766 print 'Task failed to finish'
767 # in case the playback has clues to why it
768 # failed
769 self.god.check_playback()
770 self.fail()
771 task.poll()
772 self.assertEquals(task.success, success)
jadmanski3d161b02008-06-06 15:43:36 +0000773
774
jadmanski0afbb632008-06-06 21:10:57 +0000775 def setup_run_monitor(self, exit_status):
776 monitor_db.RunMonitor.run.expect_call()
777 monitor_db.RunMonitor.exit_code.expect_call()
778 monitor_db.RunMonitor.exit_code.expect_call().and_return(
779 exit_status)
jadmanski3d161b02008-06-06 15:43:36 +0000780
781
jadmanski0afbb632008-06-06 21:10:57 +0000782 def _test_repair_task_helper(self, success):
783 self.host.set_status.expect_call('Repairing')
784 if success:
785 self.setup_run_monitor(0)
786 self.host.set_status.expect_call('Ready')
787 else:
788 self.setup_run_monitor(1)
789 self.host.set_status.expect_call('Repair Failed')
jadmanski3d161b02008-06-06 15:43:36 +0000790
jadmanski0afbb632008-06-06 21:10:57 +0000791 task = monitor_db.RepairTask(self.host)
showard56193bb2008-08-13 20:07:41 +0000792 self.assertEquals(task.failure_tasks, [])
jadmanski0afbb632008-06-06 21:10:57 +0000793 self.run_task(task, success)
jadmanskifb7cfb12008-07-09 14:13:21 +0000794
795 expected_protection = host_protections.Protection.get_string(
796 host_protections.default)
mbligh3e0f7e02008-07-28 19:42:01 +0000797 expected_protection = host_protections.Protection.get_attr_name(
798 expected_protection)
799
mblighc1603522008-07-17 21:32:21 +0000800 self.assertTrue(set(task.monitor.cmd) >=
mblighf40cf532008-06-23 23:53:23 +0000801 set(['autoserv', '-R', '-m', self.HOSTNAME, '-r',
jadmanskifb7cfb12008-07-09 14:13:21 +0000802 self.TEMP_DIR, '--host-protection',
803 expected_protection]))
jadmanski0afbb632008-06-06 21:10:57 +0000804 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +0000805
806
jadmanski0afbb632008-06-06 21:10:57 +0000807 def test_repair_task(self):
808 self._test_repair_task_helper(True)
809 self._test_repair_task_helper(False)
jadmanski3d161b02008-06-06 15:43:36 +0000810
811
jadmanski0afbb632008-06-06 21:10:57 +0000812 def test_repair_task_with_queue_entry(self):
813 queue_entry = self.god.create_mock_class(
814 monitor_db.HostQueueEntry, 'queue_entry')
815 self.host.set_status.expect_call('Repairing')
816 self.setup_run_monitor(1)
817 self.host.set_status.expect_call('Repair Failed')
818 queue_entry.handle_host_failure.expect_call()
jadmanski3d161b02008-06-06 15:43:36 +0000819
jadmanski0afbb632008-06-06 21:10:57 +0000820 task = monitor_db.RepairTask(self.host, queue_entry)
821 self.run_task(task, False)
822 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +0000823
824
jadmanski0afbb632008-06-06 21:10:57 +0000825 def setup_verify_expects(self, success, use_queue_entry):
826 if use_queue_entry:
827 self.queue_entry.set_status.expect_call('Verifying')
828 self.queue_entry.verify_results_dir.expect_call(
829 ).and_return('/verify/results/dir')
830 self.queue_entry.clear_results_dir.expect_call(
831 '/verify/results/dir')
832 self.host.set_status.expect_call('Verifying')
833 if success:
834 self.setup_run_monitor(0)
835 self.host.set_status.expect_call('Ready')
836 else:
837 self.setup_run_monitor(1)
838 if use_queue_entry:
839 self.queue_entry.requeue.expect_call()
jadmanski3d161b02008-06-06 15:43:36 +0000840
841
showard56193bb2008-08-13 20:07:41 +0000842 def _check_verify_failure_tasks(self, verify_task):
843 self.assertEquals(len(verify_task.failure_tasks), 1)
844 repair_task = verify_task.failure_tasks[0]
845 self.assert_(isinstance(repair_task, monitor_db.RepairTask))
846 self.assertEquals(verify_task.host, repair_task.host)
847 if verify_task.queue_entry and not verify_task.queue_entry.meta_host:
848 self.assertEquals(repair_task.fail_queue_entry,
849 verify_task.queue_entry)
850 else:
851 self.assertEquals(repair_task.fail_queue_entry, None)
852
853
854 def _test_verify_task_helper(self, success, use_queue_entry=False,
855 use_meta_host=False):
jadmanski0afbb632008-06-06 21:10:57 +0000856 self.setup_verify_expects(success, use_queue_entry)
jadmanski3d161b02008-06-06 15:43:36 +0000857
jadmanski0afbb632008-06-06 21:10:57 +0000858 if use_queue_entry:
859 task = monitor_db.VerifyTask(
860 queue_entry=self.queue_entry)
861 else:
862 task = monitor_db.VerifyTask(host=self.host)
showard56193bb2008-08-13 20:07:41 +0000863 self._check_verify_failure_tasks(task)
jadmanski0afbb632008-06-06 21:10:57 +0000864 self.run_task(task, success)
mblighc1603522008-07-17 21:32:21 +0000865 self.assertTrue(set(task.monitor.cmd) >=
mblighf40cf532008-06-23 23:53:23 +0000866 set(['autoserv', '-v', '-m', self.HOSTNAME, '-r',
867 self.TEMP_DIR]))
jadmanski0afbb632008-06-06 21:10:57 +0000868 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +0000869
870
jadmanski0afbb632008-06-06 21:10:57 +0000871 def test_verify_task_with_host(self):
showard56193bb2008-08-13 20:07:41 +0000872 self._test_verify_task_helper(True)
873 self._test_verify_task_helper(False)
jadmanski3d161b02008-06-06 15:43:36 +0000874
875
jadmanski0afbb632008-06-06 21:10:57 +0000876 def test_verify_task_with_queue_entry(self):
showard56193bb2008-08-13 20:07:41 +0000877 self._test_verify_task_helper(True, use_queue_entry=True)
878 self._test_verify_task_helper(False, use_queue_entry=True)
879
880
881 def test_verify_task_with_metahost(self):
882 self._test_verify_task_helper(True, use_queue_entry=True,
883 use_meta_host=True)
884 self._test_verify_task_helper(False, use_queue_entry=True,
885 use_meta_host=True)
jadmanski3d161b02008-06-06 15:43:36 +0000886
887
jadmanski0afbb632008-06-06 21:10:57 +0000888 def test_verify_synchronous_task(self):
889 job = self.god.create_mock_class(monitor_db.Job, 'job')
jadmanski3d161b02008-06-06 15:43:36 +0000890
jadmanski0afbb632008-06-06 21:10:57 +0000891 self.setup_verify_expects(True, True)
892 job.num_complete.expect_call().and_return(0)
893 self.queue_entry.set_status.expect_call('Pending')
894 job.is_ready.expect_call().and_return(True)
895 job.run.expect_call(self.queue_entry)
896 self.queue_entry.job = job
jadmanski3d161b02008-06-06 15:43:36 +0000897
jadmanski0afbb632008-06-06 21:10:57 +0000898 task = monitor_db.VerifySynchronousTask(self.queue_entry)
899 task.agent = Dummy()
900 task.agent.dispatcher = Dummy()
901 self.god.stub_with(task.agent.dispatcher, 'add_agent',
902 mock.mock_function('add_agent'))
903 self.run_task(task, True)
904 self.god.check_playback()
jadmanski3d161b02008-06-06 15:43:36 +0000905
906
showardce38e0c2008-05-29 19:36:16 +0000907if __name__ == '__main__':
jadmanski0afbb632008-06-06 21:10:57 +0000908 unittest.main()