From 2bc4c4f1565ecc0736f9fe8b17f511d9463cc565 Mon Sep 17 00:00:00 2001
From: Kedar Kekan <4506537+kedarX@users.noreply.github.com>
Date: Wed, 6 Dec 2017 22:37:31 +0530
Subject: [PATCH] IOS-XR NetConf and Cliconf plugin work (#33332)
* - Netconf plugin addition for iosxr
- Utilities refactoring to support netconf and cliconf
- iosx_banner refactoring for netconf and cliconf
- Integration testcases changes to accomodate above changes
* Fix sanity failures, shippable errors and review comments
* fix pep8 issue
* changes run_command method to send specific command args
* - Review comment fixes
- iosxr_command changes to remove ComplexDict based command_spec
* - Move namespaces removal method from utils to netconf plugin
* Minor refactoring in utils and change in deprecation message
* rewrite build_xml logic and import changes for new utils dir structure
* - Review comment changes and minor changes to documentation
* * refactor common code and docs updates
---
.../module_utils/network/iosxr/iosxr.py | 423 ++++++++++++++----
.../modules/network/iosxr/iosxr_banner.py | 194 +++++---
.../modules/network/iosxr/iosxr_command.py | 31 +-
.../modules/network/iosxr/iosxr_config.py | 8 +-
.../modules/network/iosxr/iosxr_facts.py | 9 +-
.../modules/network/iosxr/iosxr_interface.py | 7 +-
.../modules/network/iosxr/iosxr_logging.py | 6 +-
.../modules/network/iosxr/iosxr_netconf.py | 31 +-
.../modules/network/iosxr/iosxr_system.py | 15 +-
.../modules/network/iosxr/iosxr_user.py | 7 +-
lib/ansible/plugins/action/iosxr.py | 27 +-
lib/ansible/plugins/cliconf/iosxr.py | 11 +-
lib/ansible/plugins/netconf/__init__.py | 38 +-
lib/ansible/plugins/netconf/iosxr.py | 209 +++++++++
lib/ansible/plugins/netconf/junos.py | 36 ++
.../utils/module_docs_fragments/iosxr.py | 3 +
.../targets/iosxr_banner/tasks/main.yaml | 1 +
.../targets/iosxr_banner/tasks/netconf.yaml | 16 +
.../iosxr_banner/tests/cli/basic-login.yaml | 3 +
.../iosxr_banner/tests/cli/basic-motd.yaml | 3 +
.../tests/cli/basic-no-login.yaml | 3 +
.../tests/netconf/basic-login.yaml | 49 ++
.../tests/netconf/basic-motd.yaml | 49 ++
.../tests/netconf/basic-no-login.yaml | 43 ++
.../iosxr_command/tests/cli/invalid.yaml | 4 +-
.../iosxr_facts/tests/cli/all_facts.yaml | 1 +
.../iosxr_facts/tests/cli/default_facts.yaml | 1 +
.../iosxr_facts/tests/cli/invalid_subset.yaml | 2 +
.../iosxr_facts/tests/cli/not_hardware.yaml | 1 +
.../iosxr_interface/tests/cli/basic.yaml | 18 +
.../iosxr_interface/tests/cli/intent.yaml | 6 +
.../iosxr_logging/tests/cli/basic.yaml | 11 +
.../tests/cli/set_domain_list.yaml | 10 +
.../tests/cli/set_domain_name.yaml | 4 +
.../iosxr_system/tests/cli/set_hostname.yaml | 4 +
.../tests/cli/set_lookup_source.yaml | 4 +
.../tests/cli/set_name_servers.yaml | 4 +
.../targets/iosxr_user/tests/cli/auth.yaml | 6 +
.../targets/iosxr_user/tests/cli/basic.yaml | 11 +
test/sanity/pep8/legacy-files.txt | 4 -
.../network/iosxr/test_iosxr_command.py | 14 +-
.../modules/network/iosxr/test_iosxr_facts.py | 10 +-
42 files changed, 1090 insertions(+), 247 deletions(-)
create mode 100644 lib/ansible/plugins/netconf/iosxr.py
create mode 100644 test/integration/targets/iosxr_banner/tasks/netconf.yaml
create mode 100644 test/integration/targets/iosxr_banner/tests/netconf/basic-login.yaml
create mode 100644 test/integration/targets/iosxr_banner/tests/netconf/basic-motd.yaml
create mode 100644 test/integration/targets/iosxr_banner/tests/netconf/basic-no-login.yaml
diff --git a/lib/ansible/module_utils/network/iosxr/iosxr.py b/lib/ansible/module_utils/network/iosxr/iosxr.py
index 5645475656d..39119eba2b2 100644
--- a/lib/ansible/module_utils/network/iosxr/iosxr.py
+++ b/lib/ansible/module_utils/network/iosxr/iosxr.py
@@ -26,12 +26,44 @@
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
-from ansible.module_utils._text import to_text
-from ansible.module_utils.basic import env_fallback, return_values
-from ansible.module_utils.network.common.utils import to_list, ComplexList
-from ansible.module_utils.connection import exec_command
+import json
+from difflib import Differ
+from copy import deepcopy
+
+from ansible.module_utils._text import to_text, to_bytes
+from ansible.module_utils.basic import env_fallback
+from ansible.module_utils.network.common.utils import to_list
+from ansible.module_utils.connection import Connection
+from ansible.module_utils.network.common.netconf import NetconfConnection
+
+try:
+ from ncclient.xml_ import to_xml
+ HAS_NCCLIENT = True
+except ImportError:
+ HAS_NCCLIENT = False
+
+try:
+ from lxml import etree
+ HAS_XML = True
+except ImportError:
+ HAS_XML = False
_DEVICE_CONFIGS = {}
+_EDIT_OPS = frozenset(['merge', 'create', 'replace', 'delete'])
+
+BASE_1_0 = "{urn:ietf:params:xml:ns:netconf:base:1.0}"
+
+NS_DICT = {
+ 'BASE_NSMAP': {"xc": "urn:ietf:params:xml:ns:netconf:base:1.0"},
+ 'BANNERS_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-infra-infra-cfg"},
+ 'INTERFACES_NSMAP': {None: "http://openconfig.net/yang/interfaces"},
+ 'INSTALL_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-installmgr-admin-oper"},
+ 'HOST-NAMES_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-shellutil-cfg"},
+ 'M:TYPE_NSMAP': {"idx": "urn:ietf:params:xml:ns:yang:iana-if-type"},
+ 'ETHERNET_NSMAP': {None: "http://openconfig.net/yang/interfaces/ethernet"},
+ 'CETHERNET_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-drivers-media-eth-cfg"},
+ 'INTERFACE-CONFIGURATIONS_NSMAP': {None: "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"}
+}
iosxr_provider_spec = {
'host': dict(),
@@ -40,10 +72,19 @@ iosxr_provider_spec = {
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
'timeout': dict(type='int'),
+ 'transport': dict(),
}
+
iosxr_argument_spec = {
'provider': dict(type='dict', options=iosxr_provider_spec)
}
+
+command_spec = {
+ 'command': dict(),
+ 'prompt': dict(default=None),
+ 'answer': dict(default=None)
+}
+
iosxr_top_spec = {
'host': dict(removed_in_version=2.9),
'port': dict(removed_in_version=2.9, type='int'),
@@ -59,91 +100,317 @@ def get_provider_argspec():
return iosxr_provider_spec
-def check_args(module, warnings):
- pass
+def get_connection(module):
+ if hasattr(module, 'connection'):
+ return module.connection
+
+ capabilities = get_device_capabilities(module)
+ network_api = capabilities.get('network_api')
+ if network_api == 'cliconf':
+ module.connection = Connection(module._socket_path)
+ elif network_api == 'netconf':
+ module.connection = NetconfConnection(module._socket_path)
+ else:
+ module.fail_json(msg='Invalid connection type {!s}'.format(network_api))
+
+ return module.connection
-def get_config(module, flags=None):
- flags = [] if flags is None else flags
+def get_device_capabilities(module):
+ if hasattr(module, 'capabilities'):
+ return module.capabilities
- cmd = 'show running-config '
- cmd += ' '.join(flags)
- cmd = cmd.strip()
+ capabilities = Connection(module._socket_path).get_capabilities()
+ module.capabilities = json.loads(capabilities)
- try:
- return _DEVICE_CONFIGS[cmd]
- except KeyError:
- rc, out, err = exec_command(module, cmd)
- if rc != 0:
- module.fail_json(msg='unable to retrieve current config', stderr=to_text(err, errors='surrogate_or_strict'))
- cfg = to_text(out, errors='surrogate_or_strict').strip()
- _DEVICE_CONFIGS[cmd] = cfg
+ return module.capabilities
+
+
+def build_xml_subtree(container_ele, xmap, param=None, opcode=None):
+ sub_root = container_ele
+ meta_subtree = list()
+
+ for key, meta in xmap.items():
+
+ candidates = meta.get('xpath', "").split("/")
+
+ if container_ele.tag == candidates[-2]:
+ parent = container_ele
+ elif sub_root.tag == candidates[-2]:
+ parent = sub_root
+ else:
+ parent = sub_root.find(".//" + meta.get('xpath', "").split(sub_root.tag + '/', 1)[1].rsplit('/', 1)[0])
+
+ if ((opcode in ('delete', 'merge') and meta.get('operation', 'unknown') == 'edit') or
+ meta.get('operation', None) is None):
+
+ if meta.get('tag', False):
+ if parent.tag == container_ele.tag:
+ if meta.get('ns', None) is True:
+ child = etree.Element(candidates[-1], nsmap=NS_DICT[key.upper() + "_NSMAP"])
+ else:
+ child = etree.Element(candidates[-1])
+ meta_subtree.append(child)
+ sub_root = child
+ else:
+ if meta.get('ns', None) is True:
+ child = etree.SubElement(parent, candidates[-1], nsmap=NS_DICT[key.upper() + "_NSMAP"])
+ else:
+ child = etree.SubElement(parent, candidates[-1])
+
+ if meta.get('attrib', None) and opcode in ('delete', 'merge'):
+ child.set(BASE_1_0 + meta.get('attrib'), opcode)
+
+ continue
+
+ text = None
+ param_key = key.split(":")
+ if param_key[0] == 'a':
+ if param.get(param_key[1], None):
+ text = param.get(param_key[1])
+ elif param_key[0] == 'm':
+ if meta.get('value', None):
+ text = meta.get('value')
+
+ if text:
+ if meta.get('ns', None) is True:
+ child = etree.SubElement(parent, candidates[-1], nsmap=NS_DICT[key.upper() + "_NSMAP"])
+ else:
+ child = etree.SubElement(parent, candidates[-1])
+ child.text = text
+
+ if len(meta_subtree) > 1:
+ for item in meta_subtree:
+ container_ele.append(item)
+
+ return sub_root
+
+
+def build_xml(container, xmap=None, params=None, opcode=None):
+
+ '''
+ Builds netconf xml rpc document from meta-data
+
+ Args:
+ container: the YANG container within the namespace
+ xmap: meta-data map to build xml tree
+ params: Input params that feed xml tree values
+ opcode: operation to be performed (merge, delete etc.)
+
+ Example:
+ Module inputs:
+ banner_params = [{'banner':'motd', 'text':'Ansible banner example', 'state':'present'}]
+
+ Meta-data definition:
+ bannermap = collections.OrderedDict()
+ bannermap.update([
+ ('banner', {'xpath' : 'banners/banner', 'tag' : True, 'attrib' : "operation"}),
+ ('a:banner', {'xpath' : 'banner/banner-name'}),
+ ('a:text', {'xpath' : 'banner/banner-text', 'operation' : 'edit'})
+ ])
+
+ Fields:
+ key: exact match to the key in arg_spec for a parameter
+ (prefixes --> a: value fetched from arg_spec, m: value fetched from meta-data)
+ xpath: xpath of the element (based on YANG model)
+ tag: True if no text on the element
+ attrib: attribute to be embedded in the element (e.g. xc:operation="merge")
+ operation: if edit --> includes the element in edit_config() query else ignores for get() queries
+ value: if key is prefixed with "m:", value is required in meta-data
+
+ Output:
+
+
+
+ motd
+ Ansible banner example
+
+
+
+ :returns: xml rpc document as a string
+ '''
+
+ if opcode == 'filter':
+ root = etree.Element("filter", type="subtree")
+ elif opcode in ('delete', 'merge'):
+ root = etree.Element("config", nsmap=NS_DICT['BASE_NSMAP'])
+
+ container_ele = etree.SubElement(root, container, nsmap=NS_DICT[container.upper() + "_NSMAP"])
+
+ if xmap:
+ if not params:
+ build_xml_subtree(container_ele, xmap)
+ else:
+ subtree_list = list()
+
+ for param in to_list(params):
+ subtree_list.append(build_xml_subtree(container_ele, xmap, param, opcode=opcode))
+
+ for item in subtree_list:
+ container_ele.append(item)
+
+ return etree.tostring(root)
+
+
+def etree_find(root, node):
+ element = etree.fromstring(root).find('.//' + to_bytes(node, errors='surrogate_then_replace').strip())
+ if element is not None:
+ return element
+
+ return None
+
+
+def etree_findall(root, node):
+ element = etree.fromstring(root).findall('.//' + to_bytes(node, errors='surrogate_then_replace').strip())
+ if element is not None:
+ return element
+
+ return None
+
+
+def is_cliconf(module):
+ capabilities = get_device_capabilities(module)
+ network_api = capabilities.get('network_api')
+ if network_api not in ('cliconf', 'netconf'):
+ module.fail_json(msg=('unsupported network_api: {!s}'.format(network_api)))
+ return False
+
+ if network_api == 'cliconf':
+ return True
+
+ return False
+
+
+def is_netconf(module):
+ capabilities = get_device_capabilities(module)
+ network_api = capabilities.get('network_api')
+ if network_api not in ('cliconf', 'netconf'):
+ module.fail_json(msg=('unsupported network_api: {!s}'.format(network_api)))
+ return False
+
+ if network_api == 'netconf':
+ if not HAS_NCCLIENT:
+ module.fail_json(msg=('ncclient is not installed'))
+ if not HAS_XML:
+ module.fail_json(msg=('lxml is not installed'))
+
+ return True
+
+ return False
+
+
+def get_config_diff(module, running=None, candidate=None):
+ conn = get_connection(module)
+
+ if is_cliconf(module):
+ return conn.get('show commit changes diff')
+ elif is_netconf(module):
+ if running and candidate:
+ running_data = running.split("\n", 1)[1].rsplit("\n", 1)[0]
+ candidate_data = candidate.split("\n", 1)[1].rsplit("\n", 1)[0]
+ if running_data != candidate_data:
+ d = Differ()
+ diff = list(d.compare(running_data.splitlines(), candidate_data.splitlines()))
+ return '\n'.join(diff).strip()
+
+ return None
+
+
+def discard_config(module):
+ conn = get_connection(module)
+ conn.discard_changes()
+
+
+def commit_config(module, comment=None, confirmed=False, confirm_timeout=None, persist=False, check=False):
+ conn = get_connection(module)
+ reply = None
+
+ if check:
+ reply = conn.validate()
+ else:
+ if is_netconf(module):
+ reply = conn.commit(confirmed=confirmed, timeout=confirm_timeout, persist=persist)
+ elif is_cliconf(module):
+ reply = conn.commit(comment=comment)
+
+ return reply
+
+
+def get_config(module, source='running', config_filter=None):
+ global _DEVICE_CONFIGS
+
+ conn = get_connection(module)
+
+ if config_filter is not None:
+ key = (source + ' ' + ' '.join(config_filter)).strip().rstrip()
+ else:
+ key = source
+ config = _DEVICE_CONFIGS.get(key)
+ if config:
+ return config
+ else:
+ out = conn.get_config(source=source, filter=config_filter)
+ if is_netconf(module):
+ out = to_xml(conn.get_config(source=source, filter=config_filter))
+
+ cfg = to_bytes(out, errors='surrogate_then_replace').strip()
+ _DEVICE_CONFIGS.update({key: cfg})
return cfg
-def to_commands(module, commands):
- spec = {
- 'command': dict(key=True),
- 'prompt': dict(),
- 'answer': dict()
- }
- transform = ComplexList(spec, module)
- return transform(commands)
+def load_config(module, command_filter, warnings, replace=False, admin=False, commit=False, comment=None):
+ conn = get_connection(module)
+ if is_netconf(module):
+ # FIXME: check for platform behaviour and restore this
+ # ret = conn.lock(target = 'candidate')
+ # ret = conn.discard_changes()
+ try:
+ ret = conn.edit_config(command_filter)
+ finally:
+ # ret = conn.unlock(target = 'candidate')
+ pass
-def run_commands(module, commands, check_rc=True):
- responses = list()
- commands = to_commands(module, to_list(commands))
- for cmd in to_list(commands):
- cmd = module.jsonify(cmd)
- rc, out, err = exec_command(module, cmd)
- if check_rc and rc != 0:
- module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), rc=rc)
- responses.append(to_text(out, errors='surrogate_or_strict'))
- return responses
+ return ret
-
-def load_config(module, commands, warnings, commit=False, replace=False, comment=None, admin=False):
- cmd = 'configure terminal'
- if admin:
- cmd = 'admin ' + cmd
-
- rc, out, err = exec_command(module, cmd)
- if rc != 0:
- module.fail_json(msg='unable to enter configuration mode', err=to_text(err, errors='surrogate_or_strict'))
-
- failed = False
- for command in to_list(commands):
- if command == 'end':
- continue
-
- rc, out, err = exec_command(module, command)
- if rc != 0:
- failed = True
- break
-
- if failed:
- exec_command(module, 'abort')
- module.fail_json(msg=to_text(err, errors='surrogate_or_strict'), commands=commands, rc=rc)
-
- rc, diff, err = exec_command(module, 'show commit changes diff')
- if rc != 0:
- # If we failed, maybe we are in an old version so
- # we run show configuration instead
- rc, diff, err = exec_command(module, 'show configuration')
+ elif is_cliconf(module):
+ # to keep the pre-cliconf behaviour, make a copy, avoid adding commands to input list
+ cmd_filter = deepcopy(command_filter)
+ cmd_filter.insert(0, 'configure terminal')
+ if admin:
+ cmd_filter.insert(0, 'admin')
+ conn.edit_config(cmd_filter)
+ diff = get_config_diff(module)
if module._diff:
- warnings.append('device platform does not support config diff')
+ if diff:
+ module['diff'] = to_text(diff, errors='surrogate_or_strict')
+ if commit:
+ commit_config(module, comment=comment)
+ conn.edit_config('end')
+ else:
+ conn.discard_changes()
- if commit:
- cmd = 'commit'
- if comment:
- cmd += ' comment {0}'.format(comment)
- else:
- cmd = 'abort'
+ return diff
- rc, out, err = exec_command(module, cmd)
- if rc != 0:
- exec_command(module, 'abort')
- module.fail_json(msg=err, commands=commands, rc=rc)
- return to_text(diff, errors='surrogate_or_strict')
+def run_command(module, commands):
+ conn = get_connection(module)
+ responses = list()
+ for cmd in to_list(commands):
+ try:
+ cmd = json.loads(cmd)
+ command = cmd['command']
+ prompt = cmd['prompt']
+ answer = cmd['answer']
+ except:
+ command = cmd
+ prompt = None
+ answer = None
+
+ out = conn.get(command, prompt, answer)
+
+ try:
+ responses.append(to_text(out, errors='surrogate_or_strict'))
+ except UnicodeError:
+ module.fail_json(msg=u'failed to decode output from {0}:{1}'.format(cmd, to_text(out)))
+ return responses
diff --git a/lib/ansible/modules/network/iosxr/iosxr_banner.py b/lib/ansible/modules/network/iosxr/iosxr_banner.py
index c97f2d02744..264bdb7fd56 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_banner.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_banner.py
@@ -16,32 +16,32 @@ DOCUMENTATION = """
---
module: iosxr_banner
version_added: "2.4"
-author: "Trishna Guha (@trishnaguha)"
+author:
+ - Trishna Guha (@trishnaguha)
+ - Kedar Kekan (@kedarX)
short_description: Manage multiline banners on Cisco IOS XR devices
description:
- - This will configure both exec and motd banners on remote devices
- running Cisco IOS XR. It allows playbooks to add or remote
- banner text from the active running configuration.
+ - This module will configure both exec and motd banners on remote device
+ running Cisco IOS XR. It allows playbooks to add or remove
+ banner text from the running configuration.
+extends_documentation_fragment: iosxr
notes:
- - Tested against IOS XR 6.1.2
+ - Tested against IOS XRv 6.1.2
options:
banner:
description:
- - Specifies which banner that should be
- configured on the remote device.
+ - Specifies the type of banner to configure on remote device.
required: true
default: null
choices: ['login', 'motd']
text:
description:
- - The banner text that should be
- present in the remote device running configuration. This argument
- accepts a multiline string, with no empty lines. Requires I(state=present).
+ - Banner text to be configured. Accepts multiline string,
+ without empty lines. Requires I(state=present).
default: null
state:
description:
- - Specifies whether or not the configuration is present in the current
- devices active running configuration.
+ - Existential state of the configuration on the device.
default: present
choices: ['present', 'absent']
"""
@@ -79,60 +79,130 @@ commands:
"""
import re
+import collections
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
-from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
+from ansible.module_utils.network.iosxr.iosxr import get_config_diff, commit_config
+from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, discard_config
+from ansible.module_utils.network.iosxr.iosxr import build_xml, is_cliconf, is_netconf
+from ansible.module_utils.network.iosxr.iosxr import etree_find, etree_findall
-def map_obj_to_commands(updates, module):
- commands = list()
- want, have = updates
- state = module.params['state']
+class ConfigBase(object):
+ def __init__(self, module):
+ self._module = module
+ self._result = {'changed': False, 'warnings': []}
+ self._want = {}
+ self._have = {}
- if state == 'absent':
- if have.get('state') != 'absent' and ('text' in have.keys() and have['text']):
- commands.append('no banner %s' % module.params['banner'])
-
- elif state == 'present':
- if (want['text'] and
- want['text'].encode().decode('unicode_escape').strip("'") != have.get('text')):
- banner_cmd = 'banner %s ' % module.params['banner']
- banner_cmd += want['text'].strip()
- commands.append(banner_cmd)
-
- return commands
+ def map_params_to_obj(self):
+ text = self._module.params['text']
+ if text:
+ text = "{!r}".format(str(text).strip())
+ self._want.update({
+ 'banner': self._module.params['banner'],
+ 'text': text,
+ 'state': self._module.params['state']
+ })
-def map_config_to_obj(module):
- flags = 'banner %s' % module.params['banner']
- output = get_config(module, flags=[flags])
+class CliConfiguration(ConfigBase):
+ def __init__(self, module):
+ super(CliConfiguration, self).__init__(module)
- match = re.search(r'banner (\S+) (.*)', output, re.DOTALL)
- if match:
- text = match.group(2).strip("'")
- else:
- text = None
+ def map_obj_to_commands(self):
+ commands = list()
+ state = self._module.params['state']
+ if state == 'absent':
+ if self._have.get('state') != 'absent' and ('text' in self._have.keys() and self._have['text']):
+ commands.append('no banner {!s}'.format(self._module.params['banner']))
+ elif state == 'present':
+ if (self._want['text'] and
+ self._want['text'].encode().decode('unicode_escape').strip("'") != self._have.get('text')):
+ banner_cmd = 'banner {!s} '.format(self._module.params['banner'])
+ banner_cmd += self._want['text'].strip()
+ commands.append(banner_cmd)
+ self._result['commands'] = commands
+ if commands:
+ if not self._module.check_mode:
+ load_config(self._module, commands, self._result['warnings'], commit=True)
+ self._result['changed'] = True
- obj = {'banner': module.params['banner'], 'state': 'absent'}
+ def map_config_to_obj(self):
+ cli_filter = 'banner {!s}'.format(self._module.params['banner'])
+ output = get_config(self._module, config_filter=cli_filter)
+ match = re.search(r'banner (\S+) (.*)', output, re.DOTALL)
+ if match:
+ text = match.group(2).strip("'")
+ else:
+ text = None
+ obj = {'banner': self._module.params['banner'], 'state': 'absent'}
+ if output:
+ obj['text'] = text
+ obj['state'] = 'present'
+ self._have.update(obj)
- if output:
- obj['text'] = text
- obj['state'] = 'present'
+ def run(self):
+ self.map_params_to_obj()
+ self.map_config_to_obj()
+ self.map_obj_to_commands()
- return obj
+ return self._result
-def map_params_to_obj(module):
- text = module.params['text']
- if text:
- text = "%r" % (str(text).strip())
+class NCConfiguration(ConfigBase):
+ def __init__(self, module):
+ super(NCConfiguration, self).__init__(module)
+ self._banners_meta = collections.OrderedDict()
+ self._banners_meta.update([
+ ('banner', {'xpath': 'banners/banner', 'tag': True, 'attrib': "operation"}),
+ ('a:banner', {'xpath': 'banner/banner-name'}),
+ ('a:text', {'xpath': 'banner/banner-text', 'operation': 'edit'})
+ ])
- return {
- 'banner': module.params['banner'],
- 'text': text,
- 'state': module.params['state']
- }
+ def map_obj_to_commands(self):
+ state = self._module.params['state']
+ _get_filter = build_xml('banners', xmap=self._banners_meta, params=self._module.params, opcode="filter")
+
+ running = get_config(self._module, source='running', config_filter=_get_filter)
+
+ banner_name = None
+ banner_text = None
+ if etree_find(running, 'banner-text') is not None:
+ banner_name = etree_find(running, 'banner-name').text
+ banner_text = etree_find(running, 'banner-text').text
+
+ opcode = None
+ if state == 'absent' and banner_name == self._module.params['banner'] and len(banner_text):
+ opcode = "delete"
+ elif state == 'present':
+ opcode = 'merge'
+
+ self._result['commands'] = []
+ if opcode:
+ _edit_filter = build_xml('banners', xmap=self._banners_meta, params=self._module.params, opcode=opcode)
+
+ if _edit_filter is not None:
+ if not self._module.check_mode:
+ load_config(self._module, _edit_filter, self._result['warnings'])
+ candidate = get_config(self._module, source='candidate', config_filter=_get_filter)
+
+ diff = get_config_diff(self._module, running, candidate)
+ if diff:
+ commit_config(self._module)
+ self._result['changed'] = True
+ self._result['commands'] = _edit_filter
+ if self._module._diff:
+ self._result['diff'] = {'prepared': diff}
+ else:
+ discard_config(self._module)
+
+ def run(self):
+ self.map_params_to_obj()
+ self.map_obj_to_commands()
+
+ return self._result
def main():
@@ -152,23 +222,13 @@ def main():
required_if=required_if,
supports_check_mode=True)
- warnings = list()
- check_args(module, warnings)
-
- result = {'changed': False}
- result['warnings'] = warnings
-
- want = map_params_to_obj(module)
- have = map_config_to_obj(module)
-
- commands = map_obj_to_commands((want, have), module)
- result['commands'] = commands
-
- if commands:
- if not module.check_mode:
- load_config(module, commands, result['warnings'], commit=True)
- result['changed'] = True
+ if is_cliconf(module):
+ module.deprecate("cli support for 'iosxr_banner' is deprecated. Use transport netconf instead', version='4 releases from v2.5")
+ config_object = CliConfiguration(module)
+ elif is_netconf(module):
+ config_object = NCConfiguration(module)
+ result = config_object.run()
module.exit_json(**result)
diff --git a/lib/ansible/modules/network/iosxr/iosxr_command.py b/lib/ansible/modules/network/iosxr/iosxr_command.py
index 33d4d4c8453..5fca0fdb262 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_command.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_command.py
@@ -94,6 +94,7 @@ tasks:
commands:
- show version
- show interfaces
+ - [{ command: example command that prompts, prompt: expected prompt, answer: yes}]
- name: run multiple commands and evaluate the output
iosxr_command:
@@ -125,9 +126,9 @@ failed_conditions:
import time
from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.network.iosxr.iosxr import run_commands, iosxr_argument_spec, check_args
+from ansible.module_utils.network.iosxr.iosxr import run_command, iosxr_argument_spec
+from ansible.module_utils.network.iosxr.iosxr import command_spec
from ansible.module_utils.network.common.parsing import Conditional
-from ansible.module_utils.network.common.utils import ComplexList
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_native
@@ -140,27 +141,27 @@ def to_lines(stdout):
def parse_commands(module, warnings):
- command = ComplexList(dict(
- command=dict(key=True),
- prompt=dict(),
- answer=dict()
- ), module)
- commands = command(module.params['commands'])
-
+ commands = module.params['commands']
for item in list(commands):
- if module.check_mode and not item['command'].startswith('show'):
+ try:
+ command = item['command']
+ except Exception:
+ command = item
+ if module.check_mode and not command.startswith('show'):
warnings.append(
'only show commands are supported when using check mode, not '
- 'executing `%s`' % item['command']
+ 'executing `%s`' % command
)
commands.remove(item)
- elif item['command'].startswith('conf'):
+ elif command.startswith('conf'):
module.fail_json(
msg='iosxr_command does not support running config mode '
'commands. Please use iosxr_config instead'
)
+
return commands
+
def main():
spec = dict(
commands=dict(type='list', required=True),
@@ -174,11 +175,12 @@ def main():
spec.update(iosxr_argument_spec)
+ spec.update(command_spec)
+
module = AnsibleModule(argument_spec=spec,
supports_check_mode=True)
warnings = list()
- check_args(module, warnings)
commands = parse_commands(module, warnings)
@@ -190,7 +192,7 @@ def main():
match = module.params['match']
while retries > 0:
- responses = run_commands(module, commands)
+ responses = run_command(module, commands)
for item in list(conditionals):
if item(responses):
@@ -210,7 +212,6 @@ def main():
msg = 'One or more conditional statements have not be satisfied'
module.fail_json(msg=msg, failed_conditions=failed_conditions)
-
result = {
'changed': False,
'stdout': responses,
diff --git a/lib/ansible/modules/network/iosxr/iosxr_config.py b/lib/ansible/modules/network/iosxr/iosxr_config.py
index 9b34f02dc06..d886a7cc7b2 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_config.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_config.py
@@ -180,9 +180,8 @@ backup_path:
sample: /playbooks/ansible/backup/iosxr01.2016-07-16@22:28:34
"""
from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.network.iosxr.iosxr import load_config,get_config
+from ansible.module_utils.network.iosxr.iosxr import load_config, get_config
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
-from ansible.module_utils.network.iosxr.iosxr import check_args as iosxr_check_args
from ansible.module_utils.network.common.config import NetworkConfig, dumps
@@ -190,7 +189,6 @@ DEFAULT_COMMIT_COMMENT = 'configured by iosxr_config'
def check_args(module, warnings):
- iosxr_check_args(module, warnings)
if module.params['comment']:
if len(module.params['comment']) > 60:
module.fail_json(msg='comment argument cannot be more than 60 characters')
@@ -216,6 +214,7 @@ def get_candidate(module):
candidate.add(module.params['lines'], parents=parents)
return candidate
+
def run(module, result):
match = module.params['match']
replace = module.params['replace']
@@ -231,7 +230,7 @@ def run(module, result):
contents = get_running_config(module)
configobj = NetworkConfig(contents=contents, indent=1)
commands = candidate.difference(configobj, path=path, match=match,
- replace=replace)
+ replace=replace)
else:
commands = candidate.items
@@ -253,6 +252,7 @@ def run(module, result):
result['diff'] = dict(prepared=diff)
result['changed'] = True
+
def main():
"""main entry point for module execution
"""
diff --git a/lib/ansible/modules/network/iosxr/iosxr_facts.py b/lib/ansible/modules/network/iosxr/iosxr_facts.py
index cd37987fb52..74a114fec7b 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_facts.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_facts.py
@@ -115,7 +115,7 @@ ansible_net_neighbors:
import re
from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args, run_commands
+from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, run_command
from ansible.module_utils.six import iteritems
from ansible.module_utils.six.moves import zip
@@ -166,7 +166,7 @@ class Hardware(FactsBase):
results['dir /all'])
match = re.search(r'Physical Memory: (\d+)M total \((\d+)',
- results['show memory summary'])
+ results['show memory summary'])
if match:
self.facts['memtotal_mb'] = match.group(1)
self.facts['memfree_mb'] = match.group(2)
@@ -188,7 +188,7 @@ class Interfaces(FactsBase):
def commands(self):
return(['show interfaces', 'show ipv6 interface',
- 'show lldp', 'show lldp neighbors detail'])
+ 'show lldp', 'show lldp neighbors detail'])
def populate(self, results):
self.facts['all_ipv4_addresses'] = list()
@@ -360,7 +360,6 @@ def main():
supports_check_mode=True)
warnings = list()
- check_args(module, warnings)
gather_subset = module.params['gather_subset']
@@ -405,7 +404,7 @@ def main():
try:
for inst in instances:
commands = inst.commands()
- responses = run_commands(module, commands)
+ responses = run_command(module, commands)
results = dict(zip(commands, responses))
inst.populate(results)
facts.update(inst.facts)
diff --git a/lib/ansible/modules/network/iosxr/iosxr_interface.py b/lib/ansible/modules/network/iosxr/iosxr_interface.py
index 50aa0012cf9..7404ff5ccff 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_interface.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_interface.py
@@ -22,6 +22,7 @@ short_description: Manage Interface on Cisco IOS XR network devices
description:
- This module provides declarative management of Interfaces
on Cisco IOS XR network devices.
+extends_documentation_fragment: iosxr
notes:
- Tested against IOS XR 6.1.2
options:
@@ -142,7 +143,7 @@ from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import exec_command
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
-from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
+from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
from ansible.module_utils.network.common.utils import conditional, remove_default_spec
@@ -228,7 +229,7 @@ def map_params_to_obj(module):
def map_config_to_obj(module):
- data = get_config(module, flags=['interface'])
+ data = get_config(module, config_filter='interface')
interfaces = data.strip().rstrip('!').split('!')
if not interfaces:
@@ -387,7 +388,6 @@ def main():
supports_check_mode=True)
warnings = list()
- check_args(module, warnings)
result = {'changed': False}
@@ -402,7 +402,6 @@ def main():
if commands:
if not module.check_mode:
load_config(module, commands, result['warnings'], commit=True)
- exec_command(module, 'exit')
result['changed'] = True
failed_conditions = check_declarative_intent_params(module, want, result)
diff --git a/lib/ansible/modules/network/iosxr/iosxr_logging.py b/lib/ansible/modules/network/iosxr/iosxr_logging.py
index 04f37d6a169..9a5c0a672f8 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_logging.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_logging.py
@@ -21,6 +21,7 @@ short_description: Manage logging on network devices
description:
- This module provides declarative management of logging
on Cisco IOS XR devices.
+extends_documentation_fragment: iosxr
notes:
- Tested against IOS XR 6.1.2
options:
@@ -114,7 +115,7 @@ from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
-from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
+from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
from ansible.module_utils.network.common.utils import remove_default_spec
@@ -237,7 +238,7 @@ def map_config_to_obj(module):
obj = []
dest_group = ('console', 'hostnameprefix', 'monitor', 'buffered', 'on')
- data = get_config(module, flags=['logging'])
+ data = get_config(module, config_filter='logging')
lines = data.split("\n")
for line in lines:
@@ -349,7 +350,6 @@ def main():
supports_check_mode=True)
warnings = list()
- check_args(module, warnings)
result = {'changed': False}
diff --git a/lib/ansible/modules/network/iosxr/iosxr_netconf.py b/lib/ansible/modules/network/iosxr/iosxr_netconf.py
index 3ecbcdf22e1..4ba17299c61 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_netconf.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_netconf.py
@@ -38,7 +38,7 @@ options:
description:
- netconf vrf name
required: false
- default: none
+ default: default
state:
description:
- Specifies the state of the C(iosxr_netconf) resource on
@@ -75,7 +75,7 @@ import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import exec_command
-from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
+from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
from ansible.module_utils.six import iteritems
@@ -90,16 +90,12 @@ def map_obj_to_commands(updates, module):
if have['state'] == 'present':
commands.append('no netconf-yang agent ssh')
- if 'netconf_port' in have:
- commands.append('no ssh server netconf port %s' % have['netconf_port'])
+ if 'netconf_port' in have:
+ commands.append('no ssh server netconf port %s' % have['netconf_port'])
- if want['netconf_vrf']:
- for vrf in have['netconf_vrf']:
- if vrf == want['netconf_vrf']:
- commands.append('no ssh server netconf vrf %s' % vrf)
- else:
- for vrf in have['netconf_vrf']:
- commands.append('no ssh server netconf vrf %s' % vrf)
+ if have['netconf_vrf']:
+ for vrf in have['netconf_vrf']:
+ commands.append('no ssh server netconf vrf %s' % vrf)
else:
if have['state'] == 'absent':
commands.append('netconf-yang agent ssh')
@@ -131,9 +127,9 @@ def parse_port(config):
def map_config_to_obj(module):
obj = {'state': 'absent'}
- netconf_config = get_config(module, flags=['netconf-yang agent'])
+ netconf_config = get_config(module, config_filter='netconf-yang agent')
- ssh_config = get_config(module, flags=['ssh server'])
+ ssh_config = get_config(module, config_filter='ssh server')
ssh_config = [config_line for config_line in (line.strip() for line in ssh_config.splitlines()) if config_line]
obj['netconf_vrf'] = []
for config in ssh_config:
@@ -141,7 +137,7 @@ def map_config_to_obj(module):
obj.update({'netconf_port': parse_port(config)})
if 'netconf vrf' in config:
obj['netconf_vrf'].append(parse_vrf(config))
- if 'ssh' in netconf_config or 'netconf_port' in obj or obj['netconf_vrf']:
+ if 'ssh' in netconf_config and ('netconf_port' in obj or obj['netconf_vrf']):
obj.update({'state': 'present'})
if 'ssh' in netconf_config and 'netconf_port' not in obj:
@@ -176,7 +172,7 @@ def main():
"""
argument_spec = dict(
netconf_port=dict(type='int', default=830, aliases=['listens_on']),
- netconf_vrf=dict(aliases=['vrf']),
+ netconf_vrf=dict(aliases=['vrf'], default='default'),
state=dict(default='present', choices=['present', 'absent']),
)
argument_spec.update(iosxr_argument_spec)
@@ -185,7 +181,6 @@ def main():
supports_check_mode=True)
warnings = list()
- check_args(module, warnings)
result = {'changed': False, 'warnings': warnings}
@@ -197,10 +192,6 @@ def main():
if commands:
if not module.check_mode:
diff = load_config(module, commands, result['warnings'], commit=True)
- if diff:
- if module._diff:
- result['diff'] = {'prepared': diff}
- exec_command(module, 'exit')
result['changed'] = True
module.exit_json(**result)
diff --git a/lib/ansible/modules/network/iosxr/iosxr_system.py b/lib/ansible/modules/network/iosxr/iosxr_system.py
index 4de4fa8b3ae..3043156e222 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_system.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_system.py
@@ -108,18 +108,21 @@ import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
-from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
+from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
+
def diff_list(want, have):
adds = set(want).difference(have)
removes = set(have).difference(want)
return (adds, removes)
+
def map_obj_to_commands(want, have, module):
commands = list()
state = module.params['state']
- needs_update = lambda x: want.get(x) and (want.get(x) != have.get(x))
+ def needs_update(x):
+ return want.get(x) and (want.get(x) != have.get(x))
if state == 'absent':
if have['hostname'] != 'ios':
@@ -167,20 +170,24 @@ def map_obj_to_commands(want, have, module):
return commands
+
def parse_hostname(config):
match = re.search(r'^hostname (\S+)', config, re.M)
return match.group(1)
+
def parse_domain_name(config):
match = re.search(r'^domain name (\S+)', config, re.M)
if match:
return match.group(1)
+
def parse_lookup_source(config):
match = re.search(r'^domain lookup source-interface (\S+)', config, re.M)
if match:
return match.group(1)
+
def map_config_to_obj(module):
config = get_config(module)
return {
@@ -192,6 +199,7 @@ def map_config_to_obj(module):
'name_servers': re.findall(r'^domain name-server (\S+)', config, re.M)
}
+
def map_params_to_obj(module):
return {
'hostname': module.params['hostname'],
@@ -202,6 +210,7 @@ def map_params_to_obj(module):
'name_servers': module.params['name_servers']
}
+
def main():
""" Main entry point for Ansible module execution
"""
@@ -223,7 +232,6 @@ def main():
supports_check_mode=True)
warnings = list()
- check_args(module, warnings)
result = {'changed': False, 'warnings': warnings}
@@ -240,5 +248,6 @@ def main():
module.exit_json(**result)
+
if __name__ == "__main__":
main()
diff --git a/lib/ansible/modules/network/iosxr/iosxr_user.py b/lib/ansible/modules/network/iosxr/iosxr_user.py
index e4f0b16637e..0f514774db7 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_user.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_user.py
@@ -26,6 +26,7 @@ description:
either individual usernames or the aggregate of usernames in the
current running config. It also supports purging usernames from the
configuration that are not explicitly defined.
+extends_documentation_fragment: iosxr
notes:
- Tested against IOS XR 6.1.2
options:
@@ -166,7 +167,7 @@ from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.network.iosxr.iosxr import get_config, load_config
-from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, check_args
+from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec
try:
from base64 import b64decode
@@ -228,7 +229,7 @@ def map_obj_to_commands(updates, module):
def map_config_to_obj(module):
- data = get_config(module, flags=['username'])
+ data = get_config(module, config_filter='username')
users = data.strip().rstrip('!').split('!')
if not users:
@@ -453,8 +454,6 @@ def main():
'To set a user password use "configured_password" instead.'
)
- check_args(module, warnings)
-
result = {'changed': False}
want = map_params_to_obj(module)
diff --git a/lib/ansible/plugins/action/iosxr.py b/lib/ansible/plugins/action/iosxr.py
index 77c865e9220..96140b195a0 100644
--- a/lib/ansible/plugins/action/iosxr.py
+++ b/lib/ansible/plugins/action/iosxr.py
@@ -48,7 +48,17 @@ class ActionModule(_ActionModule):
elif self._play_context.connection == 'local':
provider = load_provider(iosxr_provider_spec, self._task.args)
pc = copy.deepcopy(self._play_context)
- pc.connection = 'network_cli'
+ if self._task.action in ['iosxr_netconf', 'iosxr_config', 'iosxr_command'] or \
+ (provider['transport'] == 'cli' and (self._task.action == 'iosxr_banner' or
+ self._task.action == 'iosxr_facts' or self._task.action == 'iosxr_logging' or
+ self._task.action == 'iosxr_system' or self._task.action == 'iosxr_user' or
+ self._task.action == 'iosxr_interface')):
+ pc.connection = 'network_cli'
+ pc.port = int(provider['port'] or self._play_context.port or 22)
+ else:
+ pc.connection = 'netconf'
+ pc.port = int(provider['port'] or self._play_context.port or 830)
+
pc.network_os = 'iosxr'
pc.remote_addr = provider['host'] or self._play_context.remote_addr
pc.port = int(provider['port'] or self._play_context.port or 22)
@@ -70,15 +80,16 @@ class ActionModule(_ActionModule):
# make sure we are in the right cli context which should be
# enable mode and not config module
- if socket_path is None:
- socket_path = self._connection.socket_path
+ if pc.connection == 'network_cli':
+ if socket_path is None:
+ socket_path = self._connection.socket_path
- conn = Connection(socket_path)
- out = conn.get_prompt()
- while to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
- display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
- conn.send_command('abort')
+ conn = Connection(socket_path)
out = conn.get_prompt()
+ while to_text(out, errors='surrogate_then_replace').strip().endswith(')#'):
+ display.vvvv('wrong context, sending exit to device', self._play_context.remote_addr)
+ conn.send_command('abort')
+ out = conn.get_prompt()
result = super(ActionModule, self).run(tmp, task_vars)
return result
diff --git a/lib/ansible/plugins/cliconf/iosxr.py b/lib/ansible/plugins/cliconf/iosxr.py
index f4196867ed5..0c602fe260d 100644
--- a/lib/ansible/plugins/cliconf/iosxr.py
+++ b/lib/ansible/plugins/cliconf/iosxr.py
@@ -56,14 +56,19 @@ class Cliconf(CliconfBase):
return device_info
- def get_config(self, source='running'):
+ def get_config(self, source='running', filter=None):
lookup = {'running': 'running-config'}
if source not in lookup:
return self.invalid_params("fetching configuration from %s is not supported" % source)
- return self.send_command(to_bytes(b'show %s' % lookup[source], errors='surrogate_or_strict'))
+ if filter:
+ cmd = to_bytes(b'show {0} {1}'.format(lookup[source], filter), errors='surrogate_or_strict')
+ else:
+ cmd = to_bytes(b'show {0}'.format(lookup[source]), errors='surrogate_or_strict')
+
+ return self.send_command(cmd)
def edit_config(self, command):
- for cmd in chain([b'configure'], to_list(command), [b'end']):
+ for cmd in chain(to_list(command)):
self.send_command(cmd)
def get(self, command, prompt=None, answer=None, sendonly=False):
diff --git a/lib/ansible/plugins/netconf/__init__.py b/lib/ansible/plugins/netconf/__init__.py
index f9febb5482b..e7558c46710 100644
--- a/lib/ansible/plugins/netconf/__init__.py
+++ b/lib/ansible/plugins/netconf/__init__.py
@@ -54,10 +54,9 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
problems.
List of supported rpc's:
+ :get: Retrieves running configuration and device state information
:get_config: Retrieves the specified configuration from the device
:edit_config: Loads the specified commands into the remote device
- :get: Execute specified command on remote device
- :get_capabilities: Retrieves device information and supported rpc methods
:commit: Load configuration from candidate to running
:discard_changes: Discard changes to candidate datastore
:validate: Validate the contents of the specified configuration.
@@ -65,6 +64,9 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
:unlock: Release a configuration lock, previously obtained with the lock operation.
:copy_config: create or replace an entire configuration datastore with the contents of another complete
configuration datastore.
+ :get-schema: Retrieves the required schema from the device
+ :get_capabilities: Retrieves device information and supported rpc methods
+
For JUNOS:
:execute_rpc: RPC to be execute on remote device
:load_configuration: Loads given configuration on device
@@ -100,7 +102,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
:source: name of the configuration datastore being queried
:filter: specifies the portion of the configuration to retrieve
(by default entire configuration is retrieved)"""
- return self.m.get_config(*args, **kwargs).data_xml
+ pass
@ensure_connected
def get(self, *args, **kwargs):
@@ -108,7 +110,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
*filter* specifies the portion of the configuration to retrieve
(by default entire configuration is retrieved)
"""
- return self.m.get(*args, **kwargs).data_xml
+ pass
@ensure_connected
def edit_config(self, *args, **kwargs):
@@ -122,10 +124,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
:error_option: if specified must be one of { `"stop-on-error"`, `"continue-on-error"`, `"rollback-on-error"` }
The `"rollback-on-error"` *error_option* depends on the `:rollback-on-error` capability.
"""
- try:
- return self.m.edit_config(*args, **kwargs).data_xml
- except RPCError as exc:
- raise Exception(to_xml(exc.xml))
+ pass
@ensure_connected
def validate(self, *args, **kwargs):
@@ -133,7 +132,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
:source: is the name of the configuration datastore being validated or `config`
element containing the configuration subtree to be validated
"""
- return self.m.validate(*args, **kwargs).data_xml
+ pass
@ensure_connected
def copy_config(self, *args, **kwargs):
@@ -162,7 +161,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
def discard_changes(self, *args, **kwargs):
"""Revert the candidate configuration to the currently running configuration.
Any uncommitted changes are discarded."""
- return self.m.discard_changes(*args, **kwargs).data_xml
+ pass
@ensure_connected
def commit(self, *args, **kwargs):
@@ -176,10 +175,7 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
:confirmed: whether this is a confirmed commit
:timeout: specifies the confirm timeout in seconds
"""
- try:
- return self.m.commit(*args, **kwargs).data_xml
- except RPCError as exc:
- raise Exception(to_xml(exc.xml))
+ pass
@ensure_connected
def validate(self, *args, **kwargs):
@@ -187,8 +183,18 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
:source: name of configuration data store"""
return self.m.validate(*args, **kwargs).data_xml
+ @ensure_connected
+ def get_schema(self, *args, **kwargs):
+ """Retrieves the required schema from the device
+ """
+ return self.m.get_schema(*args, **kwargs)
+
+ @ensure_connected
+ def locked(self, *args, **kwargs):
+ return self.m.locked(*args, **kwargs)
+
@abstractmethod
- def get_capabilities(self, commands):
+ def get_capabilities(self):
"""Retrieves device information and supported
rpc methods by device platform and return result
as a string
@@ -213,3 +219,5 @@ class NetconfBase(with_metaclass(ABCMeta, object)):
def fetch_file(self, source, destination):
"""Fetch file over scp from remote device"""
pass
+
+# TODO Restore .data_xml, when ncclient supports it for all platforms
diff --git a/lib/ansible/plugins/netconf/iosxr.py b/lib/ansible/plugins/netconf/iosxr.py
new file mode 100644
index 00000000000..e97a7dc3bc2
--- /dev/null
+++ b/lib/ansible/plugins/netconf/iosxr.py
@@ -0,0 +1,209 @@
+#
+# (c) 2017 Red Hat Inc.
+# (c) 2017 Kedar Kekan (kkekan@redhat.com)
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+#
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import json
+import re
+import sys
+import collections
+from io import BytesIO
+from ansible.module_utils.six import StringIO
+
+from ansible import constants as C
+from ansible.module_utils.network.iosxr.iosxr import build_xml
+from ansible.errors import AnsibleConnectionFailure, AnsibleError
+from ansible.plugins.netconf import NetconfBase
+from ansible.plugins.netconf import ensure_connected
+
+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")
+
+
+def transform_reply():
+ reply = '''
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ '''
+ if sys.version < '3':
+ return reply
+ else:
+ print("utf8")
+ return reply.encode('UTF-8')
+
+
+# Note: Workaround for ncclient 0.5.3
+def remove_namespaces(rpc_reply):
+ xslt = transform_reply()
+ parser = etree.XMLParser(remove_blank_text=True)
+ xslt_doc = etree.parse(BytesIO(xslt), parser)
+ transform = etree.XSLT(xslt_doc)
+
+ return etree.fromstring(str(transform(etree.parse(StringIO(str(rpc_reply))))))
+
+
+class Netconf(NetconfBase):
+
+ @ensure_connected
+ def get_device_info(self):
+ device_info = {}
+ device_info['network_os'] = 'iosxr'
+ install_meta = collections.OrderedDict()
+ install_meta.update([
+ ('boot-variables', {'xpath': 'install/boot-variables', 'tag': True}),
+ ('boot-variable', {'xpath': 'install/boot-variables/boot-variable', 'tag': True, 'lead': True}),
+ ('software', {'xpath': 'install/software', 'tag': True}),
+ ('alias-devices', {'xpath': 'install/software/alias-devices', 'tag': True}),
+ ('alias-device', {'xpath': 'install/software/alias-devices/alias-device', 'tag': True}),
+ ('m:device-name', {'xpath': 'install/software/alias-devices/alias-device/device-name', 'value': 'disk0:'}),
+ ])
+
+ install_filter = build_xml('install', install_meta, opcode='filter')
+
+ reply = self.get(install_filter)
+ ele_boot_variable = etree.fromstring(reply).find('.//boot-variable/boot-variable')
+ if ele_boot_variable:
+ device_info['network_os_image'] = re.split('[:|,]', ele_boot_variable.text)[1]
+ ele_package_name = etree.fromstring(reply).find('.//package-name')
+ if ele_package_name:
+ device_info['network_os_package'] = ele_package_name.text
+ device_info['network_os_version'] = re.split('-', ele_package_name.text)[-1]
+
+ hostname_filter = build_xml('host-names', opcode='filter')
+
+ reply = self.get(hostname_filter)
+ device_info['network_os_hostname'] = etree.fromstring(reply).find('.//host-name').text
+
+ return device_info
+
+ def get_capabilities(self):
+ result = dict()
+ result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes', 'validate', 'lock', 'unlock', 'get-schema']
+ result['network_api'] = 'netconf'
+ result['device_info'] = self.get_device_info()
+ result['server_capabilities'] = [c for c in self.m.server_capabilities]
+ 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('IOS-XR', c):
+ guessed_os = 'iosxr'
+ break
+
+ m.close_session()
+ return guessed_os
+
+ # TODO: change .xml to .data_xml, when ncclient supports data_xml on all platforms
+ @ensure_connected
+ def get(self, *args, **kwargs):
+ try:
+ response = self.m.get(*args, **kwargs)
+ return to_xml(remove_namespaces(response))
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def get_config(self, *args, **kwargs):
+ try:
+ response = self.m.get_config(*args, **kwargs)
+ return to_xml(remove_namespaces(response))
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def edit_config(self, *args, **kwargs):
+ try:
+ response = self.m.edit_config(*args, **kwargs)
+ return to_xml(remove_namespaces(response))
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def commit(self, *args, **kwargs):
+ try:
+ response = self.m.commit(*args, **kwargs)
+ return to_xml(remove_namespaces(response))
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def validate(self, *args, **kwargs):
+ try:
+ response = self.m.validate(*args, **kwargs)
+ return to_xml(remove_namespaces(response))
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def discard_changes(self, *args, **kwargs):
+ try:
+ response = self.m.discard_changes(*args, **kwargs)
+ return to_xml(remove_namespaces(response))
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
diff --git a/lib/ansible/plugins/netconf/junos.py b/lib/ansible/plugins/netconf/junos.py
index afb57777f64..cb61e62869e 100644
--- a/lib/ansible/plugins/netconf/junos.py
+++ b/lib/ansible/plugins/netconf/junos.py
@@ -152,3 +152,39 @@ class Netconf(NetconfBase):
def halt(self):
"""reboot the device"""
return self.m.halt().data_xml
+
+ @ensure_connected
+ def get(self, *args, **kwargs):
+ try:
+ return self.m.get(*args, **kwargs).data_xml
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def get_config(self, *args, **kwargs):
+ try:
+ return self.m.get_config(*args, **kwargs).data_xml
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def edit_config(self, *args, **kwargs):
+ try:
+ self.m.edit_config(*args, **kwargs).data_xml
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def commit(self, *args, **kwargs):
+ try:
+ return self.m.commit(*args, **kwargs).data_xml
+ except RPCError as exc:
+ raise Exception(to_xml(exc.xml))
+
+ @ensure_connected
+ def validate(self, *args, **kwargs):
+ return self.m.validate(*args, **kwargs).data_xml
+
+ @ensure_connected
+ def discard_changes(self, *args, **kwargs):
+ return self.m.discard_changes(*args, **kwargs).data_xml
diff --git a/lib/ansible/utils/module_docs_fragments/iosxr.py b/lib/ansible/utils/module_docs_fragments/iosxr.py
index abe0d41b6ca..775c9e3b3c7 100644
--- a/lib/ansible/utils/module_docs_fragments/iosxr.py
+++ b/lib/ansible/utils/module_docs_fragments/iosxr.py
@@ -64,6 +64,9 @@ options:
key used to authenticate the SSH session. If the value is not specified
in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE)
will be used instead.
+requirements:
+ - "ncclient >= 0.5.3 when using netconf"
+ - "lxml >= 4.1.1 when using netconf"
notes:
- For more information on using Ansible to manage Cisco devices see U(https://www.ansible.com/ansible-cisco).
"""
diff --git a/test/integration/targets/iosxr_banner/tasks/main.yaml b/test/integration/targets/iosxr_banner/tasks/main.yaml
index 415c99d8b12..af08869c922 100644
--- a/test/integration/targets/iosxr_banner/tasks/main.yaml
+++ b/test/integration/targets/iosxr_banner/tasks/main.yaml
@@ -1,2 +1,3 @@
---
- { include: cli.yaml, tags: ['cli'] }
+- { include: netconf.yaml, tags: ['netconf'] }
diff --git a/test/integration/targets/iosxr_banner/tasks/netconf.yaml b/test/integration/targets/iosxr_banner/tasks/netconf.yaml
new file mode 100644
index 00000000000..1286b354228
--- /dev/null
+++ b/test/integration/targets/iosxr_banner/tasks/netconf.yaml
@@ -0,0 +1,16 @@
+---
+- name: collect all netconf test cases
+ find:
+ paths: "{{ role_path }}/tests/netconf"
+ patterns: "{{ testcase }}.yaml"
+ register: test_cases
+ delegate_to: localhost
+
+- name: set test_items
+ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test case
+ include: "{{ test_case_to_run }}"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/test/integration/targets/iosxr_banner/tests/cli/basic-login.yaml b/test/integration/targets/iosxr_banner/tests/cli/basic-login.yaml
index 1f6749f2f94..032fa1f4eaf 100644
--- a/test/integration/targets/iosxr_banner/tests/cli/basic-login.yaml
+++ b/test/integration/targets/iosxr_banner/tests/cli/basic-login.yaml
@@ -2,6 +2,7 @@
- name: setup - remove login
iosxr_banner:
banner: login
+ provider: "{{ cli }}"
state: absent
- name: Set login
@@ -11,6 +12,7 @@
this is my login banner
that has a multiline
string
+ provider: "{{ cli }}"
state: present
register: result
@@ -30,6 +32,7 @@
this is my login banner
that has a multiline
string
+ provider: "{{ cli }}"
state: present
register: result
diff --git a/test/integration/targets/iosxr_banner/tests/cli/basic-motd.yaml b/test/integration/targets/iosxr_banner/tests/cli/basic-motd.yaml
index 877097fc891..67b80eb0238 100644
--- a/test/integration/targets/iosxr_banner/tests/cli/basic-motd.yaml
+++ b/test/integration/targets/iosxr_banner/tests/cli/basic-motd.yaml
@@ -3,6 +3,7 @@
iosxr_banner:
banner: motd
state: absent
+ provider: "{{ cli }}"
- name: Set motd
iosxr_banner:
@@ -11,6 +12,7 @@
this is my motd banner
that has a multiline
string
+ provider: "{{ cli }}"
state: present
register: result
@@ -30,6 +32,7 @@
this is my motd banner
that has a multiline
string
+ provider: "{{ cli }}"
state: present
register: result
diff --git a/test/integration/targets/iosxr_banner/tests/cli/basic-no-login.yaml b/test/integration/targets/iosxr_banner/tests/cli/basic-no-login.yaml
index f2784e86491..539baf23f50 100644
--- a/test/integration/targets/iosxr_banner/tests/cli/basic-no-login.yaml
+++ b/test/integration/targets/iosxr_banner/tests/cli/basic-no-login.yaml
@@ -5,12 +5,14 @@
text: |
Junk login banner
over multiple lines
+ provider: "{{ cli }}"
state: present
- name: remove login
iosxr_banner:
banner: login
state: absent
+ provider: "{{ cli }}"
register: result
- debug:
@@ -25,6 +27,7 @@
iosxr_banner:
banner: login
state: absent
+ provider: "{{ cli }}"
register: result
- assert:
diff --git a/test/integration/targets/iosxr_banner/tests/netconf/basic-login.yaml b/test/integration/targets/iosxr_banner/tests/netconf/basic-login.yaml
new file mode 100644
index 00000000000..cb78654e9dc
--- /dev/null
+++ b/test/integration/targets/iosxr_banner/tests/netconf/basic-login.yaml
@@ -0,0 +1,49 @@
+---
+- name: Enable Netconf service
+ iosxr_netconf:
+ netconf_port: 830
+ netconf_vrf: 'default'
+ state: present
+ register: result
+
+- name: setup - remove login
+ iosxr_banner:
+ banner: login
+ provider: "{{ netconf }}"
+ state: absent
+
+- name: Set login
+ iosxr_banner:
+ banner: login
+ text: |
+ this is my login banner
+ that has a multiline
+ string
+ provider: "{{ netconf }}"
+ state: present
+ register: result
+
+- debug:
+ msg: "{{ result }}"
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'this is my login banner' in result.commands"
+ - "'that has a multiline' in result.commands"
+
+- name: Set login again (idempotent)
+ iosxr_banner:
+ banner: login
+ text: |
+ this is my login banner
+ that has a multiline
+ string
+ provider: "{{ netconf }}"
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result.changed == false"
+ - "result.commands | length == 0"
diff --git a/test/integration/targets/iosxr_banner/tests/netconf/basic-motd.yaml b/test/integration/targets/iosxr_banner/tests/netconf/basic-motd.yaml
new file mode 100644
index 00000000000..b50147c9750
--- /dev/null
+++ b/test/integration/targets/iosxr_banner/tests/netconf/basic-motd.yaml
@@ -0,0 +1,49 @@
+---
+- name: Enable Netconf service
+ iosxr_netconf:
+ netconf_port: 830
+ netconf_vrf: 'default'
+ state: present
+ register: result
+
+- name: setup - remove motd
+ iosxr_banner:
+ banner: motd
+ state: absent
+ provider: "{{ netconf }}"
+
+- name: Set motd
+ iosxr_banner:
+ banner: motd
+ text: |
+ this is my motd banner
+ that has a multiline
+ string
+ provider: "{{ netconf }}"
+ state: present
+ register: result
+
+- debug:
+ msg: "{{ result }}"
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'this is my motd banner' in result.commands"
+ - "'that has a multiline' in result.commands"
+
+- name: Set motd again (idempotent)
+ iosxr_banner:
+ banner: motd
+ text: |
+ this is my motd banner
+ that has a multiline
+ string
+ provider: "{{ netconf }}"
+ state: present
+ register: result
+
+- assert:
+ that:
+ - "result.changed == false"
+ - "result.commands | length == 0"
diff --git a/test/integration/targets/iosxr_banner/tests/netconf/basic-no-login.yaml b/test/integration/targets/iosxr_banner/tests/netconf/basic-no-login.yaml
new file mode 100644
index 00000000000..e74a0e628d4
--- /dev/null
+++ b/test/integration/targets/iosxr_banner/tests/netconf/basic-no-login.yaml
@@ -0,0 +1,43 @@
+---
+- name: Enable Netconf service
+ iosxr_netconf:
+ netconf_port: 830
+ netconf_vrf: 'default'
+ state: present
+ register: result
+
+- name: Setup
+ iosxr_banner:
+ banner: login
+ text: |
+ Junk login banner
+ over multiple lines
+ provider: "{{ netconf }}"
+ state: present
+
+- name: remove login
+ iosxr_banner:
+ banner: login
+ state: absent
+ provider: "{{ netconf }}"
+ register: result
+
+- debug:
+ msg: "{{ result }}"
+
+- assert:
+ that:
+ - "result.changed == true"
+ - "'xc:operation=\"delete\"' in result.commands"
+
+- name: remove login (idempotent)
+ iosxr_banner:
+ banner: login
+ state: absent
+ provider: "{{ netconf }}"
+ register: result
+
+- assert:
+ that:
+ - "result.changed == false"
+ - "result.commands | length == 0"
diff --git a/test/integration/targets/iosxr_command/tests/cli/invalid.yaml b/test/integration/targets/iosxr_command/tests/cli/invalid.yaml
index 9b88f566c20..c128c0c1001 100644
--- a/test/integration/targets/iosxr_command/tests/cli/invalid.yaml
+++ b/test/integration/targets/iosxr_command/tests/cli/invalid.yaml
@@ -3,7 +3,7 @@
- name: run invalid command
iosxr_command:
- commands: ['show foo']
+ commands: [{command: 'show foo', prompt: 'fooprompt', answer: 'yes'}]
register: result
ignore_errors: yes
@@ -15,7 +15,7 @@
iosxr_command:
commands:
- show version
- - show foo
+ - [{command: 'show foo', prompt: 'fooprompt', answer: 'yes'}]
register: result
ignore_errors: yes
diff --git a/test/integration/targets/iosxr_facts/tests/cli/all_facts.yaml b/test/integration/targets/iosxr_facts/tests/cli/all_facts.yaml
index 806657e7e2a..3eeb8c0ed35 100644
--- a/test/integration/targets/iosxr_facts/tests/cli/all_facts.yaml
+++ b/test/integration/targets/iosxr_facts/tests/cli/all_facts.yaml
@@ -6,6 +6,7 @@
iosxr_facts:
gather_subset:
- all
+ provider: "{{ cli }}"
register: result
diff --git a/test/integration/targets/iosxr_facts/tests/cli/default_facts.yaml b/test/integration/targets/iosxr_facts/tests/cli/default_facts.yaml
index 118637cfff7..ebf9aa26523 100644
--- a/test/integration/targets/iosxr_facts/tests/cli/default_facts.yaml
+++ b/test/integration/targets/iosxr_facts/tests/cli/default_facts.yaml
@@ -4,6 +4,7 @@
- name: test getting default facts
iosxr_facts:
+ provider: "{{ cli }}"
register: result
- assert:
diff --git a/test/integration/targets/iosxr_facts/tests/cli/invalid_subset.yaml b/test/integration/targets/iosxr_facts/tests/cli/invalid_subset.yaml
index 1fd902e55e3..5bfd68708b5 100644
--- a/test/integration/targets/iosxr_facts/tests/cli/invalid_subset.yaml
+++ b/test/integration/targets/iosxr_facts/tests/cli/invalid_subset.yaml
@@ -6,6 +6,7 @@
iosxr_facts:
gather_subset:
- "foobar"
+ provider: "{{ cli }}"
register: result
ignore_errors: true
@@ -28,6 +29,7 @@
gather_subset:
- "!hardware"
- "hardware"
+ provider: "{{ cli }}"
register: result
ignore_errors: true
diff --git a/test/integration/targets/iosxr_facts/tests/cli/not_hardware.yaml b/test/integration/targets/iosxr_facts/tests/cli/not_hardware.yaml
index 1019c22dd38..cd9c60e29c6 100644
--- a/test/integration/targets/iosxr_facts/tests/cli/not_hardware.yaml
+++ b/test/integration/targets/iosxr_facts/tests/cli/not_hardware.yaml
@@ -6,6 +6,7 @@
iosxr_facts:
gather_subset:
- "!hardware"
+ provider: "{{ cli }}"
register: result
- assert:
diff --git a/test/integration/targets/iosxr_interface/tests/cli/basic.yaml b/test/integration/targets/iosxr_interface/tests/cli/basic.yaml
index 015f9718b2c..a1cb2b63013 100644
--- a/test/integration/targets/iosxr_interface/tests/cli/basic.yaml
+++ b/test/integration/targets/iosxr_interface/tests/cli/basic.yaml
@@ -5,6 +5,7 @@
iosxr_interface:
name: GigabitEthernet0/0/0/2
state: absent
+ provider: "{{ cli }}"
register: result
@@ -13,6 +14,7 @@
name: GigabitEthernet0/0/0/2
description: test-interface-initial
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -25,6 +27,7 @@
name: GigabitEthernet0/0/0/2
description: test-interface-initial
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -39,6 +42,7 @@
duplex: half
mtu: 512
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -57,6 +61,7 @@
duplex: full
mtu: 256
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -75,6 +80,7 @@
duplex: full
mtu: 256
state: present
+ provider: "{{ cli }}"
register: result
- assert:
that:
@@ -84,6 +90,7 @@
iosxr_interface:
name: GigabitEthernet0/0/0/2
enabled: False
+ provider: "{{ cli }}"
register: result
- assert:
@@ -95,6 +102,7 @@
iosxr_interface:
name: GigabitEthernet0/0/0/2
enabled: True
+ provider: "{{ cli }}"
register: result
- assert:
@@ -107,6 +115,7 @@
name: GigabitEthernet0/0/0/3
description: test-interface-initial
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -120,6 +129,7 @@
- name: GigabitEthernet0/0/0/3
- name: GigabitEthernet0/0/0/2
state: absent
+ provider: "{{ cli }}"
- name: Add interface aggregate
iosxr_interface:
@@ -129,6 +139,7 @@
speed: 100
duplex: full
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -152,6 +163,7 @@
speed: 100
duplex: full
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -165,6 +177,7 @@
- name: GigabitEthernet0/0/0/2
enabled: False
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -180,6 +193,7 @@
- name: GigabitEthernet0/0/0/2
enabled: True
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -194,6 +208,7 @@
- name: GigabitEthernet0/0/0/4
- name: GigabitEthernet0/0/0/5
description: test-interface-initial
+ provider: "{{ cli }}"
register: result
- name: Create interface aggregate
@@ -204,6 +219,7 @@
- name: GigabitEthernet0/0/0/5
description: test_interface_2
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -218,6 +234,7 @@
- name: GigabitEthernet0/0/0/4
- name: GigabitEthernet0/0/0/5
state: absent
+ provider: "{{ cli }}"
register: result
- assert:
@@ -232,6 +249,7 @@
- name: GigabitEthernet0/0/0/4
- name: GigabitEthernet0/0/0/5
state: absent
+ provider: "{{ cli }}"
register: result
- assert:
diff --git a/test/integration/targets/iosxr_interface/tests/cli/intent.yaml b/test/integration/targets/iosxr_interface/tests/cli/intent.yaml
index bbf96d1f069..c134428b4ca 100644
--- a/test/integration/targets/iosxr_interface/tests/cli/intent.yaml
+++ b/test/integration/targets/iosxr_interface/tests/cli/intent.yaml
@@ -7,6 +7,7 @@
description: test_interface_1
enabled: True
state: present
+ provider: "{{ cli }}"
register: result
- name: Check intent arguments
@@ -14,6 +15,7 @@
name: GigabitEthernet0/0/0/1
state: up
delay: 20
+ provider: "{{ cli }}"
register: result
- assert:
@@ -24,6 +26,7 @@
iosxr_interface:
name: GigabitEthernet0/0/0/1
state: down
+ provider: "{{ cli }}"
ignore_errors: yes
register: result
@@ -38,6 +41,7 @@
enabled: False
state: down
delay: 20
+ provider: "{{ cli }}"
register: result
- assert:
@@ -49,6 +53,7 @@
name: GigabitEthernet0/0/0/1
enabled: False
state: up
+ provider: "{{ cli }}"
ignore_errors: yes
register: result
@@ -64,6 +69,7 @@
enabled: True
state: up
delay: 20
+ provider: "{{ cli }}"
ignore_errors: yes
register: result
diff --git a/test/integration/targets/iosxr_logging/tests/cli/basic.yaml b/test/integration/targets/iosxr_logging/tests/cli/basic.yaml
index f608ad927ab..5ef97cd9ab8 100644
--- a/test/integration/targets/iosxr_logging/tests/cli/basic.yaml
+++ b/test/integration/targets/iosxr_logging/tests/cli/basic.yaml
@@ -5,12 +5,14 @@
dest: hostnameprefix
name: 172.16.0.1
state: absent
+ provider: "{{ cli }}"
- name: Remove console logging
iosxr_logging:
dest: console
level: warning
state: absent
+ provider: "{{ cli }}"
register: result
- name: Remove buffer
@@ -18,6 +20,7 @@
dest: buffered
size: 4800000
state: absent
+ provider: "{{ cli }}"
register: result
# Start tests
@@ -26,6 +29,7 @@
dest: hostnameprefix
name: 172.16.0.1
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -39,6 +43,7 @@
dest: hostnameprefix
name: 172.16.0.1
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -50,6 +55,7 @@
dest: hostnameprefix
name: 172.16.0.1
state: absent
+ provider: "{{ cli }}"
register: result
- assert:
@@ -62,6 +68,7 @@
dest: hostnameprefix
name: 172.16.0.1
state: absent
+ provider: "{{ cli }}"
register: result
- assert:
@@ -73,6 +80,7 @@
dest: console
level: warning
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -84,6 +92,7 @@
iosxr_logging:
dest: buffered
size: 4800000
+ provider: "{{ cli }}"
register: result
- assert:
@@ -96,6 +105,7 @@
aggregate:
- { dest: console, level: notifications }
- { dest: buffered, size: 4700000 }
+ provider: "{{ cli }}"
register: result
- assert:
@@ -110,6 +120,7 @@
- { dest: console, level: notifications }
- { dest: buffered, size: 4700000 }
state: absent
+ provider: "{{ cli }}"
register: result
- assert:
diff --git a/test/integration/targets/iosxr_system/tests/cli/set_domain_list.yaml b/test/integration/targets/iosxr_system/tests/cli/set_domain_list.yaml
index acbe9b303e8..6b3db147c5a 100644
--- a/test/integration/targets/iosxr_system/tests/cli/set_domain_list.yaml
+++ b/test/integration/targets/iosxr_system/tests/cli/set_domain_list.yaml
@@ -7,12 +7,14 @@
- no ip domain-list ansible.com
- no ip domain-list redhat.com
match: none
+ provider: "{{ cli }}"
- name: configure domain_search
iosxr_system:
domain_search:
- ansible.com
- redhat.com
+ provider: "{{ cli }}"
register: result
- assert:
@@ -26,6 +28,7 @@
domain_search:
- ansible.com
- redhat.com
+ provider: "{{ cli }}"
register: result
- assert:
@@ -36,6 +39,7 @@
iosxr_system:
domain_search:
- ansible.com
+ provider: "{{ cli }}"
register: result
- assert:
@@ -47,6 +51,7 @@
iosxr_system:
domain_search:
- ansible.com
+ provider: "{{ cli }}"
register: result
- assert:
@@ -58,6 +63,7 @@
domain_search:
- ansible.com
- redhat.com
+ provider: "{{ cli }}"
register: result
- assert:
@@ -70,6 +76,7 @@
domain_search:
- ansible.com
- redhat.com
+ provider: "{{ cli }}"
register: result
- assert:
@@ -81,6 +88,7 @@
domain_search:
- ansible.com
- eng.ansible.com
+ provider: "{{ cli }}"
register: result
- assert:
@@ -95,6 +103,7 @@
domain_search:
- ansible.com
- eng.ansible.com
+ provider: "{{ cli }}"
register: result
- assert:
@@ -108,5 +117,6 @@
- no domain list redhat.com
- no domain list eng.ansible.com
match: none
+ provider: "{{ cli }}"
- debug: msg="END cli/set_domain_search.yaml"
diff --git a/test/integration/targets/iosxr_system/tests/cli/set_domain_name.yaml b/test/integration/targets/iosxr_system/tests/cli/set_domain_name.yaml
index 5c6a0e2d7ed..fc14aaaa006 100644
--- a/test/integration/targets/iosxr_system/tests/cli/set_domain_name.yaml
+++ b/test/integration/targets/iosxr_system/tests/cli/set_domain_name.yaml
@@ -5,10 +5,12 @@
iosxr_config:
lines: no domain name
match: none
+ provider: "{{ cli }}"
- name: configure domain_name
iosxr_system:
domain_name: eng.ansible.com
+ provider: "{{ cli }}"
register: result
- assert:
@@ -18,6 +20,7 @@
- name: verify domain_name
iosxr_system:
domain_name: eng.ansible.com
+ provider: "{{ cli }}"
register: result
- assert:
@@ -28,5 +31,6 @@
iosxr_config:
lines: no domain name
match: none
+ provider: "{{ cli }}"
- debug: msg="END cli/set_domain_name.yaml"
diff --git a/test/integration/targets/iosxr_system/tests/cli/set_hostname.yaml b/test/integration/targets/iosxr_system/tests/cli/set_hostname.yaml
index 02b5f23e068..d9fd02212b6 100644
--- a/test/integration/targets/iosxr_system/tests/cli/set_hostname.yaml
+++ b/test/integration/targets/iosxr_system/tests/cli/set_hostname.yaml
@@ -5,10 +5,12 @@
iosxr_config:
lines: hostname switch
match: none
+ provider: "{{ cli }}"
- name: configure hostname
iosxr_system:
hostname: foo
+ provider: "{{ cli }}"
register: result
- assert:
@@ -18,6 +20,7 @@
- name: verify hostname
iosxr_system:
hostname: foo
+ provider: "{{ cli }}"
register: result
- assert:
@@ -28,5 +31,6 @@
iosxr_config:
lines: "hostname {{ inventory_hostname }}"
match: none
+ provider: "{{ cli }}"
- debug: msg="END cli/set_hostname.yaml"
diff --git a/test/integration/targets/iosxr_system/tests/cli/set_lookup_source.yaml b/test/integration/targets/iosxr_system/tests/cli/set_lookup_source.yaml
index 56b6b1fb706..dffe1373584 100644
--- a/test/integration/targets/iosxr_system/tests/cli/set_lookup_source.yaml
+++ b/test/integration/targets/iosxr_system/tests/cli/set_lookup_source.yaml
@@ -7,10 +7,12 @@
- no domain lookup source-interface Loopback10
# - vrf ansible
match: none
+ provider: "{{ cli }}"
- name: configure lookup_source
iosxr_system:
lookup_source: Loopback10
+ provider: "{{ cli }}"
register: result
- assert:
@@ -21,6 +23,7 @@
- name: verify lookup_source
iosxr_system:
lookup_source: Loopback10
+ provider: "{{ cli }}"
register: result
- assert:
@@ -58,5 +61,6 @@
- no domain lookup source-interface Loopback10
- no vrf ansible
match: none
+ provider: "{{ cli }}"
- debug: msg="END cli/set_lookup_source.yaml"
diff --git a/test/integration/targets/iosxr_system/tests/cli/set_name_servers.yaml b/test/integration/targets/iosxr_system/tests/cli/set_name_servers.yaml
index e0219be9cf8..bed28f63943 100644
--- a/test/integration/targets/iosxr_system/tests/cli/set_name_servers.yaml
+++ b/test/integration/targets/iosxr_system/tests/cli/set_name_servers.yaml
@@ -8,6 +8,7 @@
- no ip name-server 2.2.2.2
- no ip name-server 3.3.3.3
match: none
+ provider: "{{ cli }}"
- name: configure name_servers
iosxr_system:
@@ -15,6 +16,7 @@
- 1.1.1.1
- 2.2.2.2
- 3.3.3.3
+ provider: "{{ cli }}"
register: result
- assert:
@@ -31,6 +33,7 @@
- 1.1.1.1
- 2.2.2.2
- 3.3.3.3
+ provider: "{{ cli }}"
register: result
- assert:
@@ -69,6 +72,7 @@
name_servers:
- 1.1.1.1
- 2.2.2.2
+ provider: "{{ cli }}"
register: result
- assert:
diff --git a/test/integration/targets/iosxr_user/tests/cli/auth.yaml b/test/integration/targets/iosxr_user/tests/cli/auth.yaml
index 46ed0e16698..4aa4adfe25e 100644
--- a/test/integration/targets/iosxr_user/tests/cli/auth.yaml
+++ b/test/integration/targets/iosxr_user/tests/cli/auth.yaml
@@ -5,6 +5,7 @@
name: auth_user
state: present
configured_password: pass123
+ provider: "{{ cli }}"
- name: test login
expect:
@@ -30,6 +31,7 @@
name: auth_user
state: present
public_key_contents: "{{ lookup('file', \"{{ role_path }}/files/public.pub\") }}"
+ provider: "{{ cli }}"
- name: test login with private key
expect:
@@ -40,6 +42,7 @@
- name: remove user and key
iosxr_user:
name: auth_user
+ provider: "{{ cli }}"
state: absent
- name: test login with private key (should fail, no user)
@@ -55,6 +58,7 @@
name: auth_user
state: present
public_key: "{{ role_path }}/files/public.pub"
+ provider: "{{ cli }}"
- name: test login with private key
expect:
@@ -68,6 +72,7 @@
name: auth_user
state: present
public_key_contents: "{{ lookup('file', \"{{ role_path }}/files/public2.pub\") }}"
+ provider: "{{ cli }}"
# FIXME: pexpect fails with OSError: [Errno 5] Input/output error
- name: test login with invalid private key (should fail)
@@ -88,4 +93,5 @@
iosxr_user:
name: auth_user
state: absent
+ provider: "{{ cli }}"
register: result
diff --git a/test/integration/targets/iosxr_user/tests/cli/basic.yaml b/test/integration/targets/iosxr_user/tests/cli/basic.yaml
index eba431b1308..f1c614d82be 100644
--- a/test/integration/targets/iosxr_user/tests/cli/basic.yaml
+++ b/test/integration/targets/iosxr_user/tests/cli/basic.yaml
@@ -5,12 +5,14 @@
- no username ansibletest1
- no username ansibletest2
- no username ansibletest3
+ provider: "{{ cli }}"
- name: Create user (SetUp)
iosxr_user:
name: ansibletest1
configured_password: test
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -25,6 +27,7 @@
configured_password: test
update_password: always
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -39,6 +42,7 @@
configured_password: test
update_password: on_create
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -53,6 +57,7 @@
update_password: on_create
group: sysadmin
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -68,6 +73,7 @@
update_password: on_create
group: sysadmin
state: present
+ provider: "{{ cli }}"
register: result
- assert:
@@ -83,6 +89,7 @@
configured_password: test
state: present
group: sysadmin
+ provider: "{{ cli }}"
register: result
- assert:
@@ -103,6 +110,7 @@
configured_password: test
state: present
group: sysadmin
+ provider: "{{ cli }}"
register: result
- assert:
@@ -122,6 +130,7 @@
update_password: on_create
state: present
group: sysadmin
+ provider: "{{ cli }}"
register: result
- assert:
@@ -136,6 +145,7 @@
- name: ansibletest2
- name: ansibletest3
state: absent
+ provider: "{{ cli }}"
register: result
- assert:
@@ -150,6 +160,7 @@
- name: ansibletest2
- name: ansibletest3
state: absent
+ provider: "{{ cli }}"
register: result
- assert:
diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt
index c9cd855ad09..53c281892a2 100644
--- a/test/sanity/pep8/legacy-files.txt
+++ b/test/sanity/pep8/legacy-files.txt
@@ -182,10 +182,6 @@ lib/ansible/modules/network/ios/ios_command.py
lib/ansible/modules/network/ios/ios_facts.py
lib/ansible/modules/network/ios/ios_system.py
lib/ansible/modules/network/ios/ios_vrf.py
-lib/ansible/modules/network/iosxr/iosxr_command.py
-lib/ansible/modules/network/iosxr/iosxr_config.py
-lib/ansible/modules/network/iosxr/iosxr_facts.py
-lib/ansible/modules/network/iosxr/iosxr_system.py
lib/ansible/modules/network/netvisor/pn_cluster.py
lib/ansible/modules/network/netvisor/pn_ospfarea.py
lib/ansible/modules/network/netvisor/pn_vlag.py
diff --git a/test/units/modules/network/iosxr/test_iosxr_command.py b/test/units/modules/network/iosxr/test_iosxr_command.py
index 3f1a0e6aa41..a9393e86718 100644
--- a/test/units/modules/network/iosxr/test_iosxr_command.py
+++ b/test/units/modules/network/iosxr/test_iosxr_command.py
@@ -32,13 +32,13 @@ class TestIosxrCommandModule(TestIosxrModule):
def setUp(self):
super(TestIosxrCommandModule, self).setUp()
- self.mock_run_commands = patch('ansible.modules.network.iosxr.iosxr_command.run_commands')
- self.run_commands = self.mock_run_commands.start()
+ self.mock_run_command = patch('ansible.modules.network.iosxr.iosxr_command.run_command')
+ self.run_command = self.mock_run_command.start()
def tearDown(self):
super(TestIosxrCommandModule, self).tearDown()
- self.mock_run_commands.stop()
+ self.mock_run_command.stop()
def load_fixtures(self, commands=None):
@@ -49,13 +49,13 @@ class TestIosxrCommandModule(TestIosxrModule):
for item in commands:
try:
command = item['command']
- except ValueError:
+ except Exception:
command = item
filename = str(command).replace(' ', '_')
output.append(load_fixture(filename))
return output
- self.run_commands.side_effect = load_from_file
+ self.run_command.side_effect = load_from_file
def test_iosxr_command_simple(self):
set_module_args(dict(commands=['show version']))
@@ -78,13 +78,13 @@ class TestIosxrCommandModule(TestIosxrModule):
wait_for = 'result[0] contains "test string"'
set_module_args(dict(commands=['show version'], wait_for=wait_for))
self.execute_module(failed=True)
- self.assertEqual(self.run_commands.call_count, 10)
+ self.assertEqual(self.run_command.call_count, 10)
def test_iosxr_command_retries(self):
wait_for = 'result[0] contains "test string"'
set_module_args(dict(commands=['show version'], wait_for=wait_for, retries=2))
self.execute_module(failed=True)
- self.assertEqual(self.run_commands.call_count, 2)
+ self.assertEqual(self.run_command.call_count, 2)
def test_iosxr_command_match_any(self):
wait_for = ['result[0] contains "Cisco IOS"',
diff --git a/test/units/modules/network/iosxr/test_iosxr_facts.py b/test/units/modules/network/iosxr/test_iosxr_facts.py
index 717f403009f..8b3233d0cf2 100644
--- a/test/units/modules/network/iosxr/test_iosxr_facts.py
+++ b/test/units/modules/network/iosxr/test_iosxr_facts.py
@@ -34,14 +34,14 @@ class TestIosxrFacts(TestIosxrModule):
def setUp(self):
super(TestIosxrFacts, self).setUp()
- self.mock_run_commands = patch(
- 'ansible.modules.network.iosxr.iosxr_facts.run_commands')
- self.run_commands = self.mock_run_commands.start()
+ self.mock_run_command = patch(
+ 'ansible.modules.network.iosxr.iosxr_facts.run_command')
+ self.run_command = self.mock_run_command.start()
def tearDown(self):
super(TestIosxrFacts, self).tearDown()
- self.mock_run_commands.stop()
+ self.mock_run_command.stop()
def load_fixtures(self, commands=None):
@@ -60,7 +60,7 @@ class TestIosxrFacts(TestIosxrModule):
output.append(load_fixture(filename))
return output
- self.run_commands.side_effect = load_from_file
+ self.run_command.side_effect = load_from_file
def test_iosxr_facts_gather_subset_default(self):
set_module_args(dict())