Skip to content

Commit 4adaa11

Browse files
committed
WIP: Added exception passing from Ansible thread via threading.excepthook
1 parent b0f8805 commit 4adaa11

File tree

2 files changed

+54
-34
lines changed

2 files changed

+54
-34
lines changed

src/cotea/ansible_execution_sync.py

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ def __init__(self, logger):
77
self.ansible_event = threading.Event()
88
self.logger = logger
99
self.curr_breakpoint_label = None
10+
# Used to pass exceptions from Ansible thread
11+
self.exception = None
1012

1113
def status(self):
1214
self.logger.debug("Runner event status: %s", self.runner_event.is_set())
@@ -17,6 +19,8 @@ def runner_just_wait(self):
1719
#self.logger.debug("runner: waiting...")
1820
self.runner_event.wait()
1921
self.runner_event.clear()
22+
if self.exception is not None:
23+
raise self.exception
2024

2125
def ansible_just_wait(self):
2226
#self.logger.debug("ansible: waiting...")
@@ -36,6 +40,8 @@ def continue_ansible_with_stop(self):
3640
self.runner_event.wait()
3741
self.runner_event.clear()
3842
#self.logger.debug("runner: ANSIBLE WAKED ME UP")
43+
if self.exception is not None:
44+
raise self.exception
3945

4046
def continue_runner(self):
4147
#self.logger.debug("ansible: resume runner work")

src/cotea/runner.py

+48-34
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,17 @@ def __init__(self, pb_path, arg_maker, debug_mod=None, show_progress_bar=False):
3939
logging_lvl = logging.INFO
4040
if debug_mod:
4141
logging_lvl= logging.DEBUG
42-
42+
4343
self.show_progress_bar = show_progress_bar
44-
44+
4545
logging.basicConfig(format="%(name)s %(asctime)s %(message)s", \
4646
datefmt="%H:%M:%S", level=logging_lvl)
4747

4848
self.pb_path = pb_path
4949
self.arg_maker = arg_maker
5050

5151
self.logger = logging.getLogger("RUNNER")
52-
52+
5353
log_sync = logging.getLogger("SYNC")
5454
self.sync_obj = ans_sync(log_sync)
5555

@@ -67,7 +67,7 @@ def __init__(self, pb_path, arg_maker, debug_mod=None, show_progress_bar=False):
6767
self._set_wrappers()
6868
start_ok = self._start_ansible()
6969
self.logger.debug("Ansible start ok: %s", start_ok)
70-
70+
7171

7272
def _set_wrappers(self):
7373
wrp_lgr = logging.getLogger("WRPR")
@@ -110,7 +110,7 @@ def _set_wrappers(self):
110110
self.execution_tree,
111111
self.progress_bar)
112112
PlayIterator.add_tasks = self.iterator_add_task_wrp
113-
113+
114114

115115
def _set_wrappers_back(self):
116116
PlaybookCLI.run = self.pbcli_run_wrp.func
@@ -121,7 +121,18 @@ def _set_wrappers_back(self):
121121
PlayIterator.add_tasks = self.iterator_add_task_wrp.func
122122
if self.show_progress_bar:
123123
PlaybookExecutor.__init__ = self.playbook_executor_wrp.func
124-
124+
125+
def _except_hook(self, args, /):
126+
exc_type, exc_value, exc_traceback, thread = \
127+
args.exc_type, args.exc_value, args.exc_traceback, args.thread
128+
129+
if (exc_type == SystemExit or
130+
# NOTE: this probably should never happen
131+
thread != self.ansible_thread):
132+
return self._old_except_hook(args)
133+
134+
self.sync_obj.exception = exc_value
135+
self.sync_obj.continue_runner()
125136

126137
def _start_ansible(self):
127138
args = self.arg_maker.args
@@ -131,19 +142,22 @@ def _start_ansible(self):
131142
self.pbCLI = PlaybookCLI(args)
132143

133144
self.ansible_thread = threading.Thread(target=self.pbCLI.run)
145+
self._old_except_hook = threading.excepthook
146+
threading.excepthook = self._except_hook
147+
134148
self.ansible_thread.start()
135149
self.sync_obj.runner_just_wait()
136150

137151
if self.sync_obj.curr_breakpoint_label == self.breakpoint_labeles["before_playbook"]:
138152
return True
139-
153+
140154
return False
141-
155+
142156

143157
def has_next_play(self):
144158
if self.sync_obj.curr_breakpoint_label == self.breakpoint_labeles["after_playbook"]:
145159
return False
146-
160+
147161
self.sync_obj.continue_ansible_with_stop()
148162
current_bp_label = self.sync_obj.curr_breakpoint_label
149163
self.logger.debug("has_next_play: %s", current_bp_label)
@@ -180,18 +194,18 @@ def run_next_task(self):
180194

181195
if current_bp_label != self.breakpoint_labeles["after_task"]:
182196
self.logger.debug("run_next_task() has come not in to the 'after_task'")
183-
197+
184198
for task_result_ansible_obj in self.update_conn_wrapper.current_results:
185199
res.append(TaskResult(task_result_ansible_obj))
186200

187201
self.task_wrp.set_next_to_prev()
188202

189203
return res
190-
204+
191205

192206
def rerun_last_task(self):
193207
self.task_wrp.rerun_last_task = True
194-
208+
195209

