blob: 36a1e59cba2680cb3ca1fd3c6f0d761eafdd452e [file] [log] [blame]
Prashanth B0e960282014-05-13 19:38:28 -07001
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"""
8import logging
Prashanth B97ee2dd2014-08-14 15:21:42 -07009import os
Prashanth B0e960282014-05-13 19:38:28 -070010
11import common
12
Dan Shif6c65bd2014-08-29 16:15:07 -070013from autotest_lib.client.common_lib import global_config
Prashanth B0e960282014-05-13 19:38:28 -070014from autotest_lib.client.common_lib import logging_config
15from autotest_lib.client.common_lib import logging_manager
Dan Shif6c65bd2014-08-29 16:15:07 -070016from autotest_lib.client.common_lib import utils
Prashanth B0e960282014-05-13 19:38:28 -070017from autotest_lib.database import database_connection
18from autotest_lib.frontend import setup_django_environment
19from autotest_lib.frontend.afe import readonly_connection
Fang Dengf08814a2015-08-03 18:12:18 +000020from autotest_lib.server import utils as server_utils
Prashanth B0e960282014-05-13 19:38:28 -070021
22
23DB_CONFIG_SECTION = 'AUTOTEST_WEB'
24
Prashanth B4ec98672014-05-15 10:44:54 -070025# 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 B0e960282014-05-13 19:38:28 -070040
41class SchedulerError(Exception):
Aviv Keshet0c93b862017-07-13 11:58:03 -070042 """General parent class for exceptions raised by scheduler code."""
Prashanth B0e960282014-05-13 19:38:28 -070043
44
Aviv Keshet0c93b862017-07-13 11:58:03 -070045class 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
53class NoHostIdError(MalformedRecordError):
xixuan2d568882017-04-24 14:21:41 -070054 """Raised by the scheduler when a non-hostless job's host is None."""
55
56
Prashanth B0e960282014-05-13 19:38:28 -070057class ConnectionManager(object):
58 """Manager for the django database connections.
59
60 The connection is used through scheduler_models and monitor_db.
61 """
Fang Dengf08814a2015-08-03 18:12:18 +000062 __metaclass__ = server_utils.Singleton
Prashanth B0e960282014-05-13 19:38:28 -070063
Prashanth Bcab7d5b2014-05-28 17:48:23 -070064 def __init__(self, readonly=True, autocommit=True):
Prashanth B0e960282014-05-13 19:38:28 -070065 """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 Juelich7bef8412014-10-14 19:11:54 -070072 readonly_connection.set_globally_disabled(readonly)
Prashanth B0e960282014-05-13 19:38:28 -070073 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
111class 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 B97ee2dd2014-08-14 15:21:42 -0700145 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 B0e960282014-05-13 19:38:28 -0700152
153
154def 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 Shif6c65bd2014-08-29 16:15:07 -0700164
165
166def 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)