refactor iosxr_system for cliconf and netconf (#34749)

* * refactor iosxr_system for cliconf and netconf

* * Fix unit tests and sanity issues
pull/34939/merge
Kedar Kekan 7 years ago committed by Chris Alfonso
parent 0f692f1fe7
commit 93acd7c651

@ -84,14 +84,16 @@ class NetconfConnection(Connection):
warnings = [] warnings = []
for error in error_list: for error in error_list:
try: message_ele = error.find('./nc:error-message', NS_MAP)
message = error.find('./nc:error-message', NS_MAP).text
except Exception: if message_ele is None:
message = error.find('./nc:error-info', NS_MAP).text message_ele = error.find('./nc:error-info', NS_MAP)
message = message_ele.text if message_ele is not None else None
severity = error.find('./nc:error-severity', NS_MAP).text severity = error.find('./nc:error-severity', NS_MAP).text
if severity == 'warning' and self.ignore_warning: if severity == 'warning' and self.ignore_warning and message is not None:
warnings.append(message) warnings.append(message)
else: else:
raise ConnectionError(to_text(rpc_error, errors='surrogate_then_replace')) raise ConnectionError(to_text(rpc_error, errors='surrogate_then_replace'))

@ -137,9 +137,7 @@ def build_xml_subtree(container_ele, xmap, param=None, opcode=None):
meta_subtree = list() meta_subtree = list()
for key, meta in xmap.items(): for key, meta in xmap.items():
candidates = meta.get('xpath', "").split("/") candidates = meta.get('xpath', "").split("/")
if container_ele.tag == candidates[-2]: if container_ele.tag == candidates[-2]:
parent = container_ele parent = container_ele
elif sub_root.tag == candidates[-2]: elif sub_root.tag == candidates[-2]:
@ -249,14 +247,13 @@ def build_xml(container, xmap=None, params=None, opcode=None):
container_ele = etree.SubElement(root, container, nsmap=NS_DICT[container.upper() + "_NSMAP"]) container_ele = etree.SubElement(root, container, nsmap=NS_DICT[container.upper() + "_NSMAP"])
if xmap: if xmap is not None:
if not params: if params is None:
build_xml_subtree(container_ele, xmap) build_xml_subtree(container_ele, xmap, opcode=opcode)
else: else:
subtree_list = list() subtree_list = list()
for param in to_list(params): for param in to_list(params):
subtree_ele = build_xml_subtree(container_ele, xmap, param, opcode=opcode) subtree_ele = build_xml_subtree(container_ele, xmap, param=param, opcode=opcode)
if subtree_ele is not None: if subtree_ele is not None:
subtree_list.append(subtree_ele) subtree_list.append(subtree_ele)

@ -15,20 +15,28 @@ DOCUMENTATION = """
--- ---
module: iosxr_system module: iosxr_system
version_added: "2.3" version_added: "2.3"
author: "Peter Sprygada (@privateip)" author:
- "Peter Sprygada (@privateip)"
- "Kedar Kekan @kedarX"
short_description: Manage the system attributes on Cisco IOS XR devices short_description: Manage the system attributes on Cisco IOS XR devices
description: description:
- This module provides declarative management of node system attributes - This module provides declarative management of node system attributes
on Cisco IOS XR devices. It provides an option to configure host system on Cisco IOS XR devices. It provides an option to configure host system
parameters or remove those parameters from the device active parameters or remove those parameters from the device active
configuration. configuration.
extends_documentation_fragment: iosxr extends_documentation_fragment: iosxr
notes: notes:
- Tested against IOS XR 6.1.2 - Tested against IOS XRv 6.1.2
- name-servers I(state=absent) operation with C(netconf) transport is a success, but with rpc-error. This is
due to XR platform issue. Recommended to use I(ignore_errors) option with the task as a workaround.
options: options:
hostname: hostname:
description: description:
- Configure the device hostname parameter. This option takes an ASCII string value. - Configure the device hostname parameter. This option takes an ASCII string value.
vrf:
description:
- VRF name for domain services
version_added: 2.5
domain_name: domain_name:
description: description:
- Configure the IP domain name - Configure the IP domain name
@ -73,7 +81,7 @@ options:
""" """
EXAMPLES = """ EXAMPLES = """
- name: configure hostname and domain-name - name: configure hostname and domain-name (default vrf=default)
iosxr_system: iosxr_system:
hostname: iosxr01 hostname: iosxr01
domain_name: test.example.com domain_name: test.example.com
@ -83,11 +91,26 @@ EXAMPLES = """
- cisco.com - cisco.com
- name: remove configuration - name: remove configuration
iosxr_system: iosxr_system:
hostname: iosxr01
domain_name: test.example.com
domain-search:
- ansible.com
- redhat.com
- cisco.com
state: absent state: absent
- name: configure hostname and domain-name with vrf
iosxr_system:
hostname: iosxr01
vrf: nondefault
domain_name: test.example.com
domain-search:
- ansible.com
- redhat.com
- cisco.com
- name: configure DNS lookup sources - name: configure DNS lookup sources
iosxr_system: iosxr_system:
lookup_source: MgmtEth0/0/CPU0/0 lookup_source: MgmtEth0/0/CPU0/0
lookup_enabled: yes lookup_enabled: True
- name: configure name servers - name: configure name servers
iosxr_system: iosxr_system:
name_servers: name_servers:
@ -103,12 +126,36 @@ commands:
sample: sample:
- hostname iosxr01 - hostname iosxr01
- ip domain-name test.example.com - ip domain-name test.example.com
xml:
description: NetConf rpc xml sent to device with transport C(netconf)
returned: always (empty list when no xml rpc to send)
type: list
version_added: 2.5
sample:
- '<config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
<ip-domain xmlns="http://cisco.com/ns/yang/Cisco-IOS-XR-ip-domain-cfg">
<vrfs>
<vrf>
<vrf-name>default</vrf-name>
<lists>
<list xc:operation="merge">
<order>0</order>
<list-name>redhat.com</list-name>
</list>
</lists>
</vrf>
</vrfs>
</ip-domain>
</config>'
""" """
import re import re
import collections
from ansible.module_utils.basic import AnsibleModule 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 get_config, load_config, etree_findall
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec from ansible.module_utils.network.iosxr.iosxr import is_cliconf, is_netconf, etree_find
from ansible.module_utils.network.iosxr.iosxr import iosxr_argument_spec, build_xml
def diff_list(want, have): def diff_list(want, have):
@ -117,98 +164,389 @@ def diff_list(want, have):
return (adds, removes) return (adds, removes)
def map_obj_to_commands(want, have, module): class ConfigBase(object):
commands = list() def __init__(self, module):
state = module.params['state'] self._module = module
self._result = {'changed': False, 'warnings': []}
def needs_update(x): self._want = dict()
return want.get(x) and (want.get(x) != have.get(x)) self._have = dict()
if state == 'absent': def map_params_to_obj(self):
if have['hostname'] != 'ios': self._want.update({
commands.append('no hostname') 'hostname': self._module.params['hostname'],
if have['domain_name']: 'vrf': self._module.params['vrf'],
commands.append('no domain name') 'domain_name': self._module.params['domain_name'],
if have['lookup_source']: 'domain_search': self._module.params['domain_search'],
commands.append('no domain lookup source-interface %s' % have['lookup_source']) 'lookup_source': self._module.params['lookup_source'],
if not have['lookup_enabled']: 'lookup_enabled': self._module.params['lookup_enabled'],
commands.append('no domain lookup disable') 'name_servers': self._module.params['name_servers']
for item in have['name_servers']: })
commands.append('no domain name-server %s' % item)
for item in have['domain_search']:
commands.append('no domain list %s' % item) class CliConfiguration(ConfigBase):
def __init__(self, module):
elif state == 'present': super(CliConfiguration, self).__init__(module)
if needs_update('hostname'):
commands.append('hostname %s' % want['hostname']) def map_obj_to_commands(self):
commands = list()
if needs_update('domain_name'): state = self._module.params['state']
commands.append('domain name %s' % want['domain_name'])
def needs_update(x):
if needs_update('lookup_source'): return self._want.get(x) and (self._want.get(x) != self._have.get(x))
commands.append('domain lookup source-interface %s' % want['lookup_source'])
if state == 'absent':
if needs_update('lookup_enabled'): if self._have['hostname'] != 'ios':
cmd = 'domain lookup disable' commands.append('no hostname')
if want['lookup_enabled']: if self._have['domain_name']:
cmd = 'no %s' % cmd commands.append('no domain name')
commands.append(cmd) if self._have['lookup_source']:
commands.append('no domain lookup source-interface {0!s}'.format(self._have['lookup_source']))
if want['name_servers'] is not None: if not self._have['lookup_enabled']:
adds, removes = diff_list(want['name_servers'], have['name_servers']) commands.append('no domain lookup disable')
for item in adds: for item in self._have['name_servers']:
commands.append('domain name-server %s' % item) commands.append('no domain name-server {0!s}'.format(item))
for item in removes: for item in self._have['domain_search']:
commands.append('no domain name-server %s' % item) commands.append('no domain list {0!s}'.format(item))
if want['domain_search'] is not None: elif state == 'present':
adds, removes = diff_list(want['domain_search'], have['domain_search']) if needs_update('hostname'):
for item in adds: commands.append('hostname {0!s}'.format(self._want['hostname']))
commands.append('domain list %s' % item)
for item in removes: if needs_update('domain_name'):
commands.append('no domain list %s' % item) commands.append('domain name {0!s}'.format(self._want['domain_name']))
return commands if needs_update('lookup_source'):
commands.append('domain lookup source-interface {0!s}'.format(self._want['lookup_source']))
def parse_hostname(config): cmd = None
match = re.search(r'^hostname (\S+)', config, re.M) if not self._want['lookup_enabled'] and self._have['lookup_enabled']:
return match.group(1) cmd = 'domain lookup disable'
elif self._want['lookup_enabled'] and not self._have['lookup_enabled']:
cmd = 'no domain lookup disable'
def parse_domain_name(config): if cmd is not None:
match = re.search(r'^domain name (\S+)', config, re.M) commands.append(cmd)
if match:
if self._want['name_servers'] is not None:
adds, removes = diff_list(self._want['name_servers'], self._have['name_servers'])
for item in adds:
commands.append('domain name-server {0!s}'.format(item))
for item in removes:
commands.append('no domain name-server {0!s}'.format(item))
if self._want['domain_search'] is not None:
adds, removes = diff_list(self._want['domain_search'], self._have['domain_search'])
for item in adds:
commands.append('domain list {0!s}'.format(item))
for item in removes:
commands.append('no domain list {0!s}'.format(item))
self._result['commands'] = []
if commands:
commit = not self._module.check_mode
diff = load_config(self._module, commands, commit=commit)
if diff:
self._result['diff'] = dict(prepared=diff)
self._result['commands'] = commands
self._result['changed'] = True
def parse_hostname(self, config):
match = re.search(r'^hostname (\S+)', config, re.M)
return match.group(1) return match.group(1)
def parse_domain_name(self, config):
match = re.search(r'^domain name (\S+)', config, re.M)
if match:
return match.group(1)
def parse_lookup_source(self, config):
match = re.search(r'^domain lookup source-interface (\S+)', config, re.M)
if match:
return match.group(1)
def map_config_to_obj(self):
config = get_config(self._module)
self._have.update({
'hostname': self.parse_hostname(config),
'domain_name': self.parse_domain_name(config),
'domain_search': re.findall(r'^domain list (\S+)', config, re.M),
'lookup_source': self.parse_lookup_source(config),
'lookup_enabled': 'domain lookup disable' not in config,
'name_servers': re.findall(r'^domain name-server (\S+)', config, re.M)
})
def run(self):
self.map_params_to_obj()
self.map_config_to_obj()
self.map_obj_to_commands()
return self._result
class NCConfiguration(ConfigBase):
def __init__(self, module):
super(NCConfiguration, self).__init__(module)
self._system_meta = collections.OrderedDict()
self._system_domain_meta = collections.OrderedDict()
self._system_server_meta = collections.OrderedDict()
self._hostname_meta = collections.OrderedDict()
self._lookup_source_meta = collections.OrderedDict()
self._lookup_meta = collections.OrderedDict()
def map_obj_to_xml_rpc(self):
self._system_meta.update([
('vrfs', {'xpath': 'ip-domain/vrfs', 'tag': True, 'operation': 'edit'}),
('vrf', {'xpath': 'ip-domain/vrfs/vrf', 'tag': True, 'operation': 'edit'}),
('a:vrf', {'xpath': 'ip-domain/vrfs/vrf/vrf-name', 'operation': 'edit'}),
('a:domain_name', {'xpath': 'ip-domain/vrfs/vrf/name', 'operation': 'edit', 'attrib': "operation"}),
])
self._system_domain_meta.update([
('vrfs', {'xpath': 'ip-domain/vrfs', 'tag': True, 'operation': 'edit'}),
('vrf', {'xpath': 'ip-domain/vrfs/vrf', 'tag': True, 'operation': 'edit'}),
('a:vrf', {'xpath': 'ip-domain/vrfs/vrf/vrf-name', 'operation': 'edit'}),
('lists', {'xpath': 'ip-domain/vrfs/vrf/lists', 'tag': True, 'operation': 'edit'}),
('list', {'xpath': 'ip-domain/vrfs/vrf/lists/list', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
('a:order', {'xpath': 'ip-domain/vrfs/vrf/lists/list/order', 'operation': 'edit'}),
('a:domain_search', {'xpath': 'ip-domain/vrfs/vrf/lists/list/list-name', 'operation': 'edit'}),
])
self._system_server_meta.update([
('vrfs', {'xpath': 'ip-domain/vrfs', 'tag': True, 'operation': 'edit'}),
('vrf', {'xpath': 'ip-domain/vrfs/vrf', 'tag': True, 'operation': 'edit'}),
('a:vrf', {'xpath': 'ip-domain/vrfs/vrf/vrf-name', 'operation': 'edit'}),
('servers', {'xpath': 'ip-domain/vrfs/vrf/servers', 'tag': True, 'operation': 'edit'}),
('server', {'xpath': 'ip-domain/vrfs/vrf/servers/server', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
('a:order', {'xpath': 'ip-domain/vrfs/vrf/servers/server/order', 'operation': 'edit'}),
('a:name_servers', {'xpath': 'ip-domain/vrfs/vrf/servers/server/server-address', 'operation': 'edit'}),
])
self._hostname_meta.update([
('a:hostname', {'xpath': 'host-names/host-name', 'operation': 'edit', 'attrib': "operation"}),
])
self._lookup_source_meta.update([
('vrfs', {'xpath': 'ip-domain/vrfs', 'tag': True, 'operation': 'edit'}),
('vrf', {'xpath': 'ip-domain/vrfs/vrf', 'tag': True, 'operation': 'edit'}),
('a:vrf', {'xpath': 'ip-domain/vrfs/vrf/vrf-name', 'operation': 'edit'}),
('a:lookup_source', {'xpath': 'ip-domain/vrfs/vrf/source-interface', 'operation': 'edit', 'attrib': "operation"}),
])
self._lookup_meta.update([
('vrfs', {'xpath': 'ip-domain/vrfs', 'tag': True, 'operation': 'edit'}),
('vrf', {'xpath': 'ip-domain/vrfs/vrf', 'tag': True, 'operation': 'edit'}),
('a:vrf', {'xpath': 'ip-domain/vrfs/vrf/vrf-name', 'operation': 'edit'}),
('lookup', {'xpath': 'ip-domain/vrfs/vrf/lookup', 'tag': True, 'operation': 'edit', 'attrib': "operation"}),
])
state = self._module.params['state']
_get_filter = build_xml('ip-domain', opcode="filter")
running = get_config(self._module, source='running', config_filter=_get_filter)
_get_filter = build_xml('host-names', opcode="filter")
hostname_runn = get_config(self._module, source='running', config_filter=_get_filter)
hostname_ele = etree_find(hostname_runn, 'host-name')
hostname = hostname_ele.text if hostname_ele is not None else None
vrf_ele = etree_findall(running, 'vrf')
vrf_map = {}
for vrf in vrf_ele:
name_server_list = list()
domain_list = list()
vrf_name_ele = etree_find(vrf, 'vrf-name')
vrf_name = vrf_name_ele.text if vrf_name_ele is not None else None
domain_name_ele = etree_find(vrf, 'name')
domain_name = domain_name_ele.text if domain_name_ele is not None else None
domain_ele = etree_findall(vrf, 'list-name')
for domain in domain_ele:
domain_list.append(domain.text)
server_ele = etree_findall(vrf, 'server-address')
for server in server_ele:
name_server_list.append(server.text)
lookup_source_ele = etree_find(vrf, 'source-interface')
lookup_source = lookup_source_ele.text if lookup_source_ele is not None else None
lookup_enabled = False if etree_find(vrf, 'lookup') is not None else True
vrf_map[vrf_name] = {'domain_name': domain_name,
'domain_search': domain_list,
'name_servers': name_server_list,
'lookup_source': lookup_source,
'lookup_enabled': lookup_enabled}
opcode = None
hostname_param = {}
lookup_param = {}
system_param = {}
sys_server_params = list()
sys_domain_params = list()
add_domain_params = list()
del_domain_params = list()
add_server_params = list()
del_server_params = list()
lookup_source_params = {}
try:
sys_node = vrf_map[self._want['vrf']]
except KeyError:
sys_node = {'domain_name': None,
'domain_search': [],
'name_servers': [],
'lookup_source': None,
'lookup_enabled': True}
if state == 'absent':
opcode = "delete"
def needs_update(x):
return self._want[x] is not None and self._want[x] == sys_node[x]
if needs_update('domain_name'):
system_param = {'vrf': self._want['vrf'], 'domain_name': self._want['domain_name']}
if needs_update('hostname'):
hostname_param = {'hostname': hostname}
if not self._want['lookup_enabled'] and not sys_node['lookup_enabled']:
lookup_param['vrf'] = self._want['vrf']
if needs_update('lookup_source'):
lookup_source_params['vrf'] = self._want['vrf']
lookup_source_params['lookup_source'] = self._want['lookup_source']
if self._want['domain_search']:
domain_param = {}
domain_param['domain_name'] = self._want['domain_name']
domain_param['vrf'] = self._want['vrf']
domain_param['order'] = '0'
for domain in self._want['domain_search']:
if domain in sys_node['domain_search']:
domain_param['domain_search'] = domain
sys_domain_params.append(domain_param.copy())
if self._want['name_servers']:
server_param = {}
server_param['vrf'] = self._want['vrf']
server_param['order'] = '1'
for server in self._want['name_servers']:
if server in sys_node['name_servers']:
server_param['name_servers'] = server
sys_server_params.append(server_param.copy())
elif state == 'present':
opcode = "merge"
def needs_update(x):
return self._want[x] is not None and (sys_node[x] is None or
(sys_node[x] is not None and self._want[x] != sys_node[x]))
if needs_update('domain_name'):
system_param = {'vrf': self._want['vrf'], 'domain_name': self._want['domain_name']}
if self._want['hostname'] is not None and self._want['hostname'] != hostname:
hostname_param = {'hostname': self._want['hostname']}
if not self._want['lookup_enabled'] and sys_node['lookup_enabled']:
lookup_param['vrf'] = self._want['vrf']
if needs_update('lookup_source'):
lookup_source_params['vrf'] = self._want['vrf']
lookup_source_params['lookup_source'] = self._want['lookup_source']
if self._want['domain_search']:
domain_adds, domain_removes = diff_list(self._want['domain_search'], sys_node['domain_search'])
domain_param = {}
domain_param['domain_name'] = self._want['domain_name']
domain_param['vrf'] = self._want['vrf']
domain_param['order'] = '0'
for domain in domain_adds:
if domain not in sys_node['domain_search']:
domain_param['domain_search'] = domain
add_domain_params.append(domain_param.copy())
for domain in domain_removes:
if domain in sys_node['domain_search']:
domain_param['domain_search'] = domain
del_domain_params.append(domain_param.copy())
if self._want['name_servers']:
server_adds, server_removes = diff_list(self._want['name_servers'], sys_node['name_servers'])
server_param = {}
server_param['vrf'] = self._want['vrf']
server_param['order'] = '1'
for domain in server_adds:
if domain not in sys_node['name_servers']:
server_param['name_servers'] = domain
add_server_params.append(server_param.copy())
for domain in server_removes:
if domain in sys_node['name_servers']:
server_param['name_servers'] = domain
del_server_params.append(server_param.copy())
self._result['xml'] = []
_edit_filter_list = list()
if opcode:
if hostname_param:
_edit_filter_list.append(build_xml('host-names', xmap=self._hostname_meta,
params=hostname_param, opcode=opcode))
if system_param:
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_meta,
params=system_param, opcode=opcode))
if lookup_source_params:
_edit_filter_list.append(build_xml('ip-domain', xmap=self._lookup_source_meta,
params=lookup_source_params, opcode=opcode))
if lookup_param:
_edit_filter_list.append(build_xml('ip-domain', xmap=self._lookup_meta,
params=lookup_param, opcode=opcode))
if opcode == 'delete':
if sys_domain_params:
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_domain_meta,
params=sys_domain_params, opcode=opcode))
if sys_server_params:
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_server_meta,
params=sys_server_params, opcode=opcode))
if self._want['vrf'] != 'default':
self._result['warnings'] = ["name-servers delete operation with non-default vrf is a success, "
"but with rpc-error. Recommended to use 'ignore_errors' option with the task as a workaround"]
elif opcode == 'merge':
if add_domain_params:
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_domain_meta,
params=add_domain_params, opcode=opcode))
if del_domain_params:
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_domain_meta,
params=del_domain_params, opcode="delete"))
if add_server_params:
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_server_meta,
params=add_server_params, opcode=opcode))
if del_server_params:
_edit_filter_list.append(build_xml('ip-domain', xmap=self._system_server_meta,
params=del_server_params, opcode="delete"))
diff = None
if _edit_filter_list:
commit = not self._module.check_mode
diff = load_config(self._module, _edit_filter_list, commit=commit, running=running,
nc_get_filter=_get_filter)
def parse_lookup_source(config): if diff:
match = re.search(r'^domain lookup source-interface (\S+)', config, re.M) if self._module._diff:
if match: self._result['diff'] = dict(prepared=diff)
return match.group(1)
def map_config_to_obj(module): self._result['xml'] = _edit_filter_list
config = get_config(module) self._result['changed'] = True
return {
'hostname': parse_hostname(config),
'domain_name': parse_domain_name(config),
'domain_search': re.findall(r'^domain list (\S+)', config, re.M),
'lookup_source': parse_lookup_source(config),
'lookup_enabled': 'domain lookup disable' not in config,
'name_servers': re.findall(r'^domain name-server (\S+)', config, re.M)
}
def run(self):
self.map_params_to_obj()
self.map_obj_to_xml_rpc()
def map_params_to_obj(module): return self._result
return {
'hostname': module.params['hostname'],
'domain_name': module.params['domain_name'],
'domain_search': module.params['domain_search'],
'lookup_source': module.params['lookup_source'],
'lookup_enabled': module.params['lookup_enabled'],
'name_servers': module.params['name_servers']
}
def main(): def main():
@ -216,12 +554,13 @@ def main():
""" """
argument_spec = dict( argument_spec = dict(
hostname=dict(), hostname=dict(),
vrf=dict(type='str', default='default'),
domain_name=dict(), domain_name=dict(),
domain_search=dict(type='list'), domain_search=dict(type='list'),
name_servers=dict(type='list'), name_servers=dict(type='list'),
lookup_source=dict(), lookup_source=dict(),
lookup_enabled=dict(type='bool'), lookup_enabled=dict(type='bool', default=True),
state=dict(choices=['present', 'absent'], default='present') state=dict(choices=['present', 'absent'], default='present')
) )
@ -231,23 +570,17 @@ def main():
module = AnsibleModule(argument_spec=argument_spec, module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True) supports_check_mode=True)
warnings = list() config_object = None
if is_cliconf(module):
result = {'changed': False, 'warnings': warnings} module.deprecate(msg="cli support for 'iosxr_system' is deprecated. Use transport netconf instead",
version="4 releases from v2.5")
want = map_params_to_obj(module) config_object = CliConfiguration(module)
have = map_config_to_obj(module) elif is_netconf(module):
config_object = NCConfiguration(module)
commands = map_obj_to_commands(want, have, module)
result['commands'] = commands result = None
if config_object:
if commands: result = config_object.run()
commit = not module.check_mode
diff = load_config(module, commands, commit=commit)
if diff:
result['diff'] = dict(prepared=diff)
result['changed'] = True
module.exit_json(**result) module.exit_json(**result)

