mirror of
https://github.com/ansible/awx.git
synced 2026-03-22 11:25:08 -02:30
don't process artifacts from custom set_stat calls asynchronously
previously, we persisted custom artifacts to the database on `Job.artifacts` via the callback receiver. when the callback receiver is backed up processing events, this can result in race conditions for workflows where a playbook calls `set_stat()`, but the artifact data is not persisted in the database before the next job in the workflow starts see: https://github.com/ansible/ansible-tower/issues/7831
This commit is contained in:
@@ -18,7 +18,11 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
# Python
|
||||
import codecs
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import uuid
|
||||
from copy import copy
|
||||
@@ -292,10 +296,22 @@ class BaseCallbackModule(CallbackBase):
|
||||
failures=stats.failures,
|
||||
ok=stats.ok,
|
||||
processed=stats.processed,
|
||||
skipped=stats.skipped,
|
||||
artifact_data=stats.custom.get('_run', {}) if hasattr(stats, 'custom') else {}
|
||||
skipped=stats.skipped
|
||||
)
|
||||
|
||||
# write custom set_stat artifact data to the local disk so that it can
|
||||
# be persisted by awx after the process exits
|
||||
custom_artifact_data = stats.custom.get('_run', {}) if hasattr(stats, 'custom') else {}
|
||||
if custom_artifact_data:
|
||||
# create the directory for custom stats artifacts to live in (if it doesn't exist)
|
||||
custom_artifacts_dir = os.path.join(os.getenv('AWX_PRIVATE_DATA_DIR'), 'artifacts')
|
||||
os.makedirs(custom_artifacts_dir, mode=stat.S_IXUSR + stat.S_IWUSR + stat.S_IRUSR)
|
||||
|
||||
custom_artifacts_path = os.path.join(custom_artifacts_dir, 'custom')
|
||||
with codecs.open(custom_artifacts_path, 'w', encoding='utf-8') as f:
|
||||
os.chmod(custom_artifacts_path, stat.S_IRUSR | stat.S_IWUSR)
|
||||
json.dump(custom_artifact_data, f)
|
||||
|
||||
with self.capture_event_data('playbook_on_stats', **event_data):
|
||||
super(BaseCallbackModule, self).v2_playbook_on_stats(stats)
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ from collections import OrderedDict
|
||||
import json
|
||||
import mock
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -254,3 +256,26 @@ def test_callback_plugin_strips_task_environ_variables(executor, cache, playbook
|
||||
assert len(cache)
|
||||
for event in cache.values():
|
||||
assert os.environ['PATH'] not in json.dumps(event)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('playbook', [
|
||||
{'custom_set_stat.yml': '''
|
||||
- name: custom set_stat calls should persist to the local disk so awx can save them
|
||||
connection: local
|
||||
hosts: all
|
||||
tasks:
|
||||
- set_stats:
|
||||
data:
|
||||
foo: "bar"
|
||||
'''}, # noqa
|
||||
])
|
||||
def test_callback_plugin_saves_custom_stats(executor, cache, playbook):
|
||||
try:
|
||||
private_data_dir = tempfile.mkdtemp()
|
||||
with mock.patch.dict(os.environ, {'AWX_PRIVATE_DATA_DIR': private_data_dir}):
|
||||
executor.run()
|
||||
artifacts_path = os.path.join(private_data_dir, 'artifacts', 'custom')
|
||||
with open(artifacts_path, 'r') as f:
|
||||
assert json.load(f) == {'foo': 'bar'}
|
||||
finally:
|
||||
shutil.rmtree(os.path.join(private_data_dir))
|
||||
|
||||
Reference in New Issue
Block a user