Fix netconf post "delay persistent connections" (#63463)

* ensure_connect on manager use

* Remove ensure_connected from individual netconf plugins
This commit is contained in:
Nathaniel Case 2019-10-17 11:22:48 -04:00 committed by GitHub
parent 7da37e58de
commit f5e0995cae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 15 additions and 74 deletions

View file

@ -186,9 +186,10 @@ import json
from ansible.errors import AnsibleConnectionFailure, AnsibleError from ansible.errors import AnsibleConnectionFailure, AnsibleError
from ansible.module_utils._text import to_bytes, to_native, to_text from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE, BOOLEANS_FALSE from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE, BOOLEANS_FALSE
from ansible.plugins.loader import netconf_loader from ansible.plugins.loader import netconf_loader
from ansible.plugins.connection import NetworkConnectionBase from ansible.plugins.connection import NetworkConnectionBase, ensure_connect
try: try:
from ncclient import manager from ncclient import manager
@ -262,12 +263,14 @@ class Connection(NetworkConnectionBase):
else: else:
return super(Connection, self).exec_command(cmd, in_data, sudoable) return super(Connection, self).exec_command(cmd, in_data, sudoable)
@property
@ensure_connect
def manager(self):
return self._manager
def _connect(self): def _connect(self):
if not HAS_NCCLIENT: if not HAS_NCCLIENT:
raise AnsibleError( raise AnsibleError("%s: %s" % (missing_required_lib("ncclient"), to_native(NCCLIENT_IMP_ERR)))
'The required "ncclient" python library is required to use the netconf connection type: %s.\n'
'Please run pip install ncclient' % to_native(NCCLIENT_IMP_ERR)
)
self.queue_message('log', 'ssh connection done, starting ncclient') self.queue_message('log', 'ssh connection done, starting ncclient')

View file

@ -42,15 +42,6 @@ except ImportError:
from xml.etree.ElementTree import Element, SubElement, tostring, fromstring from xml.etree.ElementTree import Element, SubElement, tostring, fromstring
def ensure_connected(func):
@wraps(func)
def wrapped(self, *args, **kwargs):
if not self._connection._connected:
self._connection._connect()
return func(self, *args, **kwargs)
return wrapped
def ensure_ncclient(func): def ensure_ncclient(func):
@wraps(func) @wraps(func)
def wrapped(self, *args, **kwargs): def wrapped(self, *args, **kwargs):
@ -118,10 +109,8 @@ class NetconfBase(AnsiblePlugin):
@property @property
def m(self): def m(self):
return self._connection._manager return self._connection.manager
@ensure_ncclient
@ensure_connected
def rpc(self, name): def rpc(self, name):
""" """
RPC to be execute on remote device RPC to be execute on remote device
@ -136,7 +125,6 @@ class NetconfBase(AnsiblePlugin):
msg = exc.xml msg = exc.xml
raise Exception(to_xml(msg)) raise Exception(to_xml(msg))
@ensure_connected
def get_config(self, source=None, filter=None): def get_config(self, source=None, filter=None):
""" """
Retrieve all or part of a specified configuration Retrieve all or part of a specified configuration
@ -153,7 +141,6 @@ class NetconfBase(AnsiblePlugin):
resp = self.m.get_config(source=source, filter=filter) resp = self.m.get_config(source=source, filter=filter)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def get(self, filter=None, with_defaults=None): def get(self, filter=None, with_defaults=None):
""" """
Retrieve device configuration and state information. Retrieve device configuration and state information.
@ -169,7 +156,6 @@ class NetconfBase(AnsiblePlugin):
response = resp.data_xml if hasattr(resp, 'data_xml') else resp.xml response = resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
return response return response
@ensure_connected
def edit_config(self, config=None, format='xml', target='candidate', default_operation=None, test_option=None, error_option=None): def edit_config(self, config=None, format='xml', target='candidate', default_operation=None, test_option=None, error_option=None):
""" """
Loads all or part of the specified *config* to the *target* configuration datastore. Loads all or part of the specified *config* to the *target* configuration datastore.
@ -189,7 +175,6 @@ class NetconfBase(AnsiblePlugin):
error_option=error_option) error_option=error_option)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def validate(self, source='candidate'): def validate(self, source='candidate'):
""" """
Validate the contents of the specified configuration. Validate the contents of the specified configuration.
@ -200,7 +185,6 @@ class NetconfBase(AnsiblePlugin):
resp = self.m.validate(source=source) resp = self.m.validate(source=source)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def copy_config(self, source, target): def copy_config(self, source, target):
""" """
Create or replace an entire configuration datastore with the contents of another complete configuration datastore. Create or replace an entire configuration datastore with the contents of another complete configuration datastore.
@ -212,7 +196,6 @@ class NetconfBase(AnsiblePlugin):
resp = self.m.copy_config(source, target) resp = self.m.copy_config(source, target)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def dispatch(self, rpc_command=None, source=None, filter=None): def dispatch(self, rpc_command=None, source=None, filter=None):
""" """
Execute rpc on the remote device eg. dispatch('clear-arp-table') Execute rpc on the remote device eg. dispatch('clear-arp-table')
@ -240,7 +223,6 @@ class NetconfBase(AnsiblePlugin):
return result return result
@ensure_connected
def lock(self, target="candidate"): def lock(self, target="candidate"):
""" """
Allows the client to lock the configuration system of a device. Allows the client to lock the configuration system of a device.
@ -251,7 +233,6 @@ class NetconfBase(AnsiblePlugin):
resp = self.m.lock(target=target) resp = self.m.lock(target=target)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def unlock(self, target="candidate"): def unlock(self, target="candidate"):
""" """
Release a configuration lock, previously obtained with the lock operation. Release a configuration lock, previously obtained with the lock operation.
@ -262,7 +243,6 @@ class NetconfBase(AnsiblePlugin):
resp = self.m.unlock(target=target) resp = self.m.unlock(target=target)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def discard_changes(self): def discard_changes(self):
""" """
Revert the candidate configuration to the currently running configuration. Revert the candidate configuration to the currently running configuration.
@ -272,7 +252,6 @@ class NetconfBase(AnsiblePlugin):
resp = self.m.discard_changes() resp = self.m.discard_changes()
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def commit(self, confirmed=False, timeout=None, persist=None): def commit(self, confirmed=False, timeout=None, persist=None):
""" """
Commit the candidate configuration as the device's new current configuration. Commit the candidate configuration as the device's new current configuration.
@ -291,7 +270,6 @@ class NetconfBase(AnsiblePlugin):
resp = self.m.commit(confirmed=confirmed, timeout=timeout, persist=persist) resp = self.m.commit(confirmed=confirmed, timeout=timeout, persist=persist)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def get_schema(self, identifier=None, version=None, format=None): def get_schema(self, identifier=None, version=None, format=None):
""" """
Retrieve a named schema, with optional revision and type. Retrieve a named schema, with optional revision and type.
@ -303,7 +281,6 @@ class NetconfBase(AnsiblePlugin):
resp = self.m.get_schema(identifier, version=version, format=format) resp = self.m.get_schema(identifier, version=version, format=format)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def delete_config(self, target): def delete_config(self, target):
""" """
delete a configuration datastore delete a configuration datastore
@ -313,7 +290,6 @@ class NetconfBase(AnsiblePlugin):
resp = self.m.delete_config(target) resp = self.m.delete_config(target)
return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml return resp.data_xml if hasattr(resp, 'data_xml') else resp.xml
@ensure_connected
def locked(self, target): def locked(self, target):
return self.m.locked(target) return self.m.locked(target)
@ -387,7 +363,7 @@ class NetconfBase(AnsiblePlugin):
if operations['supports_startup']: if operations['supports_startup']:
operations['lock_datastore'].append('startup') operations['lock_datastore'].append('startup')
operations['supports_lock'] = True if len(operations['lock_datastore']) else False operations['supports_lock'] = bool(operations['lock_datastore'])
return operations return operations

