Base connection class for network-style connections (#41839)

* Create base class for network-style connections

* clean up some differences

* Move NetworkConnectionBase

* Tweak netconf for tests

* Tweak when network_os is checked to avoid failing tests

* Pull back exec_command
This commit is contained in:
Nathaniel Case 2018-07-02 08:41:00 -04:00 committed by GitHub
parent 380d08af5d
commit 5850591d14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 198 additions and 253 deletions

View file

@ -5,7 +5,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
---
author: Ansible Networking Team
@ -101,6 +100,32 @@ options:
key: host_key_auto_add
env:
- name: ANSIBLE_HOST_KEY_AUTO_ADD
look_for_keys:
default: True
description: 'TODO: write it'
env:
- name: ANSIBLE_PARAMIKO_LOOK_FOR_KEYS
ini:
- section: paramiko_connection
key: look_for_keys
type: boolean
host_key_checking:
description: 'Set this to "False" if you want to avoid host key checking by the underlying tools Ansible uses to connect to the host'
type: boolean
default: True
env:
- name: ANSIBLE_HOST_KEY_CHECKING
- name: ANSIBLE_SSH_HOST_KEY_CHECKING
- name: ANSIBLE_NETCONF_HOST_KEY_CHECKING
ini:
- section: defaults
key: host_key_checking
- section: paramiko_connection
key: host_key_checking
vars:
- name: ansible_host_key_checking
- name: ansible_ssh_host_key_checking
- name: ansible_netconf_host_key_checking
persistent_connect_timeout:
type: int
description:
@ -110,8 +135,8 @@ options:
will fail
default: 30
ini:
section: persistent_connection
key: persistent_connect_timeout
- section: persistent_connection
key: connect_timeout
env:
- name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT
persistent_command_timeout:
@ -123,8 +148,8 @@ options:
close
default: 10
ini:
section: persistent_connection
key: persistent_command_timeout
- section: persistent_connection
key: command_timeout
env:
- name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT
"""
@ -133,13 +158,11 @@ import os
import logging
import json
from ansible import constants as C
from ansible.errors import AnsibleConnectionFailure, AnsibleError
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
from ansible.plugins.loader import netconf_loader
from ansible.plugins.connection import ConnectionBase, ensure_connect
from ansible.plugins.connection.local import Connection as LocalConnection
from ansible.plugins.connection import NetworkConnectionBase
try:
from ncclient import manager
@ -165,36 +188,21 @@ NETWORK_OS_DEVICE_PARAM_MAP = {
}
class Connection(ConnectionBase):
class Connection(NetworkConnectionBase):
"""NetConf connections"""
transport = 'netconf'
has_pipelining = False
force_persistence = True
# Do not use _remote_is_local in other connections
_remote_is_local = True
def __init__(self, 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._network_os or 'default'
display.display('network_os is set to %s' % self._network_os, log_only=True)
self._netconf = None
self._manager = None
self._connected = False
self._local = LocalConnection(play_context, new_stdin, *args, **kwargs)
def __getattr__(self, name):
try:
return self.__dict__[name]
except KeyError:
if name.startswith('_'):
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))
return getattr(self._netconf, name)
def exec_command(self, request, in_data=None, sudoable=True):
def exec_command(self, cmd, in_data=None, sudoable=True):
"""Sends the request to the node and returns the reply
The method accepts two forms of request. The first form is as a byte
string that represents xml string be send over netconf session.
@ -202,7 +210,7 @@ class Connection(ConnectionBase):
"""
if self._manager:
# to_ele operates on native strings
request = to_ele(to_native(request, errors='surrogate_or_strict'))
request = to_ele(to_native(cmd, errors='surrogate_or_strict'))
if request is None:
return 'unable to parse request'
@ -215,15 +223,7 @@ class Connection(ConnectionBase):
return reply.data_xml
else:
return self._local.exec_command(request, in_data, sudoable)
def put_file(self, in_path, out_path):
"""Transfer a file from local to remote"""
return self._local.put_file(in_path, out_path)
def fetch_file(self, in_path, out_path):
"""Fetch a file from remote to local"""
return self._local.fetch_file(in_path, out_path)
return super(Connection, self).exec_command(cmd, in_data, sudoable)
def _connect(self):
super(Connection, self)._connect()
@ -239,15 +239,14 @@ class Connection(ConnectionBase):
if self._play_context.private_key_file:
key_filename = os.path.expanduser(self._play_context.private_key_file)
network_os = self._play_context.network_os
if not network_os:
if self._network_os == 'default':
for cls in netconf_loader.all(class_only=True):
network_os = cls.guess_network_os(self)
if network_os:
display.display('discovered network_os %s' % network_os, log_only=True)
self._network_os = network_os
device_params = {'name': (NETWORK_OS_DEVICE_PARAM_MAP.get(network_os) or network_os or 'default')}
device_params = {'name': NETWORK_OS_DEVICE_PARAM_MAP.get(self._network_os) or self._network_os}
ssh_config = os.getenv('ANSIBLE_NETCONF_SSH_CONFIG', False)
if ssh_config in BOOLEANS_TRUE:
@ -262,8 +261,8 @@ class Connection(ConnectionBase):
username=self._play_context.remote_user,
password=self._play_context.password,
key_filename=str(key_filename),
hostkey_verify=C.HOST_KEY_CHECKING,
look_for_keys=C.PARAMIKO_LOOK_FOR_KEYS,
hostkey_verify=self.get_option('host_key_checking'),
look_for_keys=self.get_option('look_for_keys'),
device_params=device_params,
allow_agent=self._play_context.allow_agent,
timeout=self._play_context.timeout,
@ -272,7 +271,7 @@ class Connection(ConnectionBase):
except SSHUnknownHostError as exc:
raise AnsibleConnectionFailure(str(exc))
except ImportError as exc:
raise AnsibleError("connection=netconf is not supported on {0}".format(network_os))
raise AnsibleError("connection=netconf is not supported on {0}".format(self._network_os))
if not self._manager.connected:
return 1, b'', b'not connected'
@ -281,26 +280,17 @@ class Connection(ConnectionBase):
self._connected = True
self._netconf = netconf_loader.get(network_os, self)
if self._netconf:
display.display('loaded netconf plugin for network_os %s' % network_os, log_only=True)
netconf = netconf_loader.get(self._network_os, self)
if netconf:
display.display('loaded netconf plugin for network_os %s' % self._network_os, log_only=True)
else:
self._netconf = netconf_loader.get("default", self)
display.display('unable to load netconf plugin for network_os %s, falling back to default plugin' % network_os)
netconf = netconf_loader.get("default", self)
display.display('unable to load netconf plugin for network_os %s, falling back to default plugin' % self._network_os)
self._implementation_plugins.append(netconf)
return 0, to_bytes(self._manager.session_id, errors='surrogate_or_strict'), b''
def reset(self):
'''
Reset the connection
'''
if self._socket_path:
display.vvvv('resetting persistent connection for socket_path %s' % self._socket_path, host=self._play_context.remote_addr)
self.close()
display.vvvv('reset call on connection instance', host=self._play_context.remote_addr)
def close(self):
if self._manager:
self._manager.close_session()
self._connected = False
super(Connection, self).close()