Add filter option to netconf_config module (#63593)

* Add filter option for netconf_config module

* Address review comments and add tests

* Refactor integration tests

* Address review comments

* Review comments

* Fix nxos_netconf include check
This commit is contained in:
Mike Wiebe 2019-10-30 09:39:13 -04:00 committed by Ganesh Nalawade
parent 003c26de04
commit e82d407fa8
7 changed files with 160 additions and 6 deletions

View file

@ -169,6 +169,15 @@ options:
type: path
type: dict
version_added: "2.8"
get_filter:
description:
- This argument specifies the XML string which acts as a filter to restrict the portions of
the data retrieved from the remote device when comparing the before and after state of the
device following calls to edit_config. When not specified, the entire configuration or
state data is returned for comparison depending on the value of C(source) option. The C(get_filter)
value can be either XML string or XPath, if the filter is in XPath format the NETCONF server
running on remote host should support xpath capability else it will result in an error.
version_added: "2.10"
requirements:
- "ncclient"
notes:
@ -254,10 +263,26 @@ from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.connection import Connection, ConnectionError
from ansible.module_utils.network.netconf.netconf import get_capabilities, get_config, sanitize_xml
import sys
try:
from lxml.etree import tostring
from lxml.etree import tostring, fromstring, XMLSyntaxError
except ImportError:
from xml.etree.ElementTree import tostring
from xml.etree.ElementTree import tostring, fromstring
if sys.version_info < (2, 7):
from xml.parsers.expat import ExpatError as XMLSyntaxError
else:
from xml.etree.ElementTree import ParseError as XMLSyntaxError
def get_filter_type(filter):
if not filter:
return None
else:
try:
fromstring(filter)
return 'subtree'
except XMLSyntaxError:
return 'xpath'
def main():
@ -283,6 +308,7 @@ def main():
delete=dict(type='bool', default=False),
commit=dict(type='bool', default=True),
validate=dict(type='bool', default=False),
get_filter=dict(),
)
# deprecated options
@ -320,6 +346,8 @@ def main():
confirm = module.params['confirm']
validate = module.params['validate']
save = module.params['save']
filter = module.params['get_filter']
filter_type = get_filter_type(filter)
conn = Connection(module._socket_path)
capabilities = get_capabilities(module)
@ -355,6 +383,11 @@ def main():
if validate and not operations.get('supports_validate', False):
module.fail_json(msg='validate is not supported by this netconf server')
if filter_type == 'xpath' and not operations.get('supports_xpath', False):
module.fail_json(msg="filter value '%s' of type xpath is not supported on this device" % filter)
filter_spec = (filter_type, filter) if filter_type else None
if lock == 'never':
execute_lock = False
elif target in operations.get('lock_datastore', []):
@ -371,7 +404,7 @@ def main():
locked = False
try:
if module.params['backup']:
response = get_config(module, target, lock=execute_lock)
response = get_config(module, target, filter_spec, lock=execute_lock)
before = to_text(tostring(response), errors='surrogate_then_replace').strip()
result['__backup__'] = before.strip()
if validate:
@ -398,7 +431,7 @@ def main():
conn.lock(target=target)
locked = True
if before is None:
before = to_text(conn.get_config(source=target), errors='surrogate_then_replace').strip()
before = to_text(conn.get_config(source=target, filter=filter_spec), errors='surrogate_then_replace').strip()
kwargs = {
'config': config,
@ -411,7 +444,7 @@ def main():
conn.edit_config(**kwargs)
if supports_commit and module.params['commit']:
after = to_text(conn.get_config(source='candidate'), errors='surrogate_then_replace').strip()
after = to_text(conn.get_config(source='candidate', filter=filter_spec), errors='surrogate_then_replace').strip()
if not module.check_mode:
confirm_timeout = confirm if confirm > 0 else None
confirmed_commit = True if confirm_timeout else False
@ -420,7 +453,7 @@ def main():
conn.discard_changes()
if after is None:
after = to_text(conn.get_config(source='running'), errors='surrogate_then_replace').strip()
after = to_text(conn.get_config(source='running', filter=filter_spec), errors='surrogate_then_replace').strip()
sanitized_before = sanitize_xml(before)
sanitized_after = sanitize_xml(after)

View file

@ -0,0 +1,3 @@
---
testcase: "*"
test_items: []

View file

@ -0,0 +1,3 @@
dependencies:
# Not needed for this test
# - prepare_nxos_tests

View file

@ -0,0 +1,41 @@
---
- name: Setup - Enable feature netconf
nxos_feature:
feature: netconf
state: enabled
vars: &ssh_credentials
ansible_connection: network_cli
ansible_ssh_port: 22
register: result
ignore_errors: yes
- debug: msg='Netconf feature is not supported on this platform!'
when: result.failed
- name: Setup - Remove Vlan
nxos_config:
lines:
- no vlan 42
ignore_errors: yes
when: not result.failed
- block:
- name: Run netconf tests
include: netconf.yaml
when: not result.failed
always:
- name: Disable feature netconf
nxos_feature:
feature: netconf
state: disabled
vars: *ssh_credentials
when: not result.failed
- name: Cleanup - Remove vlan
nxos_config:
lines:
- no vlan 42
vars: *ssh_credentials
ignore_errors: yes
when: not result.failed

View file

@ -0,0 +1,16 @@
---
- name: collect all cli 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=netconf)
include: "{{ test_case_to_run }}"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

View file

@ -0,0 +1,14 @@
---
vlan_config: |
<config>
<System xmlns="http://cisco.com/ns/yang/cisco-nx-os-device">
<bd-items>
<bd-items>
<BD-list>
<fabEncap>vlan-42</fabEncap>
<name>vlan-42</name>
</BD-list>
</bd-items>
</bd-items>
</System>
</config>

View file

@ -0,0 +1,44 @@
---
- debug: msg="START nxos_netconf cli/basic.yaml"
- include_vars: "{{playbook_dir }}/targets/nxos_netconf/tests/fixtures/config.yaml"
- debug: msg=" {{ playbook_dir }}"
- block:
- name: Configure vlan
netconf_config: &config_vlan
datastore: running
commit: false
get_filter: <System xmlns="http://cisco.com/ns/yang/cisco-nx-os-device"><bd-items><bd-items><BD-list></BD-list></bd-items></bd-items></System>
content: "{{ vlan_config }}"
register: result
- assert: &true
that:
- "result.changed == true"
- name: Configure vlan - idempotence check
netconf_config: *config_vlan
register: result
- assert: &false
that:
- "result.changed == false"
- name: Query Running Config
netconf_get:
source: running
filter: <System xmlns="http://cisco.com/ns/yang/cisco-nx-os-device"><bd-items><bd-items><BD-list></BD-list></bd-items></bd-items></System>
register: result
- assert:
that:
- "'vlan-42' in result.stdout"
vars:
ansible_connection: netconf
ansible_port: 830
always:
- debug: msg="END nxos_netconf cli/basic.yaml"