View file

@ -22,10 +22,9 @@ __metaclass__ = type
import json import json
import re import re
from ansible.module_utils._text import to_text, to_bytes, to_native from ansible.module_utils._text import to_text, to_bytes
from ansible.errors import AnsibleConnectionFailure from ansible.errors import AnsibleConnectionFailure
from ansible.plugins.netconf import NetconfBase from ansible.plugins.netconf import NetconfBase, ensure_ncclient
from ansible.plugins.netconf import ensure_connected, ensure_ncclient
try: try:
from ncclient import manager from ncclient import manager
@ -66,14 +65,12 @@ class Netconf(NetconfBase):
return device_info return device_info
@ensure_connected
def execute_rpc(self, name): def execute_rpc(self, name):
"""RPC to be execute on remote device """RPC to be execute on remote device
:name: Name of rpc in string format""" :name: Name of rpc in string format"""
return self.rpc(name) return self.rpc(name)
@ensure_ncclient @ensure_ncclient
@ensure_connected
def load_configuration(self, *args, **kwargs): def load_configuration(self, *args, **kwargs):
"""Loads given configuration on device """Loads given configuration on device
:format: Format of configuration (xml, text, set) :format: Format of configuration (xml, text, set)
@ -121,7 +118,7 @@ class Netconf(NetconfBase):
ssh_config=obj._ssh_config ssh_config=obj._ssh_config
) )
except SSHUnknownHostError as exc: except SSHUnknownHostError as exc:
raise AnsibleConnectionFailure(to_native(exc)) raise AnsibleConnectionFailure(to_text(exc))
guessed_os = None guessed_os = None
for c in m.server_capabilities: for c in m.server_capabilities:
@ -132,7 +129,6 @@ class Netconf(NetconfBase):
m.close_session() m.close_session()
return guessed_os return guessed_os
@ensure_connected
def get_configuration(self, *args, **kwargs): def get_configuration(self, *args, **kwargs):
"""Retrieve all or part of a specified configuration. """Retrieve all or part of a specified configuration.
:format: format in configuration should be retrieved :format: format in configuration should be retrieved
@ -140,14 +136,12 @@ class Netconf(NetconfBase):
(by default entire configuration is retrieved)""" (by default entire configuration is retrieved)"""
return self.m.get_configuration(*args, **kwargs).data_xml return self.m.get_configuration(*args, **kwargs).data_xml
@ensure_connected
def compare_configuration(self, *args, **kwargs): def compare_configuration(self, *args, **kwargs):
"""Compare configuration """Compare configuration
:rollback: rollback id""" :rollback: rollback id"""
return self.m.compare_configuration(*args, **kwargs).data_xml return self.m.compare_configuration(*args, **kwargs).data_xml
@ensure_ncclient @ensure_ncclient
@ensure_connected
def execute_action(self, xml_str): def execute_action(self, xml_str):
"""huawei execute-action""" """huawei execute-action"""
con_obj = None con_obj = None
@ -158,18 +152,15 @@ class Netconf(NetconfBase):
return con_obj.xml return con_obj.xml
@ensure_connected
def halt(self): def halt(self):
"""reboot the device""" """reboot the device"""
return self.m.halt().data_xml return self.m.halt().data_xml
@ensure_connected
def reboot(self): def reboot(self):
"""reboot the device""" """reboot the device"""
return self.m.reboot().data_xml return self.m.reboot().data_xml
@ensure_ncclient @ensure_ncclient
@ensure_connected
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
try: try:
if_rpc_reply = kwargs.pop('if_rpc_reply', False) if_rpc_reply = kwargs.pop('if_rpc_reply', False)
@ -180,7 +171,6 @@ class Netconf(NetconfBase):
raise Exception(to_xml(exc.xml)) raise Exception(to_xml(exc.xml))
@ensure_ncclient @ensure_ncclient
@ensure_connected
def get_config(self, *args, **kwargs): def get_config(self, *args, **kwargs):
try: try:
return self.m.get_config(*args, **kwargs).data_xml return self.m.get_config(*args, **kwargs).data_xml
@ -188,7 +178,6 @@ class Netconf(NetconfBase):
raise Exception(to_xml(exc.xml)) raise Exception(to_xml(exc.xml))
@ensure_ncclient @ensure_ncclient
@ensure_connected
def edit_config(self, *args, **kwargs): def edit_config(self, *args, **kwargs):
try: try:
return self.m.edit_config(*args, **kwargs).xml return self.m.edit_config(*args, **kwargs).xml
@ -196,7 +185,6 @@ class Netconf(NetconfBase):
raise Exception(to_xml(exc.xml)) raise Exception(to_xml(exc.xml))
@ensure_ncclient @ensure_ncclient
@ensure_connected
def execute_nc_cli(self, *args, **kwargs): def execute_nc_cli(self, *args, **kwargs):
try: try:
return self.m.cli(*args, **kwargs).xml return self.m.cli(*args, **kwargs).xml
@ -204,23 +192,19 @@ class Netconf(NetconfBase):
raise Exception(to_xml(exc.xml)) raise Exception(to_xml(exc.xml))
@ensure_ncclient @ensure_ncclient
@ensure_connected
def commit(self, *args, **kwargs): def commit(self, *args, **kwargs):
try: try:
return self.m.commit(*args, **kwargs).data_xml return self.m.commit(*args, **kwargs).data_xml
except RPCError as exc: except RPCError as exc:
raise Exception(to_xml(exc.xml)) raise Exception(to_xml(exc.xml))
@ensure_connected
def validate(self, *args, **kwargs): def validate(self, *args, **kwargs):
return self.m.validate(*args, **kwargs).data_xml return self.m.validate(*args, **kwargs).data_xml
@ensure_connected
def discard_changes(self, *args, **kwargs): def discard_changes(self, *args, **kwargs):
return self.m.discard_changes(*args, **kwargs).data_xml return self.m.discard_changes(*args, **kwargs).data_xml
@ensure_ncclient @ensure_ncclient
@ensure_connected
def dispatch_rpc(self, rpc_command=None, source=None, filter=None): def dispatch_rpc(self, rpc_command=None, source=None, filter=None):
""" """
Execute rpc on the remote device eg. dispatch('get-next') Execute rpc on the remote device eg. dispatch('get-next')

