From e82d407fa8de3a87ae885e35be81936f9c546c99 Mon Sep 17 00:00:00 2001 From: Mike Wiebe Date: Wed, 30 Oct 2019 09:39:13 -0400 Subject: [PATCH] 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 --- .../modules/network/netconf/netconf_config.py | 45 ++++++++++++++++--- .../targets/nxos_netconf/defaults/main.yaml | 3 ++ .../targets/nxos_netconf/meta/main.yaml | 3 ++ .../targets/nxos_netconf/tasks/main.yaml | 41 +++++++++++++++++ .../targets/nxos_netconf/tasks/netconf.yaml | 16 +++++++ .../nxos_netconf/tests/fixtures/config.yaml | 14 ++++++ .../nxos_netconf/tests/netconf/basic.yaml | 44 ++++++++++++++++++ 7 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 test/integration/targets/nxos_netconf/defaults/main.yaml create mode 100644 test/integration/targets/nxos_netconf/meta/main.yaml create mode 100644 test/integration/targets/nxos_netconf/tasks/main.yaml create mode 100644 test/integration/targets/nxos_netconf/tasks/netconf.yaml create mode 100644 test/integration/targets/nxos_netconf/tests/fixtures/config.yaml create mode 100644 test/integration/targets/nxos_netconf/tests/netconf/basic.yaml diff --git a/lib/ansible/modules/network/netconf/netconf_config.py b/lib/ansible/modules/network/netconf/netconf_config.py index b09d128f312..00712772992 100644 --- a/lib/ansible/modules/network/netconf/netconf_config.py +++ b/lib/ansible/modules/network/netconf/netconf_config.py @@ -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) diff --git a/test/integration/targets/nxos_netconf/defaults/main.yaml b/test/integration/targets/nxos_netconf/defaults/main.yaml new file mode 100644 index 00000000000..9ef5ba51651 --- /dev/null +++ b/test/integration/targets/nxos_netconf/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "*" +test_items: [] diff --git a/test/integration/targets/nxos_netconf/meta/main.yaml b/test/integration/targets/nxos_netconf/meta/main.yaml new file mode 100644 index 00000000000..6a8fed7644e --- /dev/null +++ b/test/integration/targets/nxos_netconf/meta/main.yaml @@ -0,0 +1,3 @@ +dependencies: + # Not needed for this test + # - prepare_nxos_tests diff --git a/test/integration/targets/nxos_netconf/tasks/main.yaml b/test/integration/targets/nxos_netconf/tasks/main.yaml new file mode 100644 index 00000000000..07e872c9458 --- /dev/null +++ b/test/integration/targets/nxos_netconf/tasks/main.yaml @@ -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 diff --git a/test/integration/targets/nxos_netconf/tasks/netconf.yaml b/test/integration/targets/nxos_netconf/tasks/netconf.yaml new file mode 100644 index 00000000000..d8d042114f4 --- /dev/null +++ b/test/integration/targets/nxos_netconf/tasks/netconf.yaml @@ -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 diff --git a/test/integration/targets/nxos_netconf/tests/fixtures/config.yaml b/test/integration/targets/nxos_netconf/tests/fixtures/config.yaml new file mode 100644 index 00000000000..de3c3402633 --- /dev/null +++ b/test/integration/targets/nxos_netconf/tests/fixtures/config.yaml @@ -0,0 +1,14 @@ +--- +vlan_config: | + + + + + + vlan-42 + vlan-42 + + + + + diff --git a/test/integration/targets/nxos_netconf/tests/netconf/basic.yaml b/test/integration/targets/nxos_netconf/tests/netconf/basic.yaml new file mode 100644 index 00000000000..b9d79ab9af2 --- /dev/null +++ b/test/integration/targets/nxos_netconf/tests/netconf/basic.yaml @@ -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: + 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: + 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"