Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 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 |
Prashanth B | 97ee2dd | 2014-08-14 15:21:42 -0700 | [diff] [blame] | 9 | import os |
Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 10 | |
| 11 | import common |
| 12 | |
Dan Shi | f6c65bd | 2014-08-29 16:15:07 -0700 | [diff] [blame] | 13 | from autotest_lib.client.common_lib import global_config |
Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 14 | from autotest_lib.client.common_lib import logging_config |
| 15 | from autotest_lib.client.common_lib import logging_manager |
Dan Shi | f6c65bd | 2014-08-29 16:15:07 -0700 | [diff] [blame] | 16 | from autotest_lib.client.common_lib import utils |
Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 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 |
Fang Deng | f08814a | 2015-08-03 18:12:18 +0000 | [diff] [blame] | 20 | from autotest_lib.server import utils as server_utils |
Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 21 | |
| 22 | |
| 23 | DB_CONFIG_SECTION = 'AUTOTEST_WEB' |
| 24 | |
Prashanth B | 4ec9867 | 2014-05-15 10:44:54 -0700 | [diff] [blame] | 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 | |
Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 40 | |
| 41 | class SchedulerError(Exception): |
Aviv Keshet | 0c93b86 | 2017-07-13 11:58:03 -0700 | [diff] [blame] | 42 | """General parent class for exceptions raised by scheduler code.""" |
Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 43 | |
| 44 | |
Aviv Keshet | 0c93b86 | 2017-07-13 11:58:03 -0700 | [diff] [blame] | 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): |
xixuan | 2d56888 | 2017-04-24 14:21:41 -0700 | [diff] [blame] | 54 | """Raised by the scheduler when a non-hostless job's host is None.""" |
| 55 | |
| 56 | |
Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 57 | class ConnectionManager(object): |
| 58 | """Manager for the django database connections. |
| 59 | |
| 60 | The connection is used through scheduler_models and monitor_db. |
| 61 | """ |
Fang Deng | f08814a | 2015-08-03 18:12:18 +0000 | [diff] [blame] | 62 | __metaclass__ = server_utils.Singleton |
Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 63 | |
Prashanth B | cab7d5b | 2014-05-28 17:48:23 -0700 | [diff] [blame] | 64 | def __init__(self, readonly=True, autocommit=True): |
Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 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 |
Jakob Juelich | 7bef841 | 2014-10-14 19:11:54 -0700 | [diff] [blame] | 72 | readonly_connection.set_globally_disabled(readonly) |
Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 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) |
Prashanth B | 97ee2dd | 2014-08-14 15:21:42 -0700 | [diff] [blame] | 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) |
Prashanth B | 0e96028 | 2014-05-13 19:38:28 -0700 | [diff] [blame] | 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) |
Dan Shi | f6c65bd | 2014-08-29 16:15:07 -0700 | [diff] [blame] | 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) |