Delay persistent connection until needed (#59153)

* Delay calling connect() until absolutely necessary

* Implement transport_test to enable wait_for_connection

* plugin might be connected already for some reason?

* ensure_connect for httpapi

There's some become shenanigans still needing to be ironed out

* Fix tests for network_cli
This commit is contained in:
Nathaniel Case 2019-08-14 16:58:03 -04:00 committed by GitHub
parent f02f5c4b5d
commit 7d3c4a8882
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 29 additions and 13 deletions

View file

@ -101,12 +101,9 @@ class ConnectionProcess(object):
ansible_playbook_pid=self._ansible_playbook_pid)
self.connection.set_options(var_options=variables)
self.connection._connect()
self.connection._socket_path = self.socket_path
self.srv.register(self.connection)
messages.extend([('vvvv', msg) for msg in sys.stdout.getvalue().splitlines()])
messages.append(('vvvv', 'connection to remote device started successfully'))
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.bind(self.socket_path)
@ -123,7 +120,7 @@ class ConnectionProcess(object):
def run(self):
try:
while self.connection.connected:
while True:
signal.signal(signal.SIGALRM, self.connect_timeout)
signal.signal(signal.SIGTERM, self.handler)
signal.alarm(self.connection.get_option('persistent_connect_timeout'))

View file

@ -174,7 +174,7 @@ from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
from ansible.module_utils.urls import open_url
from ansible.playbook.play_context import PlayContext
from ansible.plugins.loader import httpapi_loader
from ansible.plugins.connection import NetworkConnectionBase
from ansible.plugins.connection import NetworkConnectionBase, ensure_connect
class Connection(NetworkConnectionBase):
@ -250,6 +250,7 @@ class Connection(NetworkConnectionBase):
super(Connection, self).close()
@ensure_connect
def send(self, path, data, **kwargs):
'''
Sends the command to the device over api

View file

@ -209,7 +209,7 @@ from ansible.module_utils.six.moves import cPickle
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils._text import to_bytes, to_text
from ansible.playbook.play_context import PlayContext
from ansible.plugins.connection import NetworkConnectionBase
from ansible.plugins.connection import NetworkConnectionBase, ensure_connect
from ansible.plugins.loader import cliconf_loader, terminal_loader, connection_loader
@ -326,8 +326,8 @@ class Connection(NetworkConnectionBase):
self.paramiko_conn.force_persistence = self.force_persistence
ssh = self.paramiko_conn._connect()
host = self.get_option('host')
self.queue_message('vvvv', 'ssh connection done, setting terminal')
self._connected = True
self._ssh_shell = ssh.ssh.invoke_shell()
self._ssh_shell.settimeout(self.get_option('persistent_command_timeout'))
@ -338,8 +338,11 @@ class Connection(NetworkConnectionBase):
self.queue_message('vvvv', 'loaded terminal plugin for network_os %s' % self._network_os)
self.receive(prompts=self._terminal.terminal_initial_prompt, answer=self._terminal.terminal_initial_answer,
newline=self._terminal.terminal_inital_prompt_newline)
self.receive(
prompts=self._terminal.terminal_initial_prompt,
answer=self._terminal.terminal_initial_answer,
newline=self._terminal.terminal_inital_prompt_newline
)
self.queue_message('vvvv', 'firing event: on_open_shell()')
self._terminal.on_open_shell()
@ -350,7 +353,6 @@ class Connection(NetworkConnectionBase):
self._terminal.on_become(passwd=auth_pass)
self.queue_message('vvvv', 'ssh connection has completed successfully')
self._connected = True
return self
@ -450,6 +452,7 @@ class Connection(NetworkConnectionBase):
else:
command_prompt_matched = True
@ensure_connect
def send(self, command, prompt=None, answer=None, newline=True, sendonly=False, prompt_retry_check=False, check_all=False):
'''
Sends the command to the device in the opened shell
@ -587,3 +590,17 @@ class Connection(NetworkConnectionBase):
def _validate_timeout_value(self, timeout, timer_name):
if timeout < 0:
raise AnsibleConnectionFailure("'%s' timer value '%s' is invalid, value should be greater than or equal to zero." % (timer_name, timeout))
def transport_test(self, connect_timeout):
"""This method enables wait_for_connection to work.
As it is used by wait_for_connection, it is called by that module's action plugin,
which is on the controller process, which means that nothing done on this instance
should impact the actual persistent connection... this check is for informational
purposes only and should be properly cleaned up.
"""
# Force a fresh connect if for some reason we have connected before.
self.close()
self._connect()
self.close()

View file

@ -111,7 +111,8 @@ class TestConnectionClass(unittest.TestCase):
self.assertEqual(out, b'command response')
mock_send.assert_called_with(command=b'command')
def test_network_cli_send(self):
@patch("ansible.plugins.connection.network_cli.Connection._connect")
def test_network_cli_send(self, mocked_connect):
pc = PlayContext()
pc.network_os = 'ios'
conn = connection_loader.get('network_cli', pc, '/dev/null')
@ -131,7 +132,7 @@ class TestConnectionClass(unittest.TestCase):
"""
mock__shell.recv.side_effect = [response, None]
output = conn.send(b'command', None, None, None)
output = conn.send(b'command')
mock__shell.sendall.assert_called_with(b'command\r')
self.assertEqual(to_text(conn._command_response), 'command response')
@ -140,5 +141,5 @@ class TestConnectionClass(unittest.TestCase):
mock__shell.recv.side_effect = [b"ERROR: error message device#"]
with self.assertRaises(AnsibleConnectionFailure) as exc:
conn.send(b'command', None, None, None)
conn.send(b'command')
self.assertEqual(str(exc.exception), 'ERROR: error message device#')