From 160bf82544eca98e8ac2287ab7be8287097c14b9 Mon Sep 17 00:00:00 2001 From: wiso Date: Thu, 24 May 2018 12:14:39 +0200 Subject: [PATCH] Add NETCONF support for SROS devices (#40330) * added NETCONF support for SROS devices * corrected alu mapping * fix pep8 in sros.py * BOT META file updated --- .github/BOTMETA.yml | 3 + lib/ansible/plugins/connection/netconf.py | 3 +- lib/ansible/plugins/netconf/sros.py | 100 ++++++++++++++++++ .../targets/netconf_get/meta/main.yml | 1 + .../targets/netconf_get/tasks/main.yaml | 1 + .../targets/netconf_get/tasks/sros.yaml | 16 +++ .../targets/netconf_get/tests/sros/basic.yaml | 56 ++++++++++ .../targets/prepare_sros_tests/tasks/main.yml | 6 ++ 8 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 lib/ansible/plugins/netconf/sros.py create mode 100644 test/integration/targets/netconf_get/tasks/sros.yaml create mode 100644 test/integration/targets/netconf_get/tests/sros/basic.yaml create mode 100644 test/integration/targets/prepare_sros_tests/tasks/main.yml diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index bafbcdc9e1..0e19faf049 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -1057,6 +1057,9 @@ files: lib/ansible/plugins/lookup/onepassword_raw.py: maintainers: samdoran ignored: azenk + lib/ansible/plugins/netconf/sros.py: + maintainers: wisotzky $team_networking + labels: networking lib/ansible/plugins/shell/powershell.py: maintainers: $team_windows_core labels: diff --git a/lib/ansible/plugins/connection/netconf.py b/lib/ansible/plugins/connection/netconf.py index ed38f3404b..abd3989fb4 100644 --- a/lib/ansible/plugins/connection/netconf.py +++ b/lib/ansible/plugins/connection/netconf.py @@ -159,7 +159,8 @@ logging.getLogger('ncclient').setLevel(logging.INFO) NETWORK_OS_DEVICE_PARAM_MAP = { "nxos": "nexus", - "ios": "default" + "ios": "default", + "sros": "alu" } diff --git a/lib/ansible/plugins/netconf/sros.py b/lib/ansible/plugins/netconf/sros.py new file mode 100644 index 0000000000..b6b5d0fc04 --- /dev/null +++ b/lib/ansible/plugins/netconf/sros.py @@ -0,0 +1,100 @@ +# +# (c) 2018 Red Hat Inc. +# +# 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 + +from ansible import constants as C +from ansible.module_utils._text import to_text, to_bytes +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") + + +class Netconf(NetconfBase): + def get_text(self, ele, tag): + try: + return to_text(ele.find(tag).text, errors='surrogate_then_replace').strip() + except AttributeError: + pass + + def get_device_info(self): + device_info = dict() + device_info['network_os'] = 'sros' + + xmlns = "urn:nokia.com:sros:ns:yang:sr:state" + f = '' % xmlns + reply = to_ele(self.m.get(filter=('subtree', f)).data_xml) + + device_info['network_os_hostname'] = reply.findtext('.//{%s}state/{*}system/{*}lldp/{*}system-name' % xmlns) + device_info['network_os_version'] = reply.findtext('.//{%s}state/{*}system/{*}version/{*}version-number' % xmlns) + device_info['network_os_model'] = reply.findtext('.//{%s}state/{*}system/{*}platform' % xmlns) + device_info['network_os_platform'] = 'Nokia 7x50' + return device_info + + def get_capabilities(self): + result = dict() + result['rpc'] = self.get_base_rpc() + ['commit', 'discard_changes', 'validate', 'lock', 'unlock'] + 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 + result['device_operations'] = self.get_device_operations(result['server_capabilities']) + 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=obj._play_context.private_key_file, + hostkey_verify=C.HOST_KEY_CHECKING, + look_for_keys=C.PARAMIKO_LOOK_FOR_KEYS, + allow_agent=obj._play_context.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('urn:nokia.com:sros:ns:yang:sr', c): + guessed_os = 'sros' + + m.close_session() + return guessed_os diff --git a/test/integration/targets/netconf_get/meta/main.yml b/test/integration/targets/netconf_get/meta/main.yml index 3403f48112..0fb2b09f0e 100644 --- a/test/integration/targets/netconf_get/meta/main.yml +++ b/test/integration/targets/netconf_get/meta/main.yml @@ -2,3 +2,4 @@ dependencies: - { role: prepare_junos_tests, when: ansible_network_os == 'junos' } - { role: prepare_iosxr_tests, when: ansible_network_os == 'iosxr' } + - { role: prepare_sros_tests, when: ansible_network_os == 'sros' } diff --git a/test/integration/targets/netconf_get/tasks/main.yaml b/test/integration/targets/netconf_get/tasks/main.yaml index 4d8eb94cd5..a34a2fecd6 100644 --- a/test/integration/targets/netconf_get/tasks/main.yaml +++ b/test/integration/targets/netconf_get/tasks/main.yaml @@ -1,3 +1,4 @@ --- - { include: junos.yaml, when: ansible_network_os == 'junos', tags: ['netconf'] } - { include: iosxr.yaml, when: ansible_network_os == 'iosxr', tags: ['netconf'] } +- { include: sros.yaml, when: ansible_network_os == 'sros', tags: ['netconf'] } diff --git a/test/integration/targets/netconf_get/tasks/sros.yaml b/test/integration/targets/netconf_get/tasks/sros.yaml new file mode 100644 index 0000000000..bc8728b82e --- /dev/null +++ b/test/integration/targets/netconf_get/tasks/sros.yaml @@ -0,0 +1,16 @@ +--- +- name: collect all netconf test cases + find: + paths: "{{ role_path }}/tests/sros" + patterns: "{{ testcase }}.yaml" + register: test_cases + connection: local + +- 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 }} ansible_connection=netconf" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/netconf_get/tests/sros/basic.yaml b/test/integration/targets/netconf_get/tests/sros/basic.yaml new file mode 100644 index 0000000000..1a68ff3243 --- /dev/null +++ b/test/integration/targets/netconf_get/tests/sros/basic.yaml @@ -0,0 +1,56 @@ +--- +- debug: msg="START netconf_get sros/basic.yaml on connection={{ ansible_connection }}" + +- name: Get complete configuration data (SROS) + netconf_get: + filter: + register: result + +- assert: + that: + - "'urn:nokia.com:sros:ns:yang:sr:conf' in result.stdout" + - "'urn:nokia.com:sros:ns:yang:sr:state' not in result.stdout" + +- name: Get complete state data (SROS) + netconf_get: + filter: + register: result + +- assert: + that: + - "'urn:nokia.com:sros:ns:yang:sr:state' in result.stdout" + - "'urn:nokia.com:sros:ns:yang:sr:conf' not in result.stdout" + +- name: Get service configuration data from candidate datastore (SROS) + netconf_get: + source: candidate + filter: + display: json + register: result + +- assert: + that: + - "'' in result.stdout" + +- name: Get system configuration data from running datastore (SROS) + netconf_get: + source: running + filter: + register: result + +- assert: + that: + - "'' in result.stdout" + +- name: Get complete configuration and state data (SROS) + netconf_get: + register: result + +- assert: + that: + - "'' in result.stdout" + - "'' in result.stdout" + - "'urn:nokia.com:sros:ns:yang:sr:conf' in result.stdout" + - "'urn:nokia.com:sros:ns:yang:sr:state' in result.stdout" + +- debug: msg="END netconf_get sros/basic.yaml on connection={{ ansible_connection }}" diff --git a/test/integration/targets/prepare_sros_tests/tasks/main.yml b/test/integration/targets/prepare_sros_tests/tasks/main.yml new file mode 100644 index 0000000000..352484edd5 --- /dev/null +++ b/test/integration/targets/prepare_sros_tests/tasks/main.yml @@ -0,0 +1,6 @@ +--- +- debug: msg="START prepare_sros_tests/main.yaml" + +- name: wait until everything is ready to go + pause: + seconds: 1