blob: 26fef0ad7e5e812166a984a8a028acd781812d2a [file] [log] [blame]
Prashanth B4ec98672014-05-15 10:44:54 -07001#!/usr/bin/python
2#pylint: disable-msg=C0111
3
4# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8"""Host scheduler.
9
10If run as a standalone service, the host scheduler ensures the following:
11 1. Hosts will not be assigned to multiple hqes simultaneously. The process
12 of assignment in this case refers to the modification of the host_id
13 column of a row in the afe_host_queue_entries table, to reflect the host
14 id of a leased host matching the dependencies of the job.
15 2. Hosts that are not being used by active hqes or incomplete special tasks
16 will be released back to the available hosts pool, for acquisition by
17 subsequent hqes.
18In addition to these guarantees, the host scheduler also confirms that no 2
19active hqes/special tasks are assigned the same host, and sets the leased bit
20for hosts needed by frontend special tasks. The need for the latter is only
21apparent when viewed in the context of the job-scheduler (monitor_db), which
22runs special tasks only after their hosts have been leased.
Fang Deng522bc532014-11-20 17:48:34 -080023
24** Suport minimum duts requirement for suites (non-inline mode) **
25
26Each suite can specify the minimum number of duts it requires by
27dropping a 'suite_min_duts' job keyval which defaults to 0.
28
29When suites are competing for duts, if any suite has not got minimum duts
30it requires, the host scheduler will try to meet the requirement first,
31even if other suite may have higher priority or earlier timestamp. Once
32all suites' minimum duts requirement have been fullfilled, the host
33scheduler will allocate the rest of duts based on job priority and suite job id.
34This is to prevent low priority suites from starving when sharing pool with
35high-priority suites.
36
37Note:
38 1. Prevent potential starvation:
39 We need to carefully choose |suite_min_duts| for both low and high
40 priority suites. If a high priority suite didn't specify it but a low
41 priority one does, the high priority suite can be starved!
42 2. Restart requirement:
43 Restart host scheduler if you manually released a host by setting
44 leased=0 in db. This is needed because host scheduler maintains internal
45 state of host assignment for suites.
46 3. Exchanging duts triggers provisioning:
47 TODO(fdeng): There is a chance two suites can exchange duts,
48 if the two suites are for different builds, the exchange
49 will trigger provisioning. This can be optimized by preferring getting
50 hosts with the same build.
Dale Curtisaa513362011-03-01 17:27:44 -080051"""
52
Prashanth B4ec98672014-05-15 10:44:54 -070053import argparse
Prashanth Bf66d51b2014-05-06 12:42:25 -070054import collections
Fang Dengc9b1be82014-10-20 17:54:20 -070055import datetime
Dale Curtisaa513362011-03-01 17:27:44 -080056import logging
Prashanth B4ec98672014-05-15 10:44:54 -070057import os
58import signal
59import sys
60import time
Dale Curtisaa513362011-03-01 17:27:44 -080061
Prashanth B4ec98672014-05-15 10:44:54 -070062import common
63from autotest_lib.frontend import setup_django_environment
64
Aviv Keshet71088e32016-06-29 14:10:56 -070065from chromite.lib import metrics
66from chromite.lib import ts_mon_config
67
Prashanth B4ec98672014-05-15 10:44:54 -070068from autotest_lib.client.common_lib import global_config
Gabe Black1e1c41b2015-02-04 23:55:15 -080069from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Prashanth B4ec98672014-05-15 10:44:54 -070070from autotest_lib.scheduler import email_manager
Prashanth Bf66d51b2014-05-06 12:42:25 -070071from autotest_lib.scheduler import query_managers
72from autotest_lib.scheduler import rdb_lib
73from autotest_lib.scheduler import rdb_utils
Prashanth B4ec98672014-05-15 10:44:54 -070074from autotest_lib.scheduler import scheduler_lib
75from autotest_lib.scheduler import scheduler_models
Fang Deng042c1472014-10-23 13:56:41 -070076from autotest_lib.site_utils import job_overhead
Dan Shicf2e8dd2015-05-07 17:18:48 -070077from autotest_lib.site_utils import metadata_reporter
Dan Shib9144a42014-12-01 16:09:32 -080078from autotest_lib.site_utils import server_manager_utils
Dale Curtisaa513362011-03-01 17:27:44 -080079
Prashanth B4ec98672014-05-15 10:44:54 -070080_db_manager = None
81_shutdown = False
82_tick_pause_sec = global_config.global_config.get_config_value(
83 'SCHEDULER', 'tick_pause_sec', type=int, default=5)
84_monitor_db_host_acquisition = global_config.global_config.get_config_value(
Prashanth Bee0dca22014-05-28 12:16:57 -070085 'SCHEDULER', 'inline_host_acquisition', type=bool, default=True)
Prashanth B4ec98672014-05-15 10:44:54 -070086
Dale Curtisaa513362011-03-01 17:27:44 -080087
Fang Deng522bc532014-11-20 17:48:34 -080088class SuiteRecorder(object):
89 """Recording the host assignment for suites.
90
91 The recorder holds two things:
92 * suite_host_num, records how many duts a suite is holding,
93 which is a map <suite_job_id -> num_of_hosts>
94 * hosts_to_suites, records which host is assigned to which
95 suite, it is a map <host_id -> suite_job_id>
96 The two datastructure got updated when a host is assigned to or released
97 by a job.
98
99 The reason to maintain hosts_to_suites is that, when a host is released,
100 we need to know which suite it was leased to. Querying the db for the
101 latest completed job that has run on a host is slow. Therefore, we go with
102 an alternative: keeping a <host id, suite job id> map
103 in memory (for 10K hosts, the map should take less than 1M memory on
104 64-bit machine with python 2.7)
105
106 """
107
108
Gabe Black1e1c41b2015-02-04 23:55:15 -0800109 _timer = autotest_stats.Timer('suite_recorder')
Fang Deng522bc532014-11-20 17:48:34 -0800110
111
112 def __init__(self, job_query_manager):
113 """Initialize.
114
115 @param job_queue_manager: A JobQueueryManager object.
116 """
117 self.job_query_manager = job_query_manager
118 self.suite_host_num, self.hosts_to_suites = (
119 self.job_query_manager.get_suite_host_assignment())
120
121
122 def record_assignment(self, queue_entry):
123 """Record that the hqe has got a host.
124
125 @param queue_entry: A scheduler_models.HostQueueEntry object which has
126 got a host.
127 """
128 parent_id = queue_entry.job.parent_job_id
129 if not parent_id:
130 return
131 if self.hosts_to_suites.get(queue_entry.host_id, None) == parent_id:
132 logging.error('HQE (id: %d, parent_job_id: %d, host: %s) '
133 'seems already recorded', queue_entry.id,
134 parent_id, queue_entry.host.hostname)
135 return
136 num_hosts = self.suite_host_num.get(parent_id, 0)
137 self.suite_host_num[parent_id] = num_hosts + 1
138 self.hosts_to_suites[queue_entry.host_id] = parent_id
139 logging.debug('Suite %d got host %s, currently holding %d hosts',
140 parent_id, queue_entry.host.hostname,
141 self.suite_host_num[parent_id])
142
143
144 def record_release(self, hosts):
145 """Update the record with host releasing event.
146
147 @param hosts: A list of scheduler_models.Host objects.
148 """
149 for host in hosts:
150 if host.id in self.hosts_to_suites:
151 parent_job_id = self.hosts_to_suites.pop(host.id)
152 count = self.suite_host_num[parent_job_id] - 1
153 if count == 0:
154 del self.suite_host_num[parent_job_id]
155 else:
156 self.suite_host_num[parent_job_id] = count
157 logging.debug(
158 'Suite %d releases host %s, currently holding %d hosts',
159 parent_job_id, host.hostname, count)
160
161
162 def get_min_duts(self, suite_job_ids):
163 """Figure out min duts to request.
164
165 Given a set ids of suite jobs, figure out minimum duts to request for
166 each suite. It is determined by two factors: min_duts specified
167 for each suite in its job keyvals, and how many duts a suite is
168 currently holding.
169
170 @param suite_job_ids: A set of suite job ids.
171
172 @returns: A dictionary, the key is suite_job_id, the value
173 is the minimum number of duts to request.
174 """
175 suite_min_duts = self.job_query_manager.get_min_duts_of_suites(
176 suite_job_ids)
177 for parent_id in suite_job_ids:
178 min_duts = suite_min_duts.get(parent_id, 0)
179 cur_duts = self.suite_host_num.get(parent_id, 0)
180 suite_min_duts[parent_id] = max(0, min_duts - cur_duts)
181 logging.debug('Minimum duts to get for suites (suite_id: min_duts): %s',
182 suite_min_duts)
183 return suite_min_duts
184
185
Prashanth Bf66d51b2014-05-06 12:42:25 -0700186class BaseHostScheduler(object):
187 """Base class containing host acquisition logic.
Dale Curtisaa513362011-03-01 17:27:44 -0800188
Prashanth Bf66d51b2014-05-06 12:42:25 -0700189 This class contains all the core host acquisition logic needed by the
190 scheduler to run jobs on hosts. It is only capable of releasing hosts
191 back to the rdb through its tick, any other action must be instigated by
192 the job scheduler.
Dale Curtisaa513362011-03-01 17:27:44 -0800193 """
Fang Deng1d6c2a02013-04-17 15:25:45 -0700194
195
Gabe Black1e1c41b2015-02-04 23:55:15 -0800196 _timer = autotest_stats.Timer('base_host_scheduler')
Prashanth Bf66d51b2014-05-06 12:42:25 -0700197 host_assignment = collections.namedtuple('host_assignment', ['host', 'job'])
Fang Deng1d6c2a02013-04-17 15:25:45 -0700198
199
Prashanth Bf66d51b2014-05-06 12:42:25 -0700200 def __init__(self):
201 self.host_query_manager = query_managers.AFEHostQueryManager()
Dale Curtisaa513362011-03-01 17:27:44 -0800202
203
Fang Deng1d6c2a02013-04-17 15:25:45 -0700204 @_timer.decorate
beepscc9fc702013-12-02 12:45:38 -0800205 def _release_hosts(self):
206 """Release hosts to the RDB.
207
208 Release all hosts that are ready and are currently not being used by an
209 active hqe, and don't have a new special task scheduled against them.
Fang Deng522bc532014-11-20 17:48:34 -0800210
211 @return a list of hosts that are released.
beepscc9fc702013-12-02 12:45:38 -0800212 """
Fang Deng522bc532014-11-20 17:48:34 -0800213 release_hosts = self.host_query_manager.find_unused_healty_hosts()
214 release_hostnames = [host.hostname for host in release_hosts]
Prashanth Bf66d51b2014-05-06 12:42:25 -0700215 if release_hostnames:
216 self.host_query_manager.set_leased(
217 False, hostname__in=release_hostnames)
Fang Deng522bc532014-11-20 17:48:34 -0800218 return release_hosts
beepscc9fc702013-12-02 12:45:38 -0800219
220
Prashanth Bf66d51b2014-05-06 12:42:25 -0700221 @classmethod
222 def schedule_host_job(cls, host, queue_entry):
223 """Schedule a job on a host.
Dale Curtisaa513362011-03-01 17:27:44 -0800224
Prashanth Bf66d51b2014-05-06 12:42:25 -0700225 Scheduling a job involves:
226 1. Setting the active bit on the queue_entry.
227 2. Scheduling a special task on behalf of the queue_entry.
228 Performing these actions will lead the job scheduler through a chain of
229 events, culminating in running the test and collecting results from
230 the host.
Dale Curtisaa513362011-03-01 17:27:44 -0800231
Prashanth Bf66d51b2014-05-06 12:42:25 -0700232 @param host: The host against which to schedule the job.
233 @param queue_entry: The queue_entry to schedule.
Dale Curtisaa513362011-03-01 17:27:44 -0800234 """
Prashanth Bf66d51b2014-05-06 12:42:25 -0700235 if queue_entry.host_id is None:
236 queue_entry.set_host(host)
237 elif host.id != queue_entry.host_id:
238 raise rdb_utils.RDBException('The rdb returned host: %s '
239 'but the job:%s was already assigned a host: %s. ' %
240 (host.hostname, queue_entry.job_id,
241 queue_entry.host.hostname))
242 queue_entry.update_field('active', True)
243
244 # TODO: crbug.com/373936. The host scheduler should only be assigning
245 # jobs to hosts, but the criterion we use to release hosts depends
246 # on it not being used by an active hqe. Since we're activating the
247 # hqe here, we also need to schedule its first prejob task. OTOH,
248 # we could converge to having the host scheduler manager all special
249 # tasks, since their only use today is to verify/cleanup/reset a host.
250 logging.info('Scheduling pre job tasks for entry: %s', queue_entry)
251 queue_entry.schedule_pre_job_tasks()
Dale Curtisaa513362011-03-01 17:27:44 -0800252
253
Fang Deng522bc532014-11-20 17:48:34 -0800254 def acquire_hosts(self, host_jobs):
255 """Accquire hosts for given jobs.
256
257 This method sends jobs that need hosts to rdb.
258 Child class can override this method to pipe more args
259 to rdb.
260
261 @param host_jobs: A list of queue entries that either require hosts,
262 or require host assignment validation through the rdb.
263
264 @param return: A generator that yields an rdb_hosts.RDBClientHostWrapper
265 for each host acquired on behalf of a queue_entry,
266 or None if a host wasn't found.
267 """
268 return rdb_lib.acquire_hosts(host_jobs)
269
270
271 def find_hosts_for_jobs(self, host_jobs):
Prashanth Bf66d51b2014-05-06 12:42:25 -0700272 """Find and verify hosts for a list of jobs.
273
274 @param host_jobs: A list of queue entries that either require hosts,
275 or require host assignment validation through the rdb.
276 @return: A list of tuples of the form (host, queue_entry) for each
277 valid host-queue_entry assignment.
Dale Curtisaa513362011-03-01 17:27:44 -0800278 """
Prashanth Bf66d51b2014-05-06 12:42:25 -0700279 jobs_with_hosts = []
Fang Deng522bc532014-11-20 17:48:34 -0800280 hosts = self.acquire_hosts(host_jobs)
Prashanth Bf66d51b2014-05-06 12:42:25 -0700281 for host, job in zip(hosts, host_jobs):
282 if host:
Fang Deng522bc532014-11-20 17:48:34 -0800283 jobs_with_hosts.append(self.host_assignment(host, job))
Prashanth Bf66d51b2014-05-06 12:42:25 -0700284 return jobs_with_hosts
Dale Curtisaa513362011-03-01 17:27:44 -0800285
286
Fang Deng1d6c2a02013-04-17 15:25:45 -0700287 @_timer.decorate
Dale Curtisaa513362011-03-01 17:27:44 -0800288 def tick(self):
Prashanth Bf66d51b2014-05-06 12:42:25 -0700289 """Schedule core host management activities."""
beepscc9fc702013-12-02 12:45:38 -0800290 self._release_hosts()
Dale Curtisaa513362011-03-01 17:27:44 -0800291
292
Prashanth B4ec98672014-05-15 10:44:54 -0700293class HostScheduler(BaseHostScheduler):
294 """A scheduler capable managing host acquisition for new jobs."""
295
Gabe Black1e1c41b2015-02-04 23:55:15 -0800296 _timer = autotest_stats.Timer('host_scheduler')
Prashanth B4ec98672014-05-15 10:44:54 -0700297
298
299 def __init__(self):
300 super(HostScheduler, self).__init__()
301 self.job_query_manager = query_managers.AFEJobQueryManager()
Fang Deng522bc532014-11-20 17:48:34 -0800302 # Keeping track on how many hosts each suite is holding
303 # {suite_job_id: num_hosts}
304 self._suite_recorder = SuiteRecorder(self.job_query_manager)
Prashanth B4ec98672014-05-15 10:44:54 -0700305
306
Fang Deng522bc532014-11-20 17:48:34 -0800307 def _record_host_assignment(self, host, queue_entry):
308 """Record that |host| is assigned to |queue_entry|.
309
310 Record:
311 1. How long it takes to assign a host to a job in metadata db.
312 2. Record host assignment of a suite.
Fang Dengc9b1be82014-10-20 17:54:20 -0700313
314 @param host: A Host object.
315 @param queue_entry: A HostQueueEntry object.
316 """
317 secs_in_queued = (datetime.datetime.now() -
318 queue_entry.job.created_on).total_seconds()
Fang Deng042c1472014-10-23 13:56:41 -0700319 job_overhead.record_state_duration(
320 queue_entry.job_id, host.hostname,
321 job_overhead.STATUS.QUEUED, secs_in_queued)
Fang Deng522bc532014-11-20 17:48:34 -0800322 self._suite_recorder.record_assignment(queue_entry)
Fang Dengc9b1be82014-10-20 17:54:20 -0700323
324
Prashanth B4ec98672014-05-15 10:44:54 -0700325 @_timer.decorate
326 def _schedule_jobs(self):
327 """Schedule new jobs against hosts."""
Prashanth Balasubramanian628bfcf2014-10-02 12:44:13 -0700328
329 key = 'host_scheduler.jobs_per_tick'
330 new_jobs_with_hosts = 0
Prashanth B4ec98672014-05-15 10:44:54 -0700331 queue_entries = self.job_query_manager.get_pending_queue_entries(
332 only_hostless=False)
333 unverified_host_jobs = [job for job in queue_entries
334 if not job.is_hostless()]
335 if not unverified_host_jobs:
336 return
337 for acquisition in self.find_hosts_for_jobs(unverified_host_jobs):
338 self.schedule_host_job(acquisition.host, acquisition.job)
Fang Dengc9b1be82014-10-20 17:54:20 -0700339 self._record_host_assignment(acquisition.host, acquisition.job)
Prashanth Balasubramanian628bfcf2014-10-02 12:44:13 -0700340 new_jobs_with_hosts += 1
Gabe Black1e1c41b2015-02-04 23:55:15 -0800341 autotest_stats.Gauge(key).send('new_jobs_with_hosts',
342 new_jobs_with_hosts)
343 autotest_stats.Gauge(key).send('new_jobs_without_hosts',
344 len(unverified_host_jobs) -
345 new_jobs_with_hosts)
Aviv Keshet71088e32016-06-29 14:10:56 -0700346 metrics.Counter('chromeos/autotest/host_scheduler/tick').increment()
Prashanth B4ec98672014-05-15 10:44:54 -0700347
348
349 @_timer.decorate
350 def _lease_hosts_of_frontend_tasks(self):
351 """Lease hosts of tasks scheduled through the frontend."""
352 # We really don't need to get all the special tasks here, just the ones
353 # without hqes, but reusing the method used by the scheduler ensures
354 # we prioritize the same way.
355 lease_hostnames = [
356 task.host.hostname for task in
357 self.job_query_manager.get_prioritized_special_tasks(
358 only_tasks_with_leased_hosts=False)
359 if task.queue_entry_id is None and not task.host.leased]
360 # Leasing a leased hosts here shouldn't be a problem:
361 # 1. The only way a host can be leased is if it's been assigned to
362 # an active hqe or another similar frontend task, but doing so will
363 # have already precluded it from the list of tasks returned by the
364 # job_query_manager.
365 # 2. The unleasing is done based on global conditions. Eg: Even if a
366 # task has already leased a host and we lease it again, the
367 # host scheduler won't release the host till both tasks are complete.
368 if lease_hostnames:
369 self.host_query_manager.set_leased(
370 True, hostname__in=lease_hostnames)
371
372
Fang Deng522bc532014-11-20 17:48:34 -0800373 def acquire_hosts(self, host_jobs):
374 """Override acquire_hosts.
375
376 This method overrides the method in parent class.
377 It figures out a set of suites that |host_jobs| belong to;
378 and get min_duts requirement for each suite.
379 It pipes min_duts for each suite to rdb.
380
381 """
382 parent_job_ids = set([q.job.parent_job_id
383 for q in host_jobs if q.job.parent_job_id])
384 suite_min_duts = self._suite_recorder.get_min_duts(parent_job_ids)
385 return rdb_lib.acquire_hosts(host_jobs, suite_min_duts)
386
387
Prashanth B4ec98672014-05-15 10:44:54 -0700388 @_timer.decorate
389 def tick(self):
390 logging.info('Calling new tick.')
391 logging.info('Leasing hosts for frontend tasks.')
392 self._lease_hosts_of_frontend_tasks()
393 logging.info('Finding hosts for new jobs.')
394 self._schedule_jobs()
395 logging.info('Releasing unused hosts.')
Fang Deng522bc532014-11-20 17:48:34 -0800396 released_hosts = self._release_hosts()
397 logging.info('Updating suite assignment with released hosts')
398 self._suite_recorder.record_release(released_hosts)
Prashanth B4ec98672014-05-15 10:44:54 -0700399 logging.info('Calling email_manager.')
400 email_manager.manager.send_queued_emails()
401
402
Prashanth Bf66d51b2014-05-06 12:42:25 -0700403class DummyHostScheduler(BaseHostScheduler):
404 """A dummy host scheduler that doesn't acquire or release hosts."""
405
406 def __init__(self):
407 pass
Dale Curtisaa513362011-03-01 17:27:44 -0800408
409
Prashanth Bf66d51b2014-05-06 12:42:25 -0700410 def tick(self):
411 pass
Prashanth B4ec98672014-05-15 10:44:54 -0700412
413
414def handle_signal(signum, frame):
415 """Sigint handler so we don't crash mid-tick."""
416 global _shutdown
417 _shutdown = True
418 logging.info("Shutdown request received.")
419
420
421def initialize(testing=False):
422 """Initialize the host scheduler."""
423 if testing:
424 # Don't import testing utilities unless we're in testing mode,
425 # as the database imports have side effects.
426 from autotest_lib.scheduler import rdb_testing_utils
427 rdb_testing_utils.FileDatabaseHelper().initialize_database_for_testing(
428 db_file_path=rdb_testing_utils.FileDatabaseHelper.DB_FILE)
429 global _db_manager
430 _db_manager = scheduler_lib.ConnectionManager()
431 scheduler_lib.setup_logging(
432 os.environ.get('AUTOTEST_SCHEDULER_LOG_DIR', None),
433 None, timestamped_logfile_prefix='host_scheduler')
434 logging.info("Setting signal handler")
435 signal.signal(signal.SIGINT, handle_signal)
436 signal.signal(signal.SIGTERM, handle_signal)
437 scheduler_models.initialize()
438
439
440def parse_arguments(argv):
441 """
442 Parse command line arguments
443
444 @param argv: argument list to parse
445 @returns: parsed arguments.
446 """
447 parser = argparse.ArgumentParser(description='Host scheduler.')
448 parser.add_argument('--testing', action='store_true', default=False,
449 help='Start the host scheduler in testing mode.')
Dan Shif6c65bd2014-08-29 16:15:07 -0700450 parser.add_argument('--production',
451 help=('Indicate that scheduler is running in production'
452 ' environment and it can use database that is not'
453 ' hosted in localhost. If it is set to False, '
454 'scheduler will fail if database is not in '
455 'localhost.'),
Dan Shi06b09b72014-09-09 16:06:17 -0700456 action='store_true', default=False)
Dan Shif6c65bd2014-08-29 16:15:07 -0700457 options = parser.parse_args(argv)
458
459 return options
Prashanth B4ec98672014-05-15 10:44:54 -0700460
461
462def main():
463 if _monitor_db_host_acquisition:
464 logging.info('Please set inline_host_acquisition=False in the shadow '
465 'config before starting the host scheduler.')
466 # The upstart job for the host scheduler understands exit(0) to mean
467 # 'don't respawn'. This is desirable when the job scheduler is acquiring
468 # hosts inline.
469 sys.exit(0)
470 try:
Dan Shif6c65bd2014-08-29 16:15:07 -0700471 options = parse_arguments(sys.argv[1:])
472 scheduler_lib.check_production_settings(options)
473
Dan Shib9144a42014-12-01 16:09:32 -0800474 # If server database is enabled, check if the server has role
475 # `host_scheduler`. If the server does not have host_scheduler role,
476 # exception will be raised and host scheduler will not continue to run.
477 if server_manager_utils.use_server_db():
478 server_manager_utils.confirm_server_has_role(hostname='localhost',
479 role='host_scheduler')
480
Dan Shif6c65bd2014-08-29 16:15:07 -0700481 initialize(options.testing)
482
Dan Shicf2e8dd2015-05-07 17:18:48 -0700483 # Start the thread to report metadata.
484 metadata_reporter.start()
485
Aviv Keshet71088e32016-06-29 14:10:56 -0700486 ts_mon_config.SetupTsMonGlobalState('autotest_host_scheduler')
487
Prashanth B4ec98672014-05-15 10:44:54 -0700488 host_scheduler = HostScheduler()
Shuqian Zhaobf6662c2015-03-25 16:36:15 -0700489 minimum_tick_sec = global_config.global_config.get_config_value(
490 'SCHEDULER', 'minimum_tick_sec', type=float)
Prashanth B4ec98672014-05-15 10:44:54 -0700491 while not _shutdown:
Shuqian Zhaobf6662c2015-03-25 16:36:15 -0700492 start = time.time()
Prashanth B4ec98672014-05-15 10:44:54 -0700493 host_scheduler.tick()
Shuqian Zhaobf6662c2015-03-25 16:36:15 -0700494 curr_tick_sec = time.time() - start
495 if (minimum_tick_sec > curr_tick_sec):
496 time.sleep(minimum_tick_sec - curr_tick_sec)
497 else:
498 time.sleep(0.0001)
Fang Denged0c4b52016-03-02 17:45:23 -0800499 except server_manager_utils.ServerActionError as e:
500 # This error is expected when the server is not in primary status
501 # for host-scheduler role. Thus do not send email for it.
502 raise
Prashanth B4ec98672014-05-15 10:44:54 -0700503 except Exception:
504 email_manager.manager.log_stacktrace(
505 'Uncaught exception; terminating host_scheduler.')
Prashanth B3f31d5b2014-06-20 12:13:57 -0700506 raise
507 finally:
508 email_manager.manager.send_queued_emails()
Dan Shif6c65bd2014-08-29 16:15:07 -0700509 if _db_manager:
510 _db_manager.disconnect()
Dan Shicf2e8dd2015-05-07 17:18:48 -0700511 metadata_reporter.abort()
Prashanth B4ec98672014-05-15 10:44:54 -0700512
513
514if __name__ == '__main__':
515 main()