1 2 # Copyright (c) 2014 The Chromium OS Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 """Scheduler helper libraries. 7 """ 8 import logging 9 import os 10 11 import common 12 13 from autotest_lib.client.common_lib import global_config 14 from autotest_lib.client.common_lib import logging_config 15 from autotest_lib.client.common_lib import logging_manager 16 from autotest_lib.client.common_lib import utils 17 from autotest_lib.database import database_connection 18 from autotest_lib.frontend import setup_django_environment 19 from autotest_lib.frontend.afe import readonly_connection 20 from autotest_lib.server import utils as server_utils 21 22 23 DB_CONFIG_SECTION = 'AUTOTEST_WEB' 24 25 # Translations necessary for scheduler queries to work with SQLite. 26 # Though this is only used for testing it is included in this module to avoid 27 # circular imports. 28 _re_translator = database_connection.TranslatingDatabase.make_regexp_translator 29 _DB_TRANSLATORS = ( 30 _re_translator(r'NOW\(\)', 'time("now")'), 31 _re_translator(r'LAST_INSERT_ID\(\)', 'LAST_INSERT_ROWID()'), 32 # older SQLite doesn't support group_concat, so just don't bother until 33 # it arises in an important query 34 _re_translator(r'GROUP_CONCAT\((.*?)\)', r'\1'), 35 _re_translator(r'TRUNCATE TABLE', 'DELETE FROM'), 36 _re_translator(r'ISNULL\(([a-z,_]+)\)', 37 r'ifnull(nullif(\1, NULL), \1) DESC'), 38 ) 39 40 41 class SchedulerError(Exception): 42 """General parent class for exceptions raised by scheduler code.""" 43 44 45 class MalformedRecordError(SchedulerError): 46 """Exception raised when an individual job or record is malformed. 47 48 Code that handles individual records (e.g. afe jobs, hqe entries, special 49 tasks) should treat such an exception as a signal to skip or permanently 50 discard this record.""" 51 52 53 class NoHostIdError(MalformedRecordError): 54 """Raised by the scheduler when a non-hostless job's host is None.""" 55 56 57 class ConnectionManager(object): 58 """Manager for the django database connections. 59 60 The connection is used through scheduler_models and monitor_db. 61 """ 62 __metaclass__ = server_utils.Singleton 63 64 def __init__(self, readonly=True, autocommit=True): 65 """Set global django database options for correct connection handling. 66 67 @param readonly: Globally disable readonly connections. 68 @param autocommit: Initialize django autocommit options. 69 """ 70 self.db_connection = None 71 # bypass the readonly connection 72 readonly_connection.set_globally_disabled(readonly) 73 if autocommit: 74 # ensure Django connection is in autocommit 75 setup_django_environment.enable_autocommit() 76 77 78 @classmethod 79 def open_connection(cls): 80 """Open a new database connection. 81 82 @return: An instance of the newly opened connection. 83 """ 84 db = database_connection.DatabaseConnection(DB_CONFIG_SECTION) 85 db.connect(db_type='django') 86 return db 87 88 89 def get_connection(self): 90 """Get a connection. 91 92 @return: A database connection. 93 """ 94 if self.db_connection is None: 95 self.db_connection = self.open_connection() 96 return self.db_connection 97 98 99 def disconnect(self): 100 """Close the database connection.""" 101 try: 102 self.db_connection.disconnect() 103 except Exception as e: 104 logging.debug('Could not close the db connection. %s', e) 105 106 107 def __del__(self): 108 self.disconnect() 109 110 111 class SchedulerLoggingConfig(logging_config.LoggingConfig): 112 """Configure timestamped logging for a scheduler.""" 113 GLOBAL_LEVEL = logging.INFO 114 115 @classmethod 116 def get_log_name(cls, timestamped_logfile_prefix): 117 """Get the name of a logfile. 118 119 @param timestamped_logfile_prefix: The prefix to apply to the 120 a timestamped log. Eg: 'scheduler' will create a logfile named 121 scheduler.log.2014-05-12-17.24.02. 122 123 @return: The timestamped log name. 124 """ 125 return cls.get_timestamped_log_name(timestamped_logfile_prefix) 126 127 128 def configure_logging(self, log_dir=None, logfile_name=None, 129 timestamped_logfile_prefix='scheduler'): 130 """Configure logging to a specified logfile. 131 132 @param log_dir: The directory to log into. 133 @param logfile_name: The name of the log file. 134 @timestamped_logfile_prefix: The prefix to apply to the name of 135 the logfile, if a log file name isn't specified. 136 """ 137 super(SchedulerLoggingConfig, self).configure_logging(use_console=True) 138 139 if log_dir is None: 140 log_dir = self.get_server_log_dir() 141 if not logfile_name: 142 logfile_name = self.get_log_name(timestamped_logfile_prefix) 143 144 self.add_file_handler(logfile_name, logging.DEBUG, log_dir=log_dir) 145 symlink_path = os.path.join( 146 log_dir, '%s.latest' % timestamped_logfile_prefix) 147 try: 148 os.unlink(symlink_path) 149 except OSError: 150 pass 151 os.symlink(os.path.join(log_dir, logfile_name), symlink_path) 152 153 154 def setup_logging(log_dir, log_name, timestamped_logfile_prefix='scheduler'): 155 """Setup logging to a given log directory and log file. 156 157 @param log_dir: The directory to log into. 158 @param log_name: Name of the log file. 159 @param timestamped_logfile_prefix: The prefix to apply to the logfile. 160 """ 161 logging_manager.configure_logging( 162 SchedulerLoggingConfig(), log_dir=log_dir, logfile_name=log_name, 163 timestamped_logfile_prefix=timestamped_logfile_prefix) 164 165 166 def check_production_settings(scheduler_options): 167 """Check the scheduler option's production settings. 168 169 @param scheduler_options: Settings for scheduler. 170 171 @raises SchedulerError: If a loclhost scheduler is started with 172 production settings. 173 """ 174 db_server = global_config.global_config.get_config_value('AUTOTEST_WEB', 175 'host') 176 if (not scheduler_options.production and 177 not utils.is_localhost(db_server)): 178 raise SchedulerError('Scheduler is not running in production mode, you ' 179 'should not set database to hosts other than ' 180 'localhost. It\'s currently set to %s.\nAdd option' 181 ' --production if you want to skip this check.' % 182 db_server) 183