@ -107,17 +107,18 @@ class Netconf(NetconfBase):
reply = self.get(install_filter) reply = self.get(install_filter)
ele_boot_variable = etree.fromstring(reply).find('.//boot-variable/boot-variable') ele_boot_variable = etree.fromstring(reply).find('.//boot-variable/boot-variable')
if ele_boot_variable: if ele_boot_variable is not None:
device_info['network_os_image'] = re.split('[:|,]', ele_boot_variable.text)[1] device_info['network_os_image'] = re.split('[:|,]', ele_boot_variable.text)[1]
ele_package_name = etree.fromstring(reply).find('.//package-name') ele_package_name = etree.fromstring(reply).find('.//package-name')
if ele_package_name: if ele_package_name is not None:
device_info['network_os_package'] = ele_package_name.text device_info['network_os_package'] = ele_package_name.text
device_info['network_os_version'] = re.split('-', ele_package_name.text)[-1] device_info['network_os_version'] = re.split('-', ele_package_name.text)[-1]
hostname_filter = build_xml('host-names', opcode='filter') hostname_filter = build_xml('host-names', opcode='filter')
reply = self.get(hostname_filter) reply = self.get(hostname_filter)
device_info['network_os_hostname'] = etree.fromstring(reply).find('.//host-name').text hostname_ele = etree.fromstring(reply).find('.//host-name')
device_info['network_os_hostname'] = hostname_ele.text if hostname_ele is not None else None
return device_info return device_info

