From 6117e1946ebb70ea49c45e61b15c6eb2d8bb26ce Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 12 Sep 2016 08:27:39 -0700 Subject: [PATCH] Check controlpersist (#17443) * Add a new config option to cache the check for controlpersist on the control machine. Fixes #15844 * Remove the option and make the behavior the default * Make the check for controlpersist cache its status per-ssh executable --- lib/ansible/executor/playbook_executor.py | 9 +++++ lib/ansible/executor/task_executor.py | 10 +---- lib/ansible/inventory/__init__.py | 1 + lib/ansible/utils/ssh_functions.py | 47 +++++++++++++++++++++++ 4 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 lib/ansible/utils/ssh_functions.py diff --git a/lib/ansible/executor/playbook_executor.py b/lib/ansible/executor/playbook_executor.py index 058230d8441..633affe6bc5 100644 --- a/lib/ansible/executor/playbook_executor.py +++ b/lib/ansible/executor/playbook_executor.py @@ -28,6 +28,7 @@ from ansible.playbook import Playbook from ansible.template import Templar from ansible.utils.helpers import pct_to_int from ansible.utils.path import makedirs_safe +from ansible.utils.ssh_functions import check_for_controlpersist try: from __main__ import display @@ -57,6 +58,14 @@ class PlaybookExecutor: else: self._tqm = TaskQueueManager(inventory=inventory, variable_manager=variable_manager, loader=loader, options=options, passwords=self.passwords) + # Note: We run this here to cache whether the default ansible ssh + # executable supports control persist. Sometime in the future we may + # need to enhance this to check that ansible_ssh_executable specified + # in inventory is also cached. We can't do this caching at the point + # where it is used (in task_executor) because that is post-fork and + # therefore would be discarded after every task. + check_for_controlpersist(C.ANSIBLE_SSH_EXECUTABLE) + def run(self): ''' diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py index 8000fb1a1b4..9c1ef870b59 100644 --- a/lib/ansible/executor/task_executor.py +++ b/lib/ansible/executor/task_executor.py @@ -36,6 +36,7 @@ from ansible.playbook.task import Task from ansible.template import Templar from ansible.utils.encrypt import key_for_hostname from ansible.utils.listify import listify_lookup_plugin_terms +from ansible.utils.ssh_functions import check_for_controlpersist from ansible.vars.unsafe_proxy import UnsafeProxy, wrap_var try: @@ -666,14 +667,7 @@ class TaskExecutor: conn_type = "paramiko" else: # see if SSH can support ControlPersist if not use paramiko - try: - ssh_executable = C.ANSIBLE_SSH_EXECUTABLE - cmd = subprocess.Popen([ssh_executable, '-o', 'ControlPersist'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = cmd.communicate() - err = to_text(err) - if u"Bad configuration option" in err or u"Usage:" in err: - conn_type = "paramiko" - except OSError: + if not check_for_controlpersist(self._play_context.ssh_executable): conn_type = "paramiko" connection = self._shared_loader_obj.connection_loader.get(conn_type, self._play_context, self._new_stdin) diff --git a/lib/ansible/inventory/__init__.py b/lib/ansible/inventory/__init__.py index e68e5c7e521..a6ce3773666 100644 --- a/lib/ansible/inventory/__init__.py +++ b/lib/ansible/inventory/__init__.py @@ -21,6 +21,7 @@ __metaclass__ = type import fnmatch import os +import subprocess import sys import re import itertools diff --git a/lib/ansible/utils/ssh_functions.py b/lib/ansible/utils/ssh_functions.py new file mode 100644 index 00000000000..b4ac1dd5884 --- /dev/null +++ b/lib/ansible/utils/ssh_functions.py @@ -0,0 +1,47 @@ +# (c) 2016, James Tanner +# (c) 2016, Toshio Kuratomi +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import subprocess + + +_HAS_CONTROLPERSIST = {} + + +def check_for_controlpersist(ssh_executable): + try: + # If we've already checked this executable + return _HAS_CONTROLPERSIST[ssh_executable] + except KeyError: + pass + + has_cp = True + try: + cmd = subprocess.Popen([ssh_executable,'-o','ControlPersist'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (out, err) = cmd.communicate() + if b"Bad configuration option" in err or b"Usage:" in err: + has_cp = False + except OSError: + has_cp = False + + _HAS_CONTROLPERSIST[ssh_executable] = has_cp + return has_cp +