View file

@ -28,8 +28,7 @@ from ansible.module_utils._text import to_native
from ansible.module_utils.network.common.netconf import remove_namespaces from ansible.module_utils.network.common.netconf import remove_namespaces
from ansible.module_utils.network.iosxr.iosxr import build_xml, etree_find from ansible.module_utils.network.iosxr.iosxr import build_xml, etree_find
from ansible.errors import AnsibleConnectionFailure from ansible.errors import AnsibleConnectionFailure
from ansible.plugins.netconf import NetconfBase from ansible.plugins.netconf import NetconfBase, ensure_ncclient
from ansible.plugins.netconf import ensure_connected, ensure_ncclient
try: try:
from ncclient import manager from ncclient import manager
@ -42,7 +41,6 @@ except (ImportError, AttributeError): # paramiko and gssapi are incompatible an
class Netconf(NetconfBase): class Netconf(NetconfBase):
@ensure_connected
def get_device_info(self): def get_device_info(self):
device_info = {} device_info = {}
device_info['network_os'] = 'iosxr' device_info['network_os'] = 'iosxr'
@ -122,8 +120,6 @@ class Netconf(NetconfBase):
return guessed_os return guessed_os
# TODO: change .xml to .data_xml, when ncclient supports data_xml on all platforms # 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): def get(self, filter=None, remove_ns=False):
if isinstance(filter, list): if isinstance(filter, list):
filter = tuple(filter) filter = tuple(filter)
@ -137,8 +133,6 @@ class Netconf(NetconfBase):
except RPCError as exc: except RPCError as exc:
raise Exception(to_xml(exc.xml)) raise Exception(to_xml(exc.xml))
@ensure_ncclient
@ensure_connected
def get_config(self, source=None, filter=None, remove_ns=False): def get_config(self, source=None, filter=None, remove_ns=False):
if isinstance(filter, list): if isinstance(filter, list):
filter = tuple(filter) filter = tuple(filter)
@ -152,8 +146,6 @@ class Netconf(NetconfBase):
except RPCError as exc: except RPCError as exc:
raise Exception(to_xml(exc.xml)) 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): 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: if config is None:
raise ValueError('config value must be provided') raise ValueError('config value must be provided')
@ -168,8 +160,6 @@ class Netconf(NetconfBase):
except RPCError as exc: except RPCError as exc:
raise Exception(to_xml(exc.xml)) raise Exception(to_xml(exc.xml))
@ensure_ncclient
@ensure_connected
def commit(self, confirmed=False, timeout=None, persist=None, remove_ns=False): def commit(self, confirmed=False, timeout=None, persist=None, remove_ns=False):
try: try:
resp = self.m.commit(confirmed=confirmed, timeout=timeout, persist=persist) resp = self.m.commit(confirmed=confirmed, timeout=timeout, persist=persist)
@ -181,8 +171,6 @@ class Netconf(NetconfBase):
except RPCError as exc: except RPCError as exc:
raise Exception(to_xml(exc.xml)) raise Exception(to_xml(exc.xml))
@ensure_ncclient
@ensure_connected
def validate(self, source="candidate", remove_ns=False): def validate(self, source="candidate", remove_ns=False):
try: try:
resp = self.m.validate(source=source) resp = self.m.validate(source=source)
@ -194,8 +182,6 @@ class Netconf(NetconfBase):
except RPCError as exc: except RPCError as exc:
raise Exception(to_xml(exc.xml)) raise Exception(to_xml(exc.xml))
@ensure_ncclient
@ensure_connected
def discard_changes(self, remove_ns=False): def discard_changes(self, remove_ns=False):
try: try:
resp = self.m.discard_changes() resp = self.m.discard_changes()

