diff --git a/lib/ansible/plugins/netconf/__init__.py b/lib/ansible/plugins/netconf/__init__.py index 4920b4fbbb0..b55bafbb121 100644 --- a/lib/ansible/plugins/netconf/__init__.py +++ b/lib/ansible/plugins/netconf/__init__.py @@ -24,13 +24,17 @@ from functools import wraps from ansible.errors import AnsibleError from ansible.plugins import AnsiblePlugin - +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib try: from ncclient.operations import RPCError from ncclient.xml_ import to_xml, to_ele -except ImportError: - raise AnsibleError("ncclient is not installed") + HAS_NCCLIENT = True + NCCLIENT_IMP_ERR = None +except (ImportError, AttributeError) as err: # paramiko and gssapi are incompatible and raise AttributeError not ImportError + HAS_NCCLIENT = False + NCCLIENT_IMP_ERR = err try: from lxml.etree import Element, SubElement, tostring, fromstring @@ -47,6 +51,15 @@ def ensure_connected(func): return wrapped +def ensure_ncclient(func): + @wraps(func) + def wrapped(self, *args, **kwargs): + if not HAS_NCCLIENT: + raise AnsibleError("%s: %s" % (missing_required_lib('ncclient'), to_native(NCCLIENT_IMP_ERR))) + return func(self, *args, **kwargs) + return wrapped + + class NetconfBase(AnsiblePlugin): """ A base class for implementing Netconf connections @@ -107,6 +120,7 @@ class NetconfBase(AnsiblePlugin): def m(self): return self._connection._manager + @ensure_ncclient @ensure_connected def rpc(self, name): """ diff --git a/lib/ansible/plugins/netconf/ce.py b/lib/ansible/plugins/netconf/ce.py index ef41b54994c..f75465a4880 100644 --- a/lib/ansible/plugins/netconf/ce.py +++ b/lib/ansible/plugins/netconf/ce.py @@ -22,29 +22,31 @@ __metaclass__ = type import json import re -from ansible import constants as C from ansible.module_utils._text import to_text, to_bytes, to_native -from ansible.errors import AnsibleConnectionFailure, AnsibleError +from ansible.errors import AnsibleConnectionFailure from ansible.plugins.netconf import NetconfBase -from ansible.plugins.netconf import ensure_connected +from ansible.plugins.netconf import ensure_connected, ensure_ncclient 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") + HAS_NCCLIENT = True +except (ImportError, AttributeError): # paramiko and gssapi are incompatible and raise AttributeError not ImportError + HAS_NCCLIENT = False class Netconf(NetconfBase): + @ensure_ncclient def get_text(self, ele, tag): try: return to_text(ele.find(tag).text, errors='surrogate_then_replace').strip() except AttributeError: pass + @ensure_ncclient def get_device_info(self): device_info = dict() device_info['network_os'] = 'ce' @@ -65,6 +67,7 @@ class Netconf(NetconfBase): :name: Name of rpc in string format""" return self.rpc(name) + @ensure_ncclient @ensure_connected def load_configuration(self, *args, **kwargs): """Loads given configuration on device @@ -95,8 +98,8 @@ class Netconf(NetconfBase): return json.dumps(result) @staticmethod + @ensure_ncclient def guess_network_os(obj): - try: m = manager.connect( host=obj._play_context.remote_addr, @@ -135,6 +138,7 @@ class Netconf(NetconfBase): :rollback: rollback id""" return self.m.compare_configuration(*args, **kwargs).data_xml + @ensure_ncclient @ensure_connected def execute_action(self, xml_str): """huawei execute-action""" @@ -156,11 +160,7 @@ class Netconf(NetconfBase): """reboot the device""" return self.m.reboot().data_xml - @ensure_connected - def halt(self): - """reboot the device""" - return self.m.halt().data_xml - + @ensure_ncclient @ensure_connected def get(self, *args, **kwargs): try: @@ -168,6 +168,7 @@ class Netconf(NetconfBase): except RPCError as exc: raise Exception(to_xml(exc.xml)) + @ensure_ncclient @ensure_connected def get_config(self, *args, **kwargs): try: @@ -175,6 +176,7 @@ class Netconf(NetconfBase): except RPCError as exc: raise Exception(to_xml(exc.xml)) + @ensure_ncclient @ensure_connected def edit_config(self, *args, **kwargs): try: @@ -182,6 +184,7 @@ class Netconf(NetconfBase): except RPCError as exc: raise Exception(to_xml(exc.xml)) + @ensure_ncclient @ensure_connected def execute_nc_cli(self, *args, **kwargs): try: @@ -189,6 +192,7 @@ class Netconf(NetconfBase): except RPCError as exc: raise Exception(to_xml(exc.xml)) + @ensure_ncclient @ensure_connected def commit(self, *args, **kwargs): try: @@ -203,9 +207,3 @@ class Netconf(NetconfBase): @ensure_connected def discard_changes(self, *args, **kwargs): return self.m.discard_changes(*args, **kwargs).data_xml - - @ensure_connected - def execute_rpc(self, name): - """RPC to be execute on remote device - :name: Name of rpc in string format""" - return self.rpc(name) diff --git a/lib/ansible/plugins/netconf/iosxr.py b/lib/ansible/plugins/netconf/iosxr.py index d80f0dbeeaf..d7bf7af07dc 100644 --- a/lib/ansible/plugins/netconf/iosxr.py +++ b/lib/ansible/plugins/netconf/iosxr.py @@ -24,30 +24,24 @@ import json import re import collections -from ansible import constants as C from ansible.module_utils._text import to_native from ansible.module_utils.network.common.netconf import remove_namespaces from ansible.module_utils.network.iosxr.iosxr import build_xml, etree_find -from ansible.errors import AnsibleConnectionFailure, AnsibleError +from ansible.errors import AnsibleConnectionFailure from ansible.plugins.netconf import NetconfBase -from ansible.plugins.netconf import ensure_connected +from ansible.plugins.netconf import ensure_connected, ensure_ncclient 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") - -try: - from lxml import etree -except ImportError: - raise AnsibleError("lxml is not installed") + from ncclient.xml_ import to_xml + HAS_NCCLIENT = True +except (ImportError, AttributeError): # paramiko and gssapi are incompatible and raise AttributeError not ImportError + HAS_NCCLIENT = False class Netconf(NetconfBase): - @ensure_connected def get_device_info(self): device_info = {} @@ -93,6 +87,7 @@ class Netconf(NetconfBase): return json.dumps(result) @staticmethod + @ensure_ncclient def guess_network_os(obj): """ Guess the remote network os name @@ -124,6 +119,7 @@ class Netconf(NetconfBase): return guessed_os # TODO: change .xml to .data_xml, when ncclient supports data_xml on all platforms + @ensure_ncclient @ensure_connected def get(self, filter=None, remove_ns=False): if isinstance(filter, list): @@ -138,6 +134,7 @@ class Netconf(NetconfBase): except RPCError as exc: raise Exception(to_xml(exc.xml)) + @ensure_ncclient @ensure_connected def get_config(self, source=None, filter=None, remove_ns=False): if isinstance(filter, list): @@ -152,6 +149,7 @@ class Netconf(NetconfBase): except RPCError as exc: raise Exception(to_xml(exc.xml)) + @ensure_ncclient @ensure_connected def edit_config(self, config=None, format='xml', target='candidate', default_operation=None, test_option=None, error_option=None, remove_ns=False): if config is None: @@ -167,6 +165,7 @@ class Netconf(NetconfBase): except RPCError as exc: raise Exception(to_xml(exc.xml)) + @ensure_ncclient @ensure_connected def commit(self, confirmed=False, timeout=None, persist=None, remove_ns=False): try: @@ -179,6 +178,7 @@ class Netconf(NetconfBase): except RPCError as exc: raise Exception(to_xml(exc.xml)) + @ensure_ncclient @ensure_connected def validate(self, source="candidate", remove_ns=False): try: @@ -191,6 +191,7 @@ class Netconf(NetconfBase): except RPCError as exc: raise Exception(to_xml(exc.xml)) + @ensure_ncclient @ensure_connected def discard_changes(self, remove_ns=False): try: diff --git a/lib/ansible/plugins/netconf/junos.py b/lib/ansible/plugins/netconf/junos.py index 55ca60fb995..830035d37a2 100644 --- a/lib/ansible/plugins/netconf/junos.py +++ b/lib/ansible/plugins/netconf/junos.py @@ -22,29 +22,29 @@ __metaclass__ = type import json import re -from ansible import constants as C -from ansible.module_utils._text import to_text, to_bytes, to_native -from ansible.errors import AnsibleConnectionFailure, AnsibleError +from ansible.module_utils._text import to_text, to_native +from ansible.errors import AnsibleConnectionFailure from ansible.plugins.netconf import NetconfBase -from ansible.plugins.netconf import ensure_connected +from ansible.plugins.netconf import ensure_connected, ensure_ncclient 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, sub_ele -except ImportError: - raise AnsibleError("ncclient is not installed") + HAS_NCCLIENT = True +except (ImportError, AttributeError): # paramiko and gssapi are incompatible and raise AttributeError not ImportError + HAS_NCCLIENT = False class Netconf(NetconfBase): - def get_text(self, ele, tag): try: return to_text(ele.find(tag).text, errors='surrogate_then_replace').strip() except AttributeError: pass + @ensure_ncclient def get_device_info(self): device_info = dict() device_info['network_os'] = 'junos' @@ -68,6 +68,7 @@ class Netconf(NetconfBase): """ return self.rpc(name) + @ensure_ncclient @ensure_connected def load_configuration(self, format='xml', action='merge', target='candidate', config=None): """ @@ -101,6 +102,7 @@ class Netconf(NetconfBase): return json.dumps(result) @staticmethod + @ensure_ncclient def guess_network_os(obj): """ Guess the remote network os name @@ -165,6 +167,7 @@ class Netconf(NetconfBase): # below commit() is a workaround which build's raw `commit-configuration` xml with required tags and uses # ncclient generic rpc() method to execute rpc on remote host. # Remove below method after the issue in ncclient is fixed. + @ensure_ncclient @ensure_connected def commit(self, confirmed=False, check=False, timeout=None, comment=None, synchronize=False, at_time=None): """ diff --git a/lib/ansible/plugins/netconf/sros.py b/lib/ansible/plugins/netconf/sros.py index c4504ca398c..68d879f32da 100644 --- a/lib/ansible/plugins/netconf/sros.py +++ b/lib/ansible/plugins/netconf/sros.py @@ -22,24 +22,18 @@ __metaclass__ = type import json import re -from ansible import constants as C -from ansible.module_utils._text import to_text, to_bytes, to_native -from ansible.errors import AnsibleConnectionFailure, AnsibleError +from ansible.module_utils._text import to_text, to_native +from ansible.errors import AnsibleConnectionFailure from ansible.plugins.netconf import NetconfBase -from ansible.plugins.netconf import ensure_connected +from ansible.plugins.netconf import ensure_ncclient 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") - -try: - from lxml import etree -except ImportError: - raise AnsibleError("lxml is not installed") + from ncclient.xml_ import to_ele + HAS_NCCLIENT = True +except (ImportError, AttributeError): # paramiko and gssapi are incompatible and raise AttributeError not ImportError + HAS_NCCLIENT = False class Netconf(NetconfBase): @@ -49,6 +43,7 @@ class Netconf(NetconfBase): except AttributeError: pass + @ensure_ncclient def get_device_info(self): device_info = dict() device_info['network_os'] = 'sros' @@ -75,6 +70,7 @@ class Netconf(NetconfBase): return json.dumps(result) @staticmethod + @ensure_ncclient def guess_network_os(obj): try: m = manager.connect(