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:
parent
003c26de04
commit
e82d407fa8
7 changed files with 160 additions and 6 deletions
|
@ -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)
|
||||
|
|
3
test/integration/targets/nxos_netconf/defaults/main.yaml
Normal file
3
test/integration/targets/nxos_netconf/defaults/main.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
testcase: "*"
|
||||
test_items: []
|
3
test/integration/targets/nxos_netconf/meta/main.yaml
Normal file
3
test/integration/targets/nxos_netconf/meta/main.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
dependencies:
|
||||
# Not needed for this test
|
||||
# - prepare_nxos_tests
|
41
test/integration/targets/nxos_netconf/tasks/main.yaml
Normal file
41
test/integration/targets/nxos_netconf/tasks/main.yaml
Normal 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
|
16
test/integration/targets/nxos_netconf/tasks/netconf.yaml
Normal file
16
test/integration/targets/nxos_netconf/tasks/netconf.yaml
Normal 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
|
14
test/integration/targets/nxos_netconf/tests/fixtures/config.yaml
vendored
Normal file
14
test/integration/targets/nxos_netconf/tests/fixtures/config.yaml
vendored
Normal 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>
|
|
@ -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"
|
Loading…
Reference in a new issue