@ -1,2 +1,3 @@
--- ---
- { include: cli.yaml, tags: ['cli'] } - { include: cli.yaml, tags: ['cli'] }
- { include: netconf.yaml, tags: ['netconf'] }

@ -0,0 +1,22 @@
---
- 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 (connection=local)
include: "{{ test_case_to_run }} ansible_connection=local"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run
# - name: run test case (connection=netconf)
# include: "{{ test_case_to_run }} ansible_connection=netconf"
# with_items: "{{ test_items }}"
# loop_control:
# loop_var: test_case_to_run

@ -81,6 +81,13 @@
- result.commands|length == 1 - result.commands|length == 1
- "'no domain name-server 3.3.3.3' in result.commands" - "'no domain name-server 3.3.3.3' in result.commands"
# FIXME: No teardown - name: setup
# iosxr_config:
lines:
- no ip name-server 1.1.1.1
- no ip name-server 2.2.2.2
- no ip name-server 3.3.3.3
match: none
provider: "{{ cli }}"
- debug: msg="END cli/set_name_servers.yaml on connection={{ ansible_connection }}" - debug: msg="END cli/set_name_servers.yaml on connection={{ ansible_connection }}"

@ -0,0 +1,162 @@
---
- debug:
msg: "START netconf/set_domain_search.yaml on connection={{ ansible_connection }}"
- block:
- name: setup
iosxr_config:
lines:
- no domain list ansible.com
- no domain list redhat.com
match: none
provider: "{{ cli }}"
- name: configure domain_search
iosxr_system:
domain_search:
- ansible.com
- redhat.com
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- "'ansible.com' in result.xml[0]"
- "'redhat.com' in result.xml[0]"
- name: configure domain_search with vrf
iosxr_system: &domainvrf
vrf: ansiblevrf
domain_search:
- redhat.com
- ansible.com
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- "'ansiblevrf' in result.xml[0]"
- "'ansible.com' in result.xml[0]"
- "'redhat.com' in result.xml[0]"
- name: verify domain_search with vrf
iosxr_system: *domainvrf
register: result
- assert:
that:
- result.changed == false
- name: delete domain_search with vrf
iosxr_system: &deldomainvrf
vrf: ansiblevrf
domain_search:
- redhat.com
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- "'ansiblevrf' in result.xml[0]"
- "'ansible.com' in result.xml[0]"
- name: verify delete domain_search with vrf
iosxr_system: *deldomainvrf
register: result
- assert:
that:
- result.changed == false
- name: remove one entry
iosxr_system:
domain_search:
- ansible.com
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- "'redhat.com' in result.xml[0]"
- name: verify remove one entry
iosxr_system:
domain_search:
- ansible.com
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == false
- name: add one entry
iosxr_system:
domain_search:
- ansible.com
- redhat.com
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- "'redhat.com' in result.xml[0]"
- name: verify add one entry
iosxr_system:
domain_search:
- ansible.com
- redhat.com
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == false
- name: add and remove one entry
iosxr_system:
domain_search:
- ansible.com
- eng.ansible.com
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- "'redhat.com' in result.xml[1]"
- "'eng.ansible.com' in result.xml[0]"
- result.xml|length == 2
- name: verify add and remove one entry
iosxr_system:
domain_search:
- ansible.com
- eng.ansible.com
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == false
- always:
- name: teardown
iosxr_config:
lines:
- no domain list ansible.com
- no domain list redhat.com
- no domain list eng.ansible.com
- no domain vrf ansiblevrf list redhat.com
- no domain vrf ansiblevrf list ansible.com
match: none
provider: "{{ netconf }}"
- debug:
msg: "END netconf/set_domain_search.yaml on connection={{ ansible_connection }}"

