diff --git a/lib/ansible/module_utils/network/nso/nso.py b/lib/ansible/module_utils/network/nso/nso.py index 170c4ce19f4..9af19774293 100644 --- a/lib/ansible/module_utils/network/nso/nso.py +++ b/lib/ansible/module_utils/network/nso/nso.py @@ -159,6 +159,17 @@ class JsonRpc(object): resp, resp_json = self._write_call(payload) return resp_json['result'] + def show_config(self, path, operational=False): + payload = { + 'method': 'show_config', + 'params': { + 'path': path, + 'result_as': 'json', + 'with_oper': operational} + } + resp, resp_json = self._read_call(payload) + return resp_json['result'] + def run_action(self, th, path, params=None): if params is None: params = {} @@ -481,16 +492,40 @@ def connect(params): return client -def verify_version(client): +def verify_version(client, required_versions=None): + if required_versions is None: + required_versions = [(4, 5), (4, 4, 3)] + version_str = client.get_system_setting('version') + if not verify_version_str(version_str, required_versions): + supported_versions = ', '.join( + ['.'.join([str(p) for p in required_version]) + for required_version in required_versions]) + raise ModuleFailException( + 'unsupported NSO version {0}. {1} or later supported'.format( + version_str, supported_versions)) + + +def verify_version_str(version_str, required_versions): version = [int(p) for p in version_str.split('.')] if len(version) < 2: raise ModuleFailException( 'unsupported NSO version format {0}'.format(version_str)) - if (version[0] < 4 or version[1] < 4 or - (version[1] == 4 and (len(version) < 3 or version[2] < 3))): - raise ModuleFailException( - 'unsupported NSO version {0}, only 4.4.3 or later is supported'.format(version_str)) + + def check_version(required_version, version): + for pos in range(len(required_version)): + if pos >= len(version): + return False + if version[pos] > required_version[pos]: + return True + if version[pos] < required_version[pos]: + return False + return True + + for required_version in required_versions: + if check_version(required_version, version): + return True + return False def normalize_value(expected_value, value, key): diff --git a/lib/ansible/modules/network/nso/nso_action.py b/lib/ansible/modules/network/nso/nso_action.py index 93abfccfcd7..74539a9e0d6 100644 --- a/lib/ansible/modules/network/nso/nso_action.py +++ b/lib/ansible/modules/network/nso/nso_action.py @@ -36,6 +36,8 @@ short_description: Executes Cisco NSO actions and verifies output. description: - This module provices support for executing Cisco NSO actions and then verifying that the output is as expected. +requirements: + - Cisco NSO version 4.4.3 or higher. author: "Claes Nästén (@cnasten)" options: path: diff --git a/lib/ansible/modules/network/nso/nso_config.py b/lib/ansible/modules/network/nso/nso_config.py index dc0296ad3b7..1250c87c9d4 100644 --- a/lib/ansible/modules/network/nso/nso_config.py +++ b/lib/ansible/modules/network/nso/nso_config.py @@ -36,6 +36,8 @@ short_description: Manage Cisco NSO configuration and service synchronization. description: - This module provides support for managing configuration in Cisco NSO and can also ensure services are in sync. +requirements: + - Cisco NSO version 4.4.3 or higher. author: "Claes Nästén (@cnasten)" options: data: diff --git a/lib/ansible/modules/network/nso/nso_show.py b/lib/ansible/modules/network/nso/nso_show.py new file mode 100644 index 00000000000..5eb24f63af7 --- /dev/null +++ b/lib/ansible/modules/network/nso/nso_show.py @@ -0,0 +1,129 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = ''' +--- +module: nso_show +extends_documentation_fragment: nso +short_description: Displays data from Cisco NSO. +description: + - This module provides support for displaying data from Cisco NSO. +requirements: + - Cisco NSO version 3.4.12 or higher, 4.1.9 or higher, 4.2.6 or higher, + 4.3.7 or higher, 4.4.5 or higher, 4.5 or higher. +author: "Claes Nästén (@cnasten)" +options: + path: + description: Path to NSO data. + required: true + operational: + description: > + Controls wheter or not operational data is included in the result. +version_added: "2.5" +''' + +EXAMPLES = ''' +- name: Show devices including operational data + nso_show: + url: http://localhost:8080/jsonrpc + username: username + password: password + path: /ncs:devices/device + operational: true +''' + +RETURN = ''' +output: + description: Configuration + returned: success + type: dict +''' + +from ansible.module_utils.network.nso.nso import connect, verify_version, nso_argument_spec +from ansible.module_utils.network.nso.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoShow(object): + REQUIRED_VERSIONS = [ + (4, 5), + (4, 4, 5), + (4, 3, 7), + (4, 2, 6), + (4, 1, 9), + (3, 4, 12) + ] + + def __init__(self, check_mode, client, path, operational): + self._check_mode = check_mode + self._client = client + self._path = path + self._operational = operational + + def main(self): + if self._check_mode: + return {} + else: + return self._client.show_config(self._path, self._operational) + + +def main(): + argument_spec = dict( + path=dict(required=True, type='str'), + operational=dict(required=False, type='bool', default=False) + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_show = NsoShow( + module.check_mode, client, + p['path'], p['operational']) + try: + verify_version(client, NsoShow.REQUIRED_VERSIONS) + + output = nso_show.main() + client.logout() + module.exit_json(changed=False, output=output) + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/network/nso/nso_verify.py b/lib/ansible/modules/network/nso/nso_verify.py index 95a57bffceb..74b3f76ab90 100644 --- a/lib/ansible/modules/network/nso/nso_verify.py +++ b/lib/ansible/modules/network/nso/nso_verify.py @@ -36,6 +36,8 @@ short_description: Verifies Cisco NSO configuration. description: - This module provides support for verifying Cisco NSO configuration is in compliance with specified values. +requirements: + - Cisco NSO version 4.4.3 or higher. author: "Claes Nästén (@cnasten)" options: data: diff --git a/lib/ansible/utils/module_docs_fragments/nso.py b/lib/ansible/utils/module_docs_fragments/nso.py index 3ce8d236d6f..75a3ddf0b17 100644 --- a/lib/ansible/utils/module_docs_fragments/nso.py +++ b/lib/ansible/utils/module_docs_fragments/nso.py @@ -22,8 +22,6 @@ class ModuleDocFragment(object): DOCUMENTATION = ''' -notes: - - Cisco NSO version 4.4.3 or higher required. options: url: description: NSO JSON-RPC URL, http://localhost:8080/jsonrpc @@ -34,6 +32,4 @@ options: password: description: NSO password required: true -requirements: - - Cisco NSO version 4.4.3 or higher ''' diff --git a/test/units/module_utils/network/nso/test_nso.py b/test/units/module_utils/network/nso/test_nso.py index 10224fb76f0..e41e8c2594f 100644 --- a/test/units/module_utils/network/nso/test_nso.py +++ b/test/units/module_utils/network/nso/test_nso.py @@ -336,3 +336,22 @@ class TestValueBuilder(unittest.TestCase): self.assertEquals('nested-value', value.value) self.assertEqual(0, len(calls)) + + +class TestVerifyVersion(unittest.TestCase): + def test_valid_versions(self): + self.assertTrue(nso.verify_version_str('5.0', [(4, 6), (4, 5, 1)])) + self.assertTrue(nso.verify_version_str('5.1.1', [(4, 6), (4, 5, 1)])) + self.assertTrue(nso.verify_version_str('5.1.1.2', [(4, 6), (4, 5, 1)])) + self.assertTrue(nso.verify_version_str('4.6', [(4, 6), (4, 5, 1)])) + self.assertTrue(nso.verify_version_str('4.6.2', [(4, 6), (4, 5, 1)])) + self.assertTrue(nso.verify_version_str('4.6.2.1', [(4, 6), (4, 5, 1)])) + self.assertTrue(nso.verify_version_str('4.5.1', [(4, 6), (4, 5, 1)])) + self.assertTrue(nso.verify_version_str('4.5.2', [(4, 6), (4, 5, 1)])) + self.assertTrue(nso.verify_version_str('4.5.1.2', [(4, 6), (4, 5, 1)])) + + def test_invalid_versions(self): + self.assertFalse(nso.verify_version_str('4.4', [(4, 6), (4, 5, 1)])) + self.assertFalse(nso.verify_version_str('4.4.1', [(4, 6), (4, 5, 1)])) + self.assertFalse(nso.verify_version_str('4.4.1.2', [(4, 6), (4, 5, 1)])) + self.assertFalse(nso.verify_version_str('4.5.0', [(4, 6), (4, 5, 1)])) diff --git a/test/units/modules/network/nso/test_nso_show.py b/test/units/modules/network/nso/test_nso_show.py new file mode 100644 index 00000000000..75e7014e02b --- /dev/null +++ b/test/units/modules/network/nso/test_nso_show.py @@ -0,0 +1,96 @@ +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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) + +import json + +from ansible.compat.tests.mock import patch +from ansible.modules.network.nso import nso_show +from . import nso_module +from .nso_module import MockResponse + + +class TestNsoShow(nso_module.TestNsoModule): + module = nso_show + + @patch('ansible.module_utils.network.nso.nso.open_url') + def test_nso_show_missing(self, open_url_mock): + path = '/ncs:devices/device{ce0}/missing' + calls = [ + MockResponse('login', {}, 200, '{}', {'set-cookie': 'id'}), + MockResponse('get_system_setting', {'operation': 'version'}, 200, '{"result": "4.5"}'), + MockResponse('new_trans', {'mode': 'read'}, 200, '{"result": {"th": 1}}'), + MockResponse('show_config', + {'path': path, 'result_as': 'json'}, 200, + '{"error": {"data": {"param": "path"}, "type": "rpc.method.invalid_params"}}'), + MockResponse('logout', {}, 200, '{"result": {}}'), + ] + open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) + + nso_module.set_module_args({ + 'username': 'user', 'password': 'password', + 'url': 'http://localhost:8080/jsonrpc', + 'path': path + }) + self.execute_module(failed=True, msg='NSO show_config invalid params. path = /ncs:devices/device{ce0}/missing') + + self.assertEqual(0, len(calls)) + + @patch('ansible.module_utils.network.nso.nso.open_url') + def test_nso_show_config(self, open_url_mock): + path = '/ncs:devices/device{ce0}' + calls = [ + MockResponse('login', {}, 200, '{}', {'set-cookie': 'id'}), + MockResponse('get_system_setting', {'operation': 'version'}, 200, '{"result": "4.5"}'), + MockResponse('new_trans', {'mode': 'read'}, 200, '{"result": {"th": 1}}'), + MockResponse('show_config', {'path': path, 'result_as': 'json'}, 200, '{"result": {"data": {}}}'), + MockResponse('logout', {}, 200, '{"result": {}}'), + ] + open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) + + nso_module.set_module_args({ + 'username': 'user', 'password': 'password', + 'url': 'http://localhost:8080/jsonrpc', + 'path': path, + 'operational': False + }) + self.execute_module(changed=False, output={"data": {}}) + self.assertEqual(0, len(calls)) + + @patch('ansible.module_utils.network.nso.nso.open_url') + def test_nso_show_config_and_oper(self, open_url_mock): + path = '/ncs:devices/device{ce0}/sync-from' + calls = [ + MockResponse('login', {}, 200, '{}', {'set-cookie': 'id'}), + MockResponse('get_system_setting', {'operation': 'version'}, 200, '{"result": "4.5"}'), + MockResponse('new_trans', {'mode': 'read'}, 200, '{"result": {"th": 1}}'), + MockResponse('show_config', {'path': path, 'result_as': 'json'}, 200, '{"result": {"data": {}}}'), + MockResponse('logout', {}, 200, '{"result": {}}'), + ] + open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) + + nso_module.set_module_args({ + 'username': 'user', 'password': 'password', + 'url': 'http://localhost:8080/jsonrpc', + 'path': path, + 'operational': True + }) + self.execute_module(changed=False, output={"data": {}}) + + self.assertEqual(0, len(calls))