)
+class AnsibleFailedError(Exception):
+
+ """
+ Exception thrown when an ansible playbook fails
+ """
+ def __init__(self, failures):
+ self.failures = failures
+
+ def __str__(self):
+ return "{failures}".format(
+ failures=self.failures,
+ )
+
+
class CommandCrashedError(Exception):
"""
from tempfile import NamedTemporaryFile
from teuthology.config import config as teuth_config
-from teuthology.exceptions import CommandFailedError
+from teuthology.exceptions import CommandFailedError, AnsibleFailedError
from teuthology.repo_utils import fetch_repo
from . import Task
if not hasattr(self, 'playbook_file'):
self.generate_playbook()
+ @property
+ def failure_log(self):
+ if not hasattr(self, '_failure_log'):
+ self._failure_log = NamedTemporaryFile(
+ prefix="teuth_ansible_failures_",
+ delete=False,
+ )
+ return self._failure_log
+
def find_repo(self):
"""
Locate the repo we're using; cloning it from a remote repo if necessary
"""
environ = os.environ
environ['ANSIBLE_SSH_PIPELINING'] = '1'
+ environ['ANSIBLE_FAILURE_LOG'] = self.failure_log.name
environ['ANSIBLE_ROLES_PATH'] = "%s/roles" % self.repo_path
args = self._build_args()
command = ' '.join(args)
timeout=None,
)
if status != 0:
- raise CommandFailedError(command, status)
+ self._handle_failure(command, status)
if self.config.get('reconnect', True) is True:
remotes = self.cluster.remotes.keys()
for remote in remotes:
remote.reconnect()
+ def _handle_failure(self, command, status):
+ failures = None
+ with open(self.failure_log.name, 'r') as log:
+ failures = yaml.safe_load(log)
+
+ if failures:
+ if self.ctx.archive:
+ self._archive_failures()
+ raise AnsibleFailedError(failures)
+ raise CommandFailedError(command, status)
+
+ def _archive_failures(self):
+ os.rename(
+ self.failure_log.name,
+ "{0}/ansible_failures.yaml".format(self.ctx.archive)
+ )
+
def _build_args(self):
"""
Assemble the list of args to be executed