@ -0,0 +1,79 @@
---
- debug:
msg: "START netconf/set_domain_name.yaml on connection={{ ansible_connection }}"
- block:
- name: setup
iosxr_config:
lines:
- no domain name
- no domain vrf ansiblevrf name
match: none
provider: "{{ cli }}"
- name: configure domain_name
iosxr_system: &domain
domain_name: eng.ansible.com
provider: "{{ netconf }}"
register: result
- assert:
that:
- "result.changed == true"
- name: verify domain_name
iosxr_system: *domain
register: result
- assert:
that:
- "result.changed == false"
- name: configure domain_name
iosxr_system: &deldomain
domain_name: eng.ansible.com
provider: "{{ netconf }}"
state: absent
register: result
- assert:
that:
- "result.changed == true"
- name: verify domain_name
iosxr_system: *deldomain
register: result
- assert:
that:
- "result.changed == false"
- name: configure domain_name with vrf
iosxr_system: &domainvrf
domain_name: eng.ansible.com
vrf: ansiblevrf
provider: "{{ netconf }}"
register: result
- assert:
that:
- "result.changed == true"
- name: verify domain_name with vrf
iosxr_system: *domainvrf
register: result
- assert:
that:
- "result.changed == false"
- always:
- name: teardown
iosxr_config:
lines:
- no domain name
- no domain vrf ansiblevrf name
match: none
provider: "{{ cli }}"
- debug:
msg: "END netconf/set_domain_name.yaml on connection={{ ansible_connection }}"

