blob: b1aa6df6b64f81fe889feb37431eda6679ba1a2d [file] [log] [blame]
showard34ab0992009-10-05 22:47:57 +00001#!/usr/bin/python
2
3import logging, unittest
4import common
5from autotest_lib.client.common_lib import global_config
6from autotest_lib.database import database_connection
7from autotest_lib.frontend import setup_django_environment
8from autotest_lib.frontend.afe import frontend_test_utils
9from autotest_lib.scheduler import drone_manager, email_manager, monitor_db
10
11# translations necessary for scheduler queries to work with SQLite
12_re_translator = database_connection.TranslatingDatabase.make_regexp_translator
13_DB_TRANSLATORS = (
14 _re_translator(r'NOW\(\)', 'time("now")'),
15 # older SQLite doesn't support group_concat, so just don't bother until
16 # it arises in an important query
17 _re_translator(r'GROUP_CONCAT\((.*?)\)', r'\1'),
18)
19
20class NullMethodObject(object):
21 _NULL_METHODS = ()
22
23 def __init__(self):
24 def null_method(*args, **kwargs):
25 pass
26
27 for method_name in self._NULL_METHODS:
28 setattr(self, method_name, null_method)
29
30class MockGlobalConfig(object):
31 def __init__(self):
32 self._config_info = {}
33
34
35 def set_config_value(self, section, key, value):
36 self._config_info[(section, key)] = value
37
38
39 def get_config_value(self, section, key, type=str,
40 default=None, allow_blank=False):
41 identifier = (section, key)
42 if identifier not in self._config_info:
43 raise RuntimeError('Unset global config value: %s' % (identifier,))
44 return self._config_info[identifier]
45
46
47class MockDroneManager(NullMethodObject):
48 _NULL_METHODS = ('refresh', 'reinitialize_drones',
49 'copy_to_results_repository')
50
51 def __init__(self):
52 super(MockDroneManager, self).__init__()
53 # maps result_dir to set of tuples (file_path, file_contents)
54 self._attached_files = {}
55 # maps pidfile IDs to PidfileContents
56 self._pidfiles = {}
57 # pidfile IDs that haven't been created yet
58 self._future_pidfiles = []
59 # the most recently created pidfile ID
60 self._last_pidfile_id = None
61 # maps (working_directory, pidfile_name) to pidfile IDs
62 self._pidfile_index = {}
63
64
65 # utility APIs for use by the test
66
67 def set_last_pidfile_exit_status(self, exit_status):
68 assert self._last_pidfile_id is not None
69 self._set_pidfile_exit_status(self._last_pidfile_id, exit_status)
70
71
72 def set_pidfile_exit_status(self, working_directory, pidfile_name,
73 exit_status):
74 key = (working_directory, pidfile_name)
75 self._set_pidfile_exit_status(self._pidfile_index[key], exit_status)
76
77 def _set_pidfile_exit_status(self, pidfile_id, exit_status):
78 contents = self._pidfiles[pidfile_id]
79 contents.exit_status = exit_status
80 contents.num_tests_failed = 0
81
82
83 # DroneManager emulation APIs for use by monitor_db
84
85 def get_orphaned_autoserv_processes(self):
86 return set()
87
88
89 def total_running_processes(self):
90 return 0
91
92
93 def max_runnable_processes(self):
94 return 100
95
96
97 def execute_actions(self):
98 # executing an "execute_command" causes a pidfile to be created
99 for pidfile_id in self._future_pidfiles:
100 # Process objects are opaque to monitor_db
101 self._pidfiles[pidfile_id].process = object()
102 self._future_pidfiles = []
103
104
105 def attach_file_to_execution(self, result_dir, file_contents,
106 file_path=None):
107 self._attached_files.setdefault(result_dir, set()).add((file_path,
108 file_contents))
109 return 'attach_path'
110
111
112 def execute_command(self, command, working_directory, pidfile_name,
113 log_file=None, paired_with_pidfile=None):
114 # TODO: record this
115 pidfile_id = object() # PidfileIds are opaque to monitor_db
116 self._future_pidfiles.append(pidfile_id)
117 self._pidfiles[pidfile_id] = drone_manager.PidfileContents()
118 self._pidfile_index[(working_directory, pidfile_name)] = pidfile_id
119 self._last_pidfile_id = pidfile_id
120 return pidfile_id
121
122
123 def get_pidfile_contents(self, pidfile_id, use_second_read=False):
124 return self._pidfiles.get(pidfile_id,
125 drone_manager.PidfileContents())
126
127
128 def is_process_running(self, process):
129 return True
130
131
132 def register_pidfile(self, pidfile_id):
133 # TODO
134 pass
135
136
137 def absolute_path(self, path):
138 return 'absolute/' + path
139
140
141 def write_lines_to_file(self, file_path, lines, paired_with_process=None):
142 # TODO: record this
143 pass
144
145
146 def get_pidfile_id_from(self, execution_tag, pidfile_name):
147 return self._pidfile_index.get((execution_tag, pidfile_name), object())
148
149
150class MockEmailManager(NullMethodObject):
151 _NULL_METHODS = ('send_queued_emails', 'send_email')
152
153
154class SchedulerFunctionalTest(unittest.TestCase,
155 frontend_test_utils.FrontendTestMixin):
156 # some number of ticks after which the scheduler is presumed to have
157 # stabilized, given no external changes
158 _A_LOT_OF_TICKS = 10
159
160 def setUp(self):
161 self._frontend_common_setup()
162 self._set_stubs()
163 self._set_global_config_values()
164 self.dispatcher = monitor_db.Dispatcher()
165
166 logging.basicConfig(level=logging.DEBUG)
167
168
169 def tearDown(self):
170 self._frontend_common_teardown()
171
172
173 def _set_stubs(self):
174 self.mock_config = MockGlobalConfig()
175 self.god.stub_with(global_config, 'global_config', self.mock_config)
176
177 self.mock_drone_manager = MockDroneManager()
178 self.god.stub_with(monitor_db, '_drone_manager',
179 self.mock_drone_manager)
180
181 self.mock_email_manager = MockEmailManager()
182 self.god.stub_with(email_manager, 'manager', self.mock_email_manager)
183
184 self._database = (
185 database_connection.TranslatingDatabase.get_test_database(
186 file_path=self._test_db_file,
187 translators=_DB_TRANSLATORS))
188 self._database.connect(db_type='django')
189 self.god.stub_with(monitor_db, '_db', self._database)
190
191
192 def _set_global_config_values(self):
193 self.mock_config.set_config_value('SCHEDULER', 'pidfile_timeout_mins',
194 1)
195
196
197 def _initialize_test(self):
198 self.dispatcher.initialize()
199
200
201 def _run_dispatcher(self):
202 for _ in xrange(self._A_LOT_OF_TICKS):
203 self.dispatcher.tick()
204
205
206 def test_idle(self):
207 self._initialize_test()
208 self._run_dispatcher()
209
210
211 def test_simple_job(self):
212 self._initialize_test()
213 self._create_job(hosts=[1])
214 self._run_dispatcher() # launches verify
215 self.mock_drone_manager.set_last_pidfile_exit_status(0)
216 self._run_dispatcher() # launches job
217 self.mock_drone_manager.set_last_pidfile_exit_status(0)
218 self._run_dispatcher() # launches parsing + cleanup
219 self.mock_drone_manager.set_pidfile_exit_status(
220 'hosts/host1/2-cleanup', monitor_db._AUTOSERV_PID_FILE, 0)
221 self.mock_drone_manager.set_pidfile_exit_status(
222 '1-my_user/host1', monitor_db._PARSER_PID_FILE, 0)
223 self._run_dispatcher()
224
225
226if __name__ == '__main__':
227 unittest.main()