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