From 3bd8dbb53d2cbf498c229248eb5b9a050024ffb7 Mon Sep 17 00:00:00 2001 From: Ganesh Nalawade Date: Wed, 28 Jun 2017 11:07:38 +0530 Subject: [PATCH] Add support for netconf network discovery (#25435) * Add support for netconf network discovery * Fix unit test failure --- lib/ansible/plugins/connection/netconf.py | 32 +++++++++------ lib/ansible/plugins/netconf/__init__.py | 7 ++++ lib/ansible/plugins/netconf/junos.py | 39 ++++++++++++++++++- test/units/plugins/connection/test_netconf.py | 1 + 4 files changed, 65 insertions(+), 14 deletions(-) diff --git a/lib/ansible/plugins/connection/netconf.py b/lib/ansible/plugins/connection/netconf.py index 0c9f4259c87..e1762828877 100644 --- a/lib/ansible/plugins/connection/netconf.py +++ b/lib/ansible/plugins/connection/netconf.py @@ -66,16 +66,24 @@ class Connection(Rpc, ConnectionBase): display.display('ssh connection done, stating ncclient', log_only=True) - allow_agent = True + self.allow_agent = True if self._play_context.password is not None: - allow_agent = False + self.allow_agent = False - key_filename = None + self.key_filename = None if self._play_context.private_key_file: - key_filename = os.path.expanduser(self._play_context.private_key_file) + self.key_filename = os.path.expanduser(self._play_context.private_key_file) - if not self._network_os: - raise AnsibleConnectionFailure('network_os must be set for netconf connections') + network_os = self._play_context.network_os + + if not network_os: + 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) + + if not network_os: + raise AnsibleConnectionFailure('Unable to automatically determine host network os. Please ansible_network_os value') try: self._manager = manager.connect( @@ -83,12 +91,12 @@ class Connection(Rpc, ConnectionBase): port=self._play_context.port or 830, username=self._play_context.remote_user, password=self._play_context.password, - key_filename=str(key_filename), + key_filename=str(self.key_filename), hostkey_verify=C.HOST_KEY_CHECKING, look_for_keys=C.PARAMIKO_LOOK_FOR_KEYS, - allow_agent=allow_agent, + allow_agent=self.allow_agent, timeout=self._play_context.timeout, - device_params={'name': self._network_os} + device_params={'name': network_os} ) except SSHUnknownHostError as exc: raise AnsibleConnectionFailure(str(exc)) @@ -100,12 +108,12 @@ class Connection(Rpc, ConnectionBase): self._connected = True - self._netconf = netconf_loader.get(self._network_os, self) + self._netconf = netconf_loader.get(network_os, self) if self._netconf: self._rpc.add(self._netconf) - display.display('loaded netconf plugin for network_os %s' % self._network_os, log_only=True) + display.display('loaded netconf plugin for network_os %s' % network_os, log_only=True) else: - display.display('unable to load netconf for network_os %s' % self._network_os) + display.display('unable to load netconf for network_os %s' % network_os) return 0, to_bytes(self._manager.session_id, errors='surrogate_or_strict'), b'' diff --git a/lib/ansible/plugins/netconf/__init__.py b/lib/ansible/plugins/netconf/__init__.py index ce7680f44a0..add9a036e21 100644 --- a/lib/ansible/plugins/netconf/__init__.py +++ b/lib/ansible/plugins/netconf/__init__.py @@ -176,6 +176,13 @@ class NetconfBase(with_metaclass(ABCMeta, object)): """ pass + @staticmethod + def guess_network_os(obj): + """Identifies the operating system of + network device. + """ + pass + def get_base_rpc(self): """Returns list of base rpc method supported by remote device""" return ['get_config', 'edit_config', 'get_capabilities', 'get'] diff --git a/lib/ansible/plugins/netconf/junos.py b/lib/ansible/plugins/netconf/junos.py index 54c020c6cc9..758360eaee2 100644 --- a/lib/ansible/plugins/netconf/junos.py +++ b/lib/ansible/plugins/netconf/junos.py @@ -20,14 +20,23 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type import json +import re from xml.etree.ElementTree import fromstring -from ansible.module_utils._text import to_bytes, to_text +from ansible import constants as C +from ansible.module_utils._text import to_text +from ansible.errors import AnsibleConnectionFailure, AnsibleError from ansible.plugins.netconf import NetconfBase from ansible.plugins.netconf import ensure_connected -from ncclient.xml_ import new_ele +try: + from ncclient import manager + from ncclient.operations import RPCError + from ncclient.transport.errors import SSHUnknownHostError + from ncclient.xml_ import to_ele, to_xml, new_ele +except ImportError: + raise AnsibleError("ncclient is not installed") class Netconf(NetconfBase): @@ -77,3 +86,29 @@ class Netconf(NetconfBase): result['client_capabilities'] = [c for c in self.m.client_capabilities] result['session_id'] = self.m.session_id return json.dumps(result) + + @staticmethod + def guess_network_os(obj): + + try: + m = manager.connect( + host=obj._play_context.remote_addr, + port=obj._play_context.port or 830, + username=obj._play_context.remote_user, + password=obj._play_context.password, + key_filename=str(obj.key_filename), + hostkey_verify=C.HOST_KEY_CHECKING, + look_for_keys=C.PARAMIKO_LOOK_FOR_KEYS, + allow_agent=obj.allow_agent, + timeout=obj._play_context.timeout + ) + except SSHUnknownHostError as exc: + raise AnsibleConnectionFailure(str(exc)) + + guessed_os = None + for c in m.server_capabilities: + if re.search('junos', c): + guessed_os = 'junos' + + m.close_session() + return guessed_os diff --git a/test/units/plugins/connection/test_netconf.py b/test/units/plugins/connection/test_netconf.py index 4e924bd92f6..9552245a2bc 100644 --- a/test/units/plugins/connection/test_netconf.py +++ b/test/units/plugins/connection/test_netconf.py @@ -72,6 +72,7 @@ class TestNetconfConnectionClass(unittest.TestCase): mock_manager = MagicMock(name='self._manager.connect') type(mock_manager).session_id = PropertyMock(return_value='123456789') netconf.manager.connect.return_value = mock_manager + conn._play_context.network_os = 'default' rc, out, err = conn._connect()