182 lines
6.4 KiB
Python
182 lines
6.4 KiB
Python
|
|
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Scheduler helper libraries.
|
|
"""
|
|
import logging
|
|
import os
|
|
|
|
import common
|
|
|
|
from autotest_lib.client.common_lib import global_config
|
|
from autotest_lib.client.common_lib import logging_config
|
|
from autotest_lib.client.common_lib import logging_manager
|
|
from autotest_lib.client.common_lib import utils
|
|
from autotest_lib.database import database_connection
|
|
from autotest_lib.frontend import setup_django_environment
|
|
from autotest_lib.frontend.afe import readonly_connection
|
|
from autotest_lib.server import utils as server_utils
|
|
|
|
|
|
DB_CONFIG_SECTION = 'AUTOTEST_WEB'
|
|
|
|
# Translations necessary for scheduler queries to work with SQLite.
|
|
# Though this is only used for testing it is included in this module to avoid
|
|
# circular imports.
|
|
_re_translator = database_connection.TranslatingDatabase.make_regexp_translator
|
|
_DB_TRANSLATORS = (
|
|
_re_translator(r'NOW\(\)', 'time("now")'),
|
|
_re_translator(r'LAST_INSERT_ID\(\)', 'LAST_INSERT_ROWID()'),
|
|
# older SQLite doesn't support group_concat, so just don't bother until
|
|
# it arises in an important query
|
|
_re_translator(r'GROUP_CONCAT\((.*?)\)', r'\1'),
|
|
_re_translator(r'TRUNCATE TABLE', 'DELETE FROM'),
|
|
_re_translator(r'ISNULL\(([a-z,_]+)\)',
|
|
r'ifnull(nullif(\1, NULL), \1) DESC'),
|
|
)
|
|
|
|
|
|
class SchedulerError(Exception):
|
|
"""General parent class for exceptions raised by scheduler code."""
|
|
|
|
|
|
class MalformedRecordError(SchedulerError):
|
|
"""Exception raised when an individual job or record is malformed.
|
|
|
|
Code that handles individual records (e.g. afe jobs, hqe entries, special
|
|
tasks) should treat such an exception as a signal to skip or permanently
|
|
discard this record."""
|
|
|
|
|
|
class NoHostIdError(MalformedRecordError):
|
|
"""Raised by the scheduler when a non-hostless job's host is None."""
|
|
|
|
|
|
class ConnectionManager(object):
|
|
"""Manager for the django database connections.
|
|
|
|
The connection is used through scheduler_models and monitor_db.
|
|
"""
|
|
__metaclass__ = server_utils.Singleton
|
|
|
|
def __init__(self, readonly=True, autocommit=True):
|
|
"""Set global django database options for correct connection handling.
|
|
|
|
@param readonly: Globally disable readonly connections.
|
|
@param autocommit: Initialize django autocommit options.
|
|
"""
|
|
self.db_connection = None
|
|
# bypass the readonly connection
|
|
readonly_connection.set_globally_disabled(readonly)
|
|
if autocommit:
|
|
# ensure Django connection is in autocommit
|
|
setup_django_environment.enable_autocommit()
|
|
|
|
|
|
@classmethod
|
|
def open_connection(cls):
|
|
"""Open a new database connection.
|
|
|
|
@return: An instance of the newly opened connection.
|
|
"""
|
|
db = database_connection.DatabaseConnection(DB_CONFIG_SECTION)
|
|
db.connect(db_type='django')
|
|
return db
|
|
|
|
|
|
def get_connection(self):
|
|
"""Get a connection.
|
|
|
|
@return: A database connection.
|
|
"""
|
|
if self.db_connection is None:
|
|
self.db_connection = self.open_connection()
|
|
return self.db_connection
|
|
|
|
|
|
def disconnect(self):
|
|
"""Close the database connection."""
|
|
try:
|
|
self.db_connection.disconnect()
|
|
except Exception as e:
|
|
logging.debug('Could not close the db connection. %s', e)
|
|
|
|
|
|
def __del__(self):
|
|
self.disconnect()
|
|
|
|
|
|
class SchedulerLoggingConfig(logging_config.LoggingConfig):
|
|
"""Configure timestamped logging for a scheduler."""
|
|
GLOBAL_LEVEL = logging.INFO
|
|
|
|
@classmethod
|
|
def get_log_name(cls, timestamped_logfile_prefix):
|
|
"""Get the name of a logfile.
|
|
|
|
@param timestamped_logfile_prefix: The prefix to apply to the
|
|
a timestamped log. Eg: 'scheduler' will create a logfile named
|
|
scheduler.log.2014-05-12-17.24.02.
|
|
|
|
@return: The timestamped log name.
|
|
"""
|
|
return cls.get_timestamped_log_name(timestamped_logfile_prefix)
|
|
|
|
|
|
def configure_logging(self, log_dir=None, logfile_name=None,
|
|
timestamped_logfile_prefix='scheduler'):
|
|
"""Configure logging to a specified logfile.
|
|
|
|
@param log_dir: The directory to log into.
|
|
@param logfile_name: The name of the log file.
|
|
@timestamped_logfile_prefix: The prefix to apply to the name of
|
|
the logfile, if a log file name isn't specified.
|
|
"""
|
|
super(SchedulerLoggingConfig, self).configure_logging(use_console=True)
|
|
|
|
if log_dir is None:
|
|
log_dir = self.get_server_log_dir()
|
|
if not logfile_name:
|
|
logfile_name = self.get_log_name(timestamped_logfile_prefix)
|
|
|
|
self.add_file_handler(logfile_name, logging.DEBUG, log_dir=log_dir)
|
|
symlink_path = os.path.join(
|
|
log_dir, '%s.latest' % timestamped_logfile_prefix)
|
|
try:
|
|
os.unlink(symlink_path)
|
|
except OSError:
|
|
pass
|
|
os.symlink(os.path.join(log_dir, logfile_name), symlink_path)
|
|
|
|
|
|
def setup_logging(log_dir, log_name, timestamped_logfile_prefix='scheduler'):
|
|
"""Setup logging to a given log directory and log file.
|
|
|
|
@param log_dir: The directory to log into.
|
|
@param log_name: Name of the log file.
|
|
@param timestamped_logfile_prefix: The prefix to apply to the logfile.
|
|
"""
|
|
logging_manager.configure_logging(
|
|
SchedulerLoggingConfig(), log_dir=log_dir, logfile_name=log_name,
|
|
timestamped_logfile_prefix=timestamped_logfile_prefix)
|
|
|
|
|
|
def check_production_settings(scheduler_options):
|
|
"""Check the scheduler option's production settings.
|
|
|
|
@param scheduler_options: Settings for scheduler.
|
|
|
|
@raises SchedulerError: If a loclhost scheduler is started with
|
|
production settings.
|
|
"""
|
|
db_server = global_config.global_config.get_config_value('AUTOTEST_WEB',
|
|
'host')
|
|
if (not scheduler_options.production and
|
|
not utils.is_localhost(db_server)):
|
|
raise SchedulerError('Scheduler is not running in production mode, you '
|
|
'should not set database to hosts other than '
|
|
'localhost. It\'s currently set to %s.\nAdd option'
|
|
' --production if you want to skip this check.' %
|
|
db_server)
|