@ -0,0 +1,39 @@
---
- debug:
msg: "START netconf/set_hostname.yaml on connection={{ ansible_connection }}"
- block:
- name: setup
iosxr_config:
lines: hostname switch
match: none
provider: "{{ cli }}"
- name: configure hostname
iosxr_system:
hostname: foo
provider: "{{ netconf }}"
register: result
- assert:
that:
- "result.changed == true"
- name: verify hostname
iosxr_system:
hostname: foo
provider: "{{ netconf }}"
register: result
- assert:
that:
- "result.changed == false"
- always:
- name: teardown
iosxr_config:
lines: "hostname {{ inventory_hostname }}"
match: none
provider: "{{ cli }}"
- debug:
msg: "END netconf/set_hostname.yaml on connection={{ ansible_connection }}"

@ -0,0 +1,150 @@
---
- debug:
msg: "START netconf/set_lookup_source.yaml on connection={{ ansible_connection }}"
- block:
- name: setup
iosxr_config:
lines:
- no domain lookup source-interface Loopback10
- no domain vrf ansiblevrf lookup source-interface Loopback10
- no domain lookup disable
- no domain vrf ansiblevrf lookup disable
match: none
provider: "{{ cli }}"
- name: configure lookup_source
iosxr_system: &lookup
lookup_source: Loopback10
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- "'Loopback10' in result.xml[0]"
- name: verify lookup_source
iosxr_system: *lookup
register: result
- assert:
that:
- result.changed == false
- name: disable lookup
iosxr_system: &disable
lookup_enabled: False
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- "'lookup' in result.xml[0]"
- name: verify disable lookup
iosxr_system: *disable
register: result
- assert:
that:
- result.changed == false
- name: delete lookup_source
iosxr_system: &dellookup
lookup_source: Loopback10
provider: "{{ netconf }}"
state: absent
register: result
- assert:
that:
- result.changed == true
- "'Loopback10' in result.xml[0]"
- name: verify lookup_source
iosxr_system: *dellookup
register: result
- assert:
that:
- result.changed == false
- name: configure lookup_source with vrf
iosxr_system: &lookupvrf
lookup_source: Loopback10
vrf: ansiblevrf
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- "'Loopback10' in result.xml[0]"
- "'ansiblevrf' in result.xml[0]"
- name: verify lookup_source
iosxr_system: *lookupvrf
register: result
- assert:
that:
- result.changed == false
- name: disable lookup
iosxr_system: &disablevrf
lookup_enabled: False
vrf: ansiblevrf
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- "'lookup' in result.xml[0]"
- "'ansiblevrf' in result.xml[0]"
- name: verify disable lookup
iosxr_system: *disablevrf
register: result
- assert:
that:
- result.changed == false
- name: delete lookup_source
iosxr_system: &dellookupvrf
lookup_source: Loopback10
vrf: ansiblevrf
provider: "{{ netconf }}"
state: absent
register: result
- assert:
that:
- result.changed == true
- "'Loopback10' in result.xml[0]"
- "'ansiblevrf' in result.xml[0]"
- name: verify lookup_source
iosxr_system: *dellookupvrf
register: result
- assert:
that:
- result.changed == false
- always:
- name: teardown
iosxr_config:
lines:
- no domain lookup source-interface Loopback10
- no domain vrf ansiblevrf lookup source-interface Loopback10
- no domain lookup disable
- no domain vrf ansiblevrf lookup disable
match: none
provider: "{{ cli }}"
- debug:
msg: "END netconf/set_lookup_source.yaml on connection={{ ansible_connection }}"

