mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
implements python logging for network connection plugin (#22817)
* enables logging for network_cli and paramiko * enables logging for ansible-connection * enabled logging for netconf connection
This commit is contained in:
parent
c254618d4f
commit
ed7cace425
3 changed files with 61 additions and 45 deletions
|
@ -39,6 +39,7 @@ import time
|
||||||
import traceback
|
import traceback
|
||||||
import syslog
|
import syslog
|
||||||
import datetime
|
import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
@ -49,8 +50,8 @@ from ansible.playbook.play_context import PlayContext
|
||||||
from ansible.plugins import connection_loader
|
from ansible.plugins import connection_loader
|
||||||
from ansible.utils.path import unfrackpath, makedirs_safe
|
from ansible.utils.path import unfrackpath, makedirs_safe
|
||||||
from ansible.errors import AnsibleConnectionFailure
|
from ansible.errors import AnsibleConnectionFailure
|
||||||
from ansible.utils.display import Display
|
|
||||||
|
|
||||||
|
logger = logging.getLogger('ansible-connection')
|
||||||
|
|
||||||
def do_fork():
|
def do_fork():
|
||||||
'''
|
'''
|
||||||
|
@ -109,17 +110,24 @@ def recv_data(s):
|
||||||
data += d
|
data += d
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def log(msg, host, user=None):
|
||||||
|
msg = 'h=%s u=%s %s' % (host, user, msg)
|
||||||
|
logger.debug(msg)
|
||||||
|
|
||||||
|
|
||||||
class Server():
|
class Server():
|
||||||
|
|
||||||
def __init__(self, path, play_context):
|
def __init__(self, path, play_context):
|
||||||
display.vvvv("starting new persistent socket with path %s" % path, play_context.remote_addr)
|
|
||||||
|
|
||||||
self.path = path
|
self.path = path
|
||||||
self.play_context = play_context
|
self.play_context = play_context
|
||||||
|
self.log = lambda x: log(x, play_context.remote_addr, play_context.remote_user)
|
||||||
|
|
||||||
|
self.log("starting new persistent socket with path %s" % path)
|
||||||
|
|
||||||
self._start_time = datetime.datetime.now()
|
self._start_time = datetime.datetime.now()
|
||||||
|
|
||||||
display.vvv("using connection plugin %s" % self.play_context.connection, play_context.remote_addr)
|
self.log("using connection plugin %s" % self.play_context.connection)
|
||||||
|
|
||||||
self.conn = connection_loader.get(play_context.connection, play_context, sys.stdin)
|
self.conn = connection_loader.get(play_context.connection, play_context, sys.stdin)
|
||||||
self.conn._connect()
|
self.conn._connect()
|
||||||
|
@ -127,7 +135,7 @@ class Server():
|
||||||
raise AnsibleConnectionFailure('unable to connect to remote host')
|
raise AnsibleConnectionFailure('unable to connect to remote host')
|
||||||
|
|
||||||
connection_time = datetime.datetime.now() - self._start_time
|
connection_time = datetime.datetime.now() - self._start_time
|
||||||
display.vvvv('connection established in %s' % connection_time, play_context.remote_addr)
|
self.log('connection established in %s' % connection_time)
|
||||||
|
|
||||||
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
self.socket.bind(path)
|
self.socket.bind(path)
|
||||||
|
@ -165,7 +173,7 @@ class Server():
|
||||||
signal.alarm(C.PERSISTENT_CONNECT_TIMEOUT)
|
signal.alarm(C.PERSISTENT_CONNECT_TIMEOUT)
|
||||||
try:
|
try:
|
||||||
(s, addr) = self.socket.accept()
|
(s, addr) = self.socket.accept()
|
||||||
display.vvvv('incoming request accepted on persistent socket', self.play_context.remote_addr)
|
self.log('incoming request accepted on persistent socket')
|
||||||
# clear the alarm
|
# clear the alarm
|
||||||
# FIXME: potential race condition here between the accept and
|
# FIXME: potential race condition here between the accept and
|
||||||
# time to this call.
|
# time to this call.
|
||||||
|
@ -177,14 +185,13 @@ class Server():
|
||||||
data = recv_data(s)
|
data = recv_data(s)
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
display.vvvv("received data on socket with len %s" % len(data), self.play_context.remote_addr)
|
|
||||||
|
|
||||||
signal.alarm(C.DEFAULT_TIMEOUT)
|
signal.alarm(C.DEFAULT_TIMEOUT)
|
||||||
|
|
||||||
rc = 255
|
rc = 255
|
||||||
try:
|
try:
|
||||||
if data.startswith(b'EXEC: '):
|
if data.startswith(b'EXEC: '):
|
||||||
display.vvvv("socket operation is EXEC", self.play_context.remote_addr)
|
self.log("socket operation is EXEC")
|
||||||
cmd = data.split(b'EXEC: ')[1]
|
cmd = data.split(b'EXEC: ')[1]
|
||||||
(rc, stdout, stderr) = self.conn.exec_command(cmd)
|
(rc, stdout, stderr) = self.conn.exec_command(cmd)
|
||||||
elif data.startswith(b'PUT: ') or data.startswith(b'FETCH: '):
|
elif data.startswith(b'PUT: ') or data.startswith(b'FETCH: '):
|
||||||
|
@ -192,16 +199,16 @@ class Server():
|
||||||
stdout = stderr = ''
|
stdout = stderr = ''
|
||||||
try:
|
try:
|
||||||
if op == 'FETCH:':
|
if op == 'FETCH:':
|
||||||
display.vvvv("socket operation is FETCH", self.play_context.remote_addr)
|
self.log("socket operation is FETCH")
|
||||||
self.conn.fetch_file(src, dst)
|
self.conn.fetch_file(src, dst)
|
||||||
elif op == 'PUT:':
|
elif op == 'PUT:':
|
||||||
display.vvvv("socket operation is PUT", self.play_context.remote_addr)
|
self.log("socket operation is PUT")
|
||||||
self.conn.put_file(src, dst)
|
self.conn.put_file(src, dst)
|
||||||
rc = 0
|
rc = 0
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
elif data.startswith(b'CONTEXT: '):
|
elif data.startswith(b'CONTEXT: '):
|
||||||
display.vvvv("socket operation is CONTEXT", self.play_context.remote_addr)
|
self.log("socket operation is CONTEXT")
|
||||||
pc_data = data.split(b'CONTEXT: ')[1]
|
pc_data = data.split(b'CONTEXT: ')[1]
|
||||||
|
|
||||||
src = StringIO(pc_data)
|
src = StringIO(pc_data)
|
||||||
|
@ -214,7 +221,7 @@ class Server():
|
||||||
self.dispatch(self.conn, 'update_play_context', pc)
|
self.dispatch(self.conn, 'update_play_context', pc)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
display.vvvv("socket operation is UNKNOWN", self.play_context.remote_addr)
|
self.log("socket operation is UNKNOWN")
|
||||||
stdout = ''
|
stdout = ''
|
||||||
stderr = 'Invalid action specified'
|
stderr = 'Invalid action specified'
|
||||||
except:
|
except:
|
||||||
|
@ -223,20 +230,20 @@ class Server():
|
||||||
|
|
||||||
signal.alarm(0)
|
signal.alarm(0)
|
||||||
|
|
||||||
display.vvvv("socket operation completed with rc %s" % rc, self.play_context.remote_addr)
|
self.log("socket operation completed with rc %s" % rc)
|
||||||
|
|
||||||
send_data(s, to_bytes(str(rc)))
|
send_data(s, to_bytes(str(rc)))
|
||||||
send_data(s, to_bytes(stdout))
|
send_data(s, to_bytes(stdout))
|
||||||
send_data(s, to_bytes(stderr))
|
send_data(s, to_bytes(stderr))
|
||||||
s.close()
|
s.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
display.vvvv(traceback.format_exc())
|
self.log(traceback.foramt_exec())
|
||||||
finally:
|
finally:
|
||||||
# when done, close the connection properly and cleanup
|
# when done, close the connection properly and cleanup
|
||||||
# the socket file so it can be recreated
|
# the socket file so it can be recreated
|
||||||
end_time = datetime.datetime.now()
|
end_time = datetime.datetime.now()
|
||||||
delta = end_time - self._start_time
|
delta = end_time - self._start_time
|
||||||
display.v('shutting down control socket, connection was active for %s secs' % delta, self.play_context.remote_addr)
|
self.log('shutting down control socket, connection was active for %s secs' % delta)
|
||||||
try:
|
try:
|
||||||
self.conn.close()
|
self.conn.close()
|
||||||
self.socket.close()
|
self.socket.close()
|
||||||
|
@ -268,8 +275,6 @@ def main():
|
||||||
sys.stderr.write(traceback.format_exc())
|
sys.stderr.write(traceback.format_exc())
|
||||||
sys.exit("FAIL: %s" % e)
|
sys.exit("FAIL: %s" % e)
|
||||||
|
|
||||||
display.verbosity = pc.verbosity
|
|
||||||
|
|
||||||
ssh = connection_loader.get('ssh', class_only=True)
|
ssh = connection_loader.get('ssh', class_only=True)
|
||||||
m = ssh._create_control_path(pc.remote_addr, pc.port, pc.remote_user)
|
m = ssh._create_control_path(pc.remote_addr, pc.port, pc.remote_user)
|
||||||
|
|
||||||
|
@ -290,16 +295,18 @@ def main():
|
||||||
try:
|
try:
|
||||||
server = Server(sf_path, pc)
|
server = Server(sf_path, pc)
|
||||||
except AnsibleConnectionFailure as exc:
|
except AnsibleConnectionFailure as exc:
|
||||||
display.vvv(str(exc), pc.remote_addr)
|
log(str(exc), pc.remote_addr, pc.remote_user)
|
||||||
rc = 1
|
rc = 1
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
display.vvv(traceback.format_exc(), pc.remote_addr)
|
log(traceback.format_exc(), pc.remote_addr, pc.remote_user)
|
||||||
rc = 1
|
rc = 1
|
||||||
fcntl.lockf(lock_fd, fcntl.LOCK_UN)
|
fcntl.lockf(lock_fd, fcntl.LOCK_UN)
|
||||||
os.close(lock_fd)
|
os.close(lock_fd)
|
||||||
if rc == 0:
|
if rc == 0:
|
||||||
server.run()
|
server.run()
|
||||||
sys.exit(rc)
|
sys.exit(rc)
|
||||||
|
else:
|
||||||
|
log('re-using existing socket connection', pc.remote_addr, pc.remote_user)
|
||||||
fcntl.lockf(lock_fd, fcntl.LOCK_UN)
|
fcntl.lockf(lock_fd, fcntl.LOCK_UN)
|
||||||
os.close(lock_fd)
|
os.close(lock_fd)
|
||||||
|
|
||||||
|
@ -324,8 +331,8 @@ def main():
|
||||||
time.sleep(C.PERSISTENT_CONNECT_INTERVAL)
|
time.sleep(C.PERSISTENT_CONNECT_INTERVAL)
|
||||||
attempts += 1
|
attempts += 1
|
||||||
if attempts > C.PERSISTENT_CONNECT_RETRIES:
|
if attempts > C.PERSISTENT_CONNECT_RETRIES:
|
||||||
display.vvvv('number of connection attempts exceeded, unable to connect to control socket')
|
log('number of connection attempts exceeded, unable to connect to control socket', pc.remote_addr, pc.remote_user)
|
||||||
display.vvvv('persistent_connect_interval=%s, persistent_connect_retries=%s' % (C.PERSISTENT_CONNECT_INTERVAL, C.PERSISTENT_CONNECT_RETRIES))
|
log('persistent_connect_interval=%s, persistent_connect_retries=%s' % (C.PERSISTENT_CONNECT_INTERVAL, C.PERSISTENT_CONNECT_RETRIES), pc.remote_addr, pc.remote_user)
|
||||||
sys.stderr.write('failed to connect to control socket')
|
sys.stderr.write('failed to connect to control socket')
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
|
|
||||||
|
@ -350,5 +357,4 @@ def main():
|
||||||
sys.exit(rc)
|
sys.exit(rc)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
display = Display()
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -23,6 +23,7 @@ import re
|
||||||
import socket
|
import socket
|
||||||
import json
|
import json
|
||||||
import signal
|
import signal
|
||||||
|
import logging
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible.errors import AnsibleConnectionFailure, AnsibleError
|
from ansible.errors import AnsibleConnectionFailure, AnsibleError
|
||||||
|
@ -37,11 +38,8 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise AnsibleError("ncclient is not installed")
|
raise AnsibleError("ncclient is not installed")
|
||||||
|
|
||||||
try:
|
logger = logging.getLogger('ansible.netconf')
|
||||||
from __main__ import display
|
logging.getLogger('ncclient').setLevel(logging.INFO)
|
||||||
except ImportError:
|
|
||||||
from ansible.utils.display import Display
|
|
||||||
display = Display()
|
|
||||||
|
|
||||||
class Connection(ConnectionBase):
|
class Connection(ConnectionBase):
|
||||||
''' NetConf connections '''
|
''' NetConf connections '''
|
||||||
|
@ -53,14 +51,20 @@ class Connection(ConnectionBase):
|
||||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||||
|
|
||||||
self._network_os = self._play_context.network_os or 'default'
|
self._network_os = self._play_context.network_os or 'default'
|
||||||
display.vvv('network_os is set to %s' % self._network_os, play_context.remote_addr)
|
self.log('network_os is set to %s' % self._network_os)
|
||||||
|
|
||||||
self._manager = None
|
self._manager = None
|
||||||
self._connected = False
|
self._connected = False
|
||||||
|
|
||||||
|
def log(self, msg):
|
||||||
|
msg = 'h=%s u=%s %s' % (self._play_context.remote_addr, self._play_context.remote_user, msg)
|
||||||
|
logger.debug(msg)
|
||||||
|
|
||||||
def _connect(self):
|
def _connect(self):
|
||||||
super(Connection, self)._connect()
|
super(Connection, self)._connect()
|
||||||
|
|
||||||
|
self.log('ssh connection done, stating ncclient')
|
||||||
|
|
||||||
allow_agent = True
|
allow_agent = True
|
||||||
if self._play_context.password is not None:
|
if self._play_context.password is not None:
|
||||||
allow_agent = False
|
allow_agent = False
|
||||||
|
@ -69,6 +73,9 @@ class Connection(ConnectionBase):
|
||||||
if self._play_context.private_key_file:
|
if self._play_context.private_key_file:
|
||||||
key_filename = os.path.expanduser(self._play_context.private_key_file)
|
key_filename = os.path.expanduser(self._play_context.private_key_file)
|
||||||
|
|
||||||
|
if not self._network_os:
|
||||||
|
raise AnsibleConnectionError('network_os must be set for netconf connections')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._manager = manager.connect(
|
self._manager = manager.connect(
|
||||||
host=self._play_context.remote_addr,
|
host=self._play_context.remote_addr,
|
||||||
|
@ -88,6 +95,8 @@ class Connection(ConnectionBase):
|
||||||
if not self._manager.connected:
|
if not self._manager.connected:
|
||||||
return (1, '', 'not connected')
|
return (1, '', 'not connected')
|
||||||
|
|
||||||
|
self.log('ncclient manager object created successfully')
|
||||||
|
|
||||||
self._connected = True
|
self._connected = True
|
||||||
return (0, self._manager.session_id, '')
|
return (0, self._manager.session_id, '')
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import json
|
||||||
import signal
|
import signal
|
||||||
import datetime
|
import datetime
|
||||||
import traceback
|
import traceback
|
||||||
|
import logging
|
||||||
|
|
||||||
from ansible.errors import AnsibleConnectionFailure
|
from ansible.errors import AnsibleConnectionFailure
|
||||||
from ansible.module_utils.six.moves import StringIO
|
from ansible.module_utils.six.moves import StringIO
|
||||||
|
@ -31,12 +32,7 @@ from ansible.plugins import terminal_loader
|
||||||
from ansible.plugins.connection import ensure_connect
|
from ansible.plugins.connection import ensure_connect
|
||||||
from ansible.plugins.connection.paramiko_ssh import Connection as _Connection
|
from ansible.plugins.connection.paramiko_ssh import Connection as _Connection
|
||||||
|
|
||||||
try:
|
logger = logging.getLogger('ansible.network_cli')
|
||||||
from __main__ import display
|
|
||||||
except ImportError:
|
|
||||||
from ansible.utils.display import Display
|
|
||||||
display = Display()
|
|
||||||
|
|
||||||
|
|
||||||
class Connection(_Connection):
|
class Connection(_Connection):
|
||||||
''' CLI (shell) SSH connections on Paramiko '''
|
''' CLI (shell) SSH connections on Paramiko '''
|
||||||
|
@ -54,9 +50,17 @@ class Connection(_Connection):
|
||||||
self._last_response = None
|
self._last_response = None
|
||||||
self._history = list()
|
self._history = list()
|
||||||
|
|
||||||
|
if play_context.verbosity > 3:
|
||||||
|
logging.getLogger('paramiko').setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
def log(self, msg):
|
||||||
|
msg = 'h=%s u=%s %s' % (self._play_context.remote_addr, self._play_context.remote_user, msg)
|
||||||
|
logger.debug(msg)
|
||||||
|
|
||||||
def update_play_context(self, play_context):
|
def update_play_context(self, play_context):
|
||||||
"""Updates the play context information for the connection"""
|
"""Updates the play context information for the connection"""
|
||||||
display.vvvv('updating play_context for connection', self._play_context.remote_addr)
|
|
||||||
|
self.log('updating play_context for connection')
|
||||||
|
|
||||||
if self._play_context.become is False and play_context.become is True:
|
if self._play_context.become is False and play_context.become is True:
|
||||||
auth_pass = play_context.become_pass
|
auth_pass = play_context.become_pass
|
||||||
|
@ -71,7 +75,7 @@ class Connection(_Connection):
|
||||||
"""Connections to the device and sets the terminal type"""
|
"""Connections to the device and sets the terminal type"""
|
||||||
super(Connection, self)._connect()
|
super(Connection, self)._connect()
|
||||||
|
|
||||||
display.vvvv('ssh connection done, setting terminal', self._play_context.remote_addr)
|
self.log('ssh connection done, setting terminal')
|
||||||
|
|
||||||
network_os = self._play_context.network_os
|
network_os = self._play_context.network_os
|
||||||
if not network_os:
|
if not network_os:
|
||||||
|
@ -85,11 +89,11 @@ class Connection(_Connection):
|
||||||
raise AnsibleConnectionFailure('network os %s is not supported' % network_os)
|
raise AnsibleConnectionFailure('network os %s is not supported' % network_os)
|
||||||
|
|
||||||
self._connected = True
|
self._connected = True
|
||||||
display.vvvv('ssh connection has completed successfully', self._play_context.remote_addr)
|
self.log('ssh connection has completed successfully')
|
||||||
|
|
||||||
@ensure_connect
|
@ensure_connect
|
||||||
def open_shell(self):
|
def open_shell(self):
|
||||||
display.vvvv('attempting to open shell to device', self._play_context.remote_addr)
|
self.log('attempting to open shell to device')
|
||||||
self._shell = self.ssh.invoke_shell()
|
self._shell = self.ssh.invoke_shell()
|
||||||
self._shell.settimeout(self._play_context.timeout)
|
self._shell.settimeout(self._play_context.timeout)
|
||||||
|
|
||||||
|
@ -102,18 +106,18 @@ class Connection(_Connection):
|
||||||
auth_pass = self._play_context.become_pass
|
auth_pass = self._play_context.become_pass
|
||||||
self._terminal.on_authorize(passwd=auth_pass)
|
self._terminal.on_authorize(passwd=auth_pass)
|
||||||
|
|
||||||
display.vvvv('shell successfully opened', self._play_context.remote_addr)
|
self.log('shell successfully opened')
|
||||||
return (0, 'ok', '')
|
return (0, 'ok', '')
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
display.vvvv('closing connection', self._play_context.remote_addr)
|
self.log('closing connection')
|
||||||
self.close_shell()
|
self.close_shell()
|
||||||
super(Connection, self).close()
|
super(Connection, self).close()
|
||||||
self._connected = False
|
self._connected = False
|
||||||
|
|
||||||
def close_shell(self):
|
def close_shell(self):
|
||||||
"""Closes the vty shell if the device supports multiplexing"""
|
"""Closes the vty shell if the device supports multiplexing"""
|
||||||
display.vvvv('closing shell on device', self._play_context.remote_addr)
|
self.log('closing shell on device')
|
||||||
if self._shell:
|
if self._shell:
|
||||||
self._terminal.on_close_shell()
|
self._terminal.on_close_shell()
|
||||||
|
|
||||||
|
@ -157,7 +161,7 @@ class Connection(_Connection):
|
||||||
return
|
return
|
||||||
return self.receive(obj)
|
return self.receive(obj)
|
||||||
except (socket.timeout, AttributeError) as exc:
|
except (socket.timeout, AttributeError) as exc:
|
||||||
display.vvv(traceback.format_exc())
|
self.log(traceback.format_exc())
|
||||||
raise AnsibleConnectionFailure("timeout trying to send command: %s" % command.strip())
|
raise AnsibleConnectionFailure("timeout trying to send command: %s" % command.strip())
|
||||||
|
|
||||||
def _strip(self, data):
|
def _strip(self, data):
|
||||||
|
@ -209,7 +213,7 @@ class Connection(_Connection):
|
||||||
|
|
||||||
def alarm_handler(self, signum, frame):
|
def alarm_handler(self, signum, frame):
|
||||||
"""Alarm handler raised in case of command timeout """
|
"""Alarm handler raised in case of command timeout """
|
||||||
display.vvvv('closing shell due to sigalarm', self._play_context.remote_addr)
|
self.log('closing shell due to sigalarm')
|
||||||
self.close_shell()
|
self.close_shell()
|
||||||
|
|
||||||
def exec_command(self, cmd):
|
def exec_command(self, cmd):
|
||||||
|
@ -241,8 +245,6 @@ class Connection(_Connection):
|
||||||
return self.open_shell()
|
return self.open_shell()
|
||||||
elif obj['command'] == 'prompt()':
|
elif obj['command'] == 'prompt()':
|
||||||
return (0, self._matched_prompt, '')
|
return (0, self._matched_prompt, '')
|
||||||
elif obj['command'] == 'history()':
|
|
||||||
return (0, self._history, '')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self._shell is None:
|
if self._shell is None:
|
||||||
|
@ -252,7 +254,6 @@ class Connection(_Connection):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not signal.getsignal(signal.SIGALRM):
|
if not signal.getsignal(signal.SIGALRM):
|
||||||
display.debug('setting alarm handler in network_cli')
|
|
||||||
signal.signal(signal.SIGALRM, self.alarm_handler)
|
signal.signal(signal.SIGALRM, self.alarm_handler)
|
||||||
signal.alarm(self._play_context.timeout)
|
signal.alarm(self._play_context.timeout)
|
||||||
out = self.send(obj)
|
out = self.send(obj)
|
||||||
|
|
Loading…
Reference in a new issue