View file

@ -25,8 +25,7 @@ import re
from ansible.module_utils._text import to_text, to_native from ansible.module_utils._text import to_text, to_native
from ansible.module_utils.six import string_types from ansible.module_utils.six import string_types
from ansible.errors import AnsibleConnectionFailure from ansible.errors import AnsibleConnectionFailure
from ansible.plugins.netconf import NetconfBase from ansible.plugins.netconf import NetconfBase, ensure_ncclient
from ansible.plugins.netconf import ensure_connected, ensure_ncclient
try: try:
from ncclient import manager from ncclient import manager
@ -60,7 +59,6 @@ class Netconf(NetconfBase):
return device_info return device_info
@ensure_connected
def execute_rpc(self, name): def execute_rpc(self, name):
""" """
RPC to be execute on remote device RPC to be execute on remote device
@ -70,7 +68,6 @@ class Netconf(NetconfBase):
return self.rpc(name) return self.rpc(name)
@ensure_ncclient @ensure_ncclient
@ensure_connected
def load_configuration(self, format='xml', action='merge', target='candidate', config=None): def load_configuration(self, format='xml', action='merge', target='candidate', config=None):
""" """
Load given configuration on device Load given configuration on device
@ -136,7 +133,6 @@ class Netconf(NetconfBase):
m.close_session() m.close_session()
return guessed_os return guessed_os
@ensure_connected
def get_configuration(self, format='xml', filter=None): def get_configuration(self, format='xml', filter=None):
""" """
Retrieve all or part of a specified configuration. Retrieve all or part of a specified configuration.
@ -153,7 +149,6 @@ class Netconf(NetconfBase):
return self.m.get_configuration(format=format, filter=filter).data_xml return self.m.get_configuration(format=format, filter=filter).data_xml
@ensure_connected
def compare_configuration(self, rollback=0): def compare_configuration(self, rollback=0):
""" """
Compare the candidate configuration with running configuration Compare the candidate configuration with running configuration
@ -164,12 +159,10 @@ class Netconf(NetconfBase):
""" """
return self.m.compare_configuration(rollback=rollback).data_xml return self.m.compare_configuration(rollback=rollback).data_xml
@ensure_connected
def halt(self): def halt(self):
"""reboot the device""" """reboot the device"""
return self.m.halt().data_xml return self.m.halt().data_xml
@ensure_connected
def reboot(self): def reboot(self):
"""reboot the device""" """reboot the device"""
return self.m.reboot().data_xml return self.m.reboot().data_xml
@ -179,7 +172,6 @@ class Netconf(NetconfBase):
# ncclient generic rpc() method to execute rpc on remote host. # ncclient generic rpc() method to execute rpc on remote host.
# Remove below method after the issue in ncclient is fixed. # Remove below method after the issue in ncclient is fixed.
@ensure_ncclient @ensure_ncclient
@ensure_connected
def commit(self, confirmed=False, check=False, timeout=None, comment=None, synchronize=False, at_time=None): def commit(self, confirmed=False, check=False, timeout=None, comment=None, synchronize=False, at_time=None):
""" """
Commit the candidate configuration as the device's new current configuration. Commit the candidate configuration as the device's new current configuration.