@ -0,0 +1,120 @@
---
- debug:
msg: "START netconf/set_name_servers.yaml on connection={{ ansible_connection }}"
- block:
- name: configure name_servers
iosxr_system:
name_servers:
- 1.1.1.1
- 2.2.2.2
- 3.3.3.3
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- result.xml|length == 1
- "'1.1.1.1' in result.xml[0]"
- "'2.2.2.2' in result.xml[0]"
- "'3.3.3.3' in result.xml[0]"
- name: verify name_servers
iosxr_system:
name_servers:
- 1.1.1.1
- 2.2.2.2
- 3.3.3.3
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == false
- name: add with to vrf
iosxr_system: &addvrf
vrf: ansible
name_servers:
- 1.1.1.1
- 2.2.2.2
- 3.3.3.3
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- result.xml|length == 1
- "'ansible' in result.xml[0]"
- "'1.1.1.1' in result.xml[0]"
- "'2.2.2.2' in result.xml[0]"
- "'3.3.3.3' in result.xml[0]"
- name: verify change to vrf
iosxr_system: *addvrf
register: result
- assert:
that:
- result.changed == false
- name: remove one
iosxr_system:
name_servers:
- 1.1.1.1
- 2.2.2.2
provider: "{{ netconf }}"
register: result
- assert:
that:
- result.changed == true
- result.xml|length == 1
- "'3.3.3.3' in result.xml[0]"
## multiple name-servers deletion commands doesn't work in single
# config session (only the 1st one takes effect). May or may not be
# a VIRL software issue.
- always:
- name: setup
iosxr_config:
lines: no domain name-server 1.1.1.1
match: none
provider: "{{ cli }}"
- name: setup
iosxr_config:
lines: no domain name-server 2.2.2.2
match: none
provider: "{{ cli }}"
- name: setup
iosxr_config:
lines: no domain name-server 3.3.3.3
match: none
provider: "{{ cli }}"
- name: setup
iosxr_config:
lines: no domain vrf ansible name-server 1.1.1.1
match: none
provider: "{{ cli }}"
ignore_errors: true
- name: setup
iosxr_config:
lines: no domain vrf ansible name-server 2.2.2.2
match: none
provider: "{{ cli }}"
ignore_errors: true
- name: setup
iosxr_config:
lines: no domain vrf ansible name-server 3.3.3.3
match: none
provider: "{{ cli }}"
ignore_errors: true
- debug:
msg: "END netconf/set_name_servers.yaml on connection={{ ansible_connection }}"