196210
# returns True and empty string if success
197211
# False and error msg otherwise
@@ -202,7 +216,7 @@ def add_new_task(self, new_task_str, is_dict=False):
202216
has_attrs, error_msg = cotea_utils.obj_has_attrs(prev_task, ["_parent"])
203217
if not has_attrs:
204218
return False, error_msg
205-
219+
206220
curr_block = prev_task._parent
207221
block_attrs = ["_loader", "_play", "_role", "_variable_manager", "_use_handlers"]
208222
has_attrs, error_msg = cotea_utils.obj_has_attrs(curr_block, block_attrs)
@@ -227,16 +241,16 @@ def add_new_task(self, new_task_str, is_dict=False):
227241
error_msg += "(from str-aka-dict to python ds): {}"
228242
return False, error_msg.format(is_dict, str(e))
229243
ds = [new_task_str_dict]
230-
244+
231245
#print("DS:\n", ds)
232-
246+
233247
has_attrs, _ = cotea_utils.obj_has_attrs(ds, ["__len__"])
234248
if not has_attrs:
235249
error_msg = "Python repr of the input string should have "
236250
error_msg += "__len__ attr. Maybe something wrong with input: {}\n"
237251
error_msg += "Python repr without __len__ attr: {}"
238252
return False, error_msg.format(new_task_str, str(ds))
239-
253+
240254
if len(ds) != 1:
241255
error_msg = "You must add 1 new task. Instead you add: {}"
242256
return False, error_msg.format(str(ds))
@@ -261,7 +275,7 @@ def add_new_task(self, new_task_str, is_dict=False):
261275
error_msg = "Exception during load_list_of_tasks call "
262276
error_msg += "(creats Ansible.Task objects): {}"
263277
return False, error_msg.format(str(e))
264-
278+
265279
has_attrs, _ = cotea_utils.obj_has_attrs(new_ansible_task, ["__len__"])
266280
if not has_attrs:
267281
error_msg = "Python repr of the input string should have "
@@ -274,23 +288,23 @@ def add_new_task(self, new_task_str, is_dict=False):
274288
error_msg = "The input '{}' has been interpreted into {} tasks "
275289
error_msg += "instead of 1. Interpretation result: {}"
276290
return False, error_msg.format(new_task_str, new_tasks_count, str(ds))
277-
291+
278292
#self.task_wrp.new_task_to_add = True
279293
self.task_wrp.new_task = new_ansible_task[0]
280-
294+
281295
adding_res, error_msg = self.task_wrp.add_tasks(new_ansible_task)
282296

283297
return adding_res, error_msg
284298

285-
299+
286300
def get_new_added_task(self):
287301
return self.task_wrp.new_task
288302

289-
303+
290304
def ignore_errors_of_next_task(self):
291305
self.task_wrp.next_task_ignore_errors = True
292306

293-
307+
294308
def dont_add_last_task_after_new(self):
295309
self.task_wrp.dont_add_last_task_after_new()
296310

@@ -306,31 +320,31 @@ def get_already_ignore_unrch(self):
306320
def finish_ansible(self):
307321
while self.sync_obj.curr_breakpoint_label != self.breakpoint_labeles["after_playbook"]:
308322
self.sync_obj.continue_ansible_with_stop()
309-
323+
310324
self.sync_obj.continue_ansible()
311325
self.ansible_thread.join(timeout=5)
312326
self._set_wrappers_back()
313-
327+
314328

315329
def get_cur_play_name(self):
316330
return str(self.play_wrp.current_play_name)
317-
331+
318332

319333
def get_next_task(self):
320334
return self.task_wrp.get_next_task()
321335

322336

323337
def get_next_task_name(self):
324338
return str(self.task_wrp.get_next_task_name())
325-
339+
326340

327341
def get_prev_task(self):
328342
return self.task_wrp.get_prev_task()
329-
343+
330344

331345
def get_prev_task_name(self):
332346
return str(self.task_wrp.get_prev_task_name())
333-
347+
334348

335349
def get_last_task_result(self):
336350
res = []
@@ -339,17 +353,17 @@ def get_last_task_result(self):
339353
res.append(TaskResult(task_result_ansible_obj))
340354

341355
return res
342-
356+
343357

344358
# returns True if there was an non ignored error
345359
def was_error(self):
346360
return self.play_wrp.was_error
347-
361+
348362

349363
# returns list with all errors, including the ignored ones
350364
def get_all_error_msgs(self):
351365
return self.update_conn_wrapper.error_msgs
352-
366+
353367

354368
# returns last error msg that wasn't ignored
355369
def get_error_msg(self):
@@ -361,9 +375,9 @@ def get_error_msg(self):
361375

362376
if errors_count > 0:
363377
res = self.update_conn_wrapper.error_msgs[errors_count - 1]
364-
378+
365379
return res
366-
380+
367381

368382
def get_all_vars(self):
369383
variable_manager = self.play_wrp.variable_manager
@@ -419,7 +433,7 @@ def get_variable(self, var_name):
419433
self.logger.info("There is no variable with name %s", var_name)
420434

421435
return None
422-
436+
423437

424438
def add_var_as_extra_var(self, new_var_name, value):
425439
variable_manager = self.play_wrp.variable_manager

0 commit comments

Comments
 (0)