@ -38,6 +38,9 @@ class TestIosxrSystemModule(TestIosxrModule):
self.mock_load_config = patch('ansible.modules.network.iosxr.iosxr_system.load_config') self.mock_load_config = patch('ansible.modules.network.iosxr.iosxr_system.load_config')
self.load_config = self.mock_load_config.start() self.load_config = self.mock_load_config.start()
self.mock_is_cliconf = patch('ansible.modules.network.iosxr.iosxr_system.is_cliconf')
self.is_cliconf = self.mock_is_cliconf.start()
def tearDown(self): def tearDown(self):
super(TestIosxrSystemModule, self).tearDown() super(TestIosxrSystemModule, self).tearDown()
@ -47,25 +50,26 @@ class TestIosxrSystemModule(TestIosxrModule):
def load_fixtures(self, commands=None): def load_fixtures(self, commands=None):
self.get_config.return_value = load_fixture('iosxr_system_config.cfg') self.get_config.return_value = load_fixture('iosxr_system_config.cfg')
self.load_config.return_value = dict(diff=None, session='session') self.load_config.return_value = dict(diff=None, session='session')
self.is_cliconf.return_value = True
def test_iosxr_system_hostname_changed(self): def test_iosxr_system_hostname_changed(self):
set_module_args(dict(hostname='foo')) set_module_args(dict(hostname='foo'))
commands = ['hostname foo'] commands = ['hostname foo', 'no domain lookup disable']
self.execute_module(changed=True, commands=commands) self.execute_module(changed=True, commands=commands)
def test_iosxr_system_domain_name(self): def test_iosxr_system_domain_name(self):
set_module_args(dict(domain_name='test.com')) set_module_args(dict(domain_name='test.com'))
commands = ['domain name test.com'] commands = ['domain name test.com', 'no domain lookup disable']
self.execute_module(changed=True, commands=commands) self.execute_module(changed=True, commands=commands)
def test_iosxr_system_domain_search(self): def test_iosxr_system_domain_search(self):
set_module_args(dict(domain_search=['ansible.com', 'redhat.com'])) set_module_args(dict(domain_search=['ansible.com', 'redhat.com']))
commands = ['domain list ansible.com', 'no domain list cisco.com'] commands = ['domain list ansible.com', 'no domain list cisco.com', 'no domain lookup disable']
self.execute_module(changed=True, commands=commands) self.execute_module(changed=True, commands=commands)
def test_iosxr_system_lookup_source(self): def test_iosxr_system_lookup_source(self):
set_module_args(dict(lookup_source='Ethernet1')) set_module_args(dict(lookup_source='Ethernet1'))
commands = ['domain lookup source-interface Ethernet1'] commands = ['domain lookup source-interface Ethernet1', 'no domain lookup disable']
self.execute_module(changed=True, commands=commands) self.execute_module(changed=True, commands=commands)
def test_iosxr_system_lookup_enabled(self): def test_iosxr_system_lookup_enabled(self):
@ -76,7 +80,7 @@ class TestIosxrSystemModule(TestIosxrModule):
def test_iosxr_system_name_servers(self): def test_iosxr_system_name_servers(self):
name_servers = ['8.8.8.8', '8.8.4.4', '1.1.1.1'] name_servers = ['8.8.8.8', '8.8.4.4', '1.1.1.1']
set_module_args(dict(name_servers=name_servers)) set_module_args(dict(name_servers=name_servers))
commands = ['domain name-server 1.1.1.1', 'no domain name-server 8.8.4.4'] commands = ['domain name-server 1.1.1.1', 'no domain name-server 8.8.4.4', 'no domain lookup disable']
self.execute_module(changed=True) self.execute_module(changed=True)
def test_iosxr_system_state_absent(self): def test_iosxr_system_state_absent(self):
@ -94,5 +98,5 @@ class TestIosxrSystemModule(TestIosxrModule):
self.execute_module(changed=True, commands=commands) self.execute_module(changed=True, commands=commands)
def test_iosxr_system_no_change(self): def test_iosxr_system_no_change(self):
set_module_args(dict(hostname='iosxr01', domain_name='eng.ansible.com')) set_module_args(dict(hostname='iosxr01', domain_name='eng.ansible.com', lookup_enabled=False))
self.execute_module() self.execute_module()

Loading…
Cancel
Save