diff --git a/changelogs/fragments/160_available_network_resources.yaml b/changelogs/fragments/160_available_network_resources.yaml new file mode 100644 index 0000000..c7a92f2 --- /dev/null +++ b/changelogs/fragments/160_available_network_resources.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - Add support for available_network_resources key, which allows to fetch the available resources for a platform (https://github.com/ansible-collections/vyos.vyos/issues/138). diff --git a/plugins/module_utils/network/vyos/argspec/facts/facts.py b/plugins/module_utils/network/vyos/argspec/facts/facts.py index d78dd3a..efd98e3 100644 --- a/plugins/module_utils/network/vyos/argspec/facts/facts.py +++ b/plugins/module_utils/network/vyos/argspec/facts/facts.py @@ -1,23 +1,24 @@ # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The arg spec for the vyos facts module. """ from __future__ import absolute_import, division, print_function __metaclass__ = type class FactsArgs(object): # pylint: disable=R0903 """The arg spec for the vyos facts module""" def __init__(self, **kwargs): pass argument_spec = { "gather_subset": dict( default=["!config"], type="list", elements="str" ), "gather_network_resources": dict(type="list", elements="str"), + "available_network_resources": {"type": "bool", "default": False}, } diff --git a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py index 7ef05cb..4057db5 100644 --- a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py +++ b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py @@ -1,106 +1,106 @@ # -*- coding: utf-8 -*- # Copyright 2020 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type """ The vyos ospf_interfaces fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( utils, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import ( Ospf_interfacesTemplate, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospf_interfaces.ospf_interfaces import ( Ospf_interfacesArgs, ) class Ospf_interfacesFacts(object): """The vyos ospf_interfaces facts class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = Ospf_interfacesArgs.argument_spec def get_device_data(self, connection): return connection.get( 'show configuration commands | match "set interfaces"' ) def get_config_set(self, data): - """ To classify the configurations beased on interface """ + """To classify the configurations beased on interface""" interface_list = [] config_set = [] int_string = "" for config_line in data.splitlines(): ospf_int = re.search(r"set interfaces \S+ (\S+) .*", config_line) if ospf_int: if ospf_int.group(1) not in interface_list: if int_string: config_set.append(int_string) interface_list.append(ospf_int.group(1)) int_string = "" int_string = int_string + config_line + "\n" if int_string: config_set.append(int_string) return config_set def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for Ospf_interfaces network resource :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ facts = {} objs = [] ospf_interfaces_parser = Ospf_interfacesTemplate( lines=[], module=self._module ) if not data: data = self.get_device_data(connection) # parse native config using the Ospf_interfaces template ospf_interfaces_facts = [] resources = self.get_config_set(data) for resource in resources: ospf_interfaces_parser = Ospf_interfacesTemplate( lines=resource.split("\n"), module=self._module ) objs = ospf_interfaces_parser.parse() for key, sortv in [("address_family", "afi")]: if key in objs and objs[key]: objs[key] = list(objs[key].values()) ospf_interfaces_facts.append(objs) ansible_facts["ansible_network_resources"].pop("ospf_interfaces", None) facts = {"ospf_interfaces": []} params = utils.remove_empties( ospf_interfaces_parser.validate_config( self.argument_spec, {"config": ospf_interfaces_facts}, redact=True, ) ) if params.get("config"): for cfg in params["config"]: facts["ospf_interfaces"].append(utils.remove_empties(cfg)) ansible_facts["ansible_network_resources"].update(facts) return ansible_facts diff --git a/plugins/modules/vyos_facts.py b/plugins/modules/vyos_facts.py index 01c43c2..8bffcda 100644 --- a/plugins/modules/vyos_facts.py +++ b/plugins/modules/vyos_facts.py @@ -1,177 +1,187 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type """ The module file for vyos_facts """ DOCUMENTATION = """ module: vyos_facts short_description: Get facts about vyos devices. description: - Collects facts from network devices running the vyos operating system. This module places the facts gathered in the fact tree keyed by the respective resource name. The facts module will always collect a base set of facts from the device and can enable or disable collection of additional facts. version_added: 1.0.0 author: - Nathaniel Case (@qalthos) - Nilashish Chakraborty (@Nilashishc) - Rohit Thakur (@rohitthakur2590) extends_documentation_fragment: - vyos.vyos.vyos notes: - Tested against VyOS 1.1.8 (helium). - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). options: gather_subset: description: - When supplied, this argument will restrict the facts collected to a given subset. Possible values for this argument include all, default, config, and neighbors. Can specify a list of values to include a larger subset. Values can also be used with an initial C(M(!)) to specify that a specific subset should not be collected. required: false default: '!config' type: list elements: str gather_network_resources: description: - When supplied, this argument will restrict the facts collected to a given subset. Possible values for this argument include all and the resources like interfaces. Can specify a list of values to include a larger subset. Values can also be used with an initial C(M(!)) to specify that a specific subset should not be collected. Valid subsets are 'all', 'interfaces', 'l3_interfaces', 'lag_interfaces', 'lldp_global', 'lldp_interfaces', 'static_routes', 'firewall_rules', 'firewall_global', 'firewall_interfaces', 'ospfv3', 'ospfv2'. required: false type: list elements: str + available_network_resources: + description: When 'True' a list of network resources for which resource modules are available will be provided. + type: bool + default: false """ EXAMPLES = """ # Gather all facts - vyos.vyos.vyos_facts: gather_subset: all gather_network_resources: all # collect only the config and default facts - vyos.vyos.vyos_facts: gather_subset: config # collect everything exception the config - vyos.vyos.vyos_facts: gather_subset: '!config' # Collect only the interfaces facts - vyos.vyos.vyos_facts: gather_subset: - '!all' - '!min' gather_network_resources: - interfaces # Do not collect interfaces facts - vyos.vyos.vyos_facts: gather_network_resources: - '!interfaces' # Collect interfaces and minimal default facts - vyos.vyos.vyos_facts: gather_subset: min gather_network_resources: interfaces """ RETURN = """ ansible_net_config: description: The running-config from the device returned: when config is configured type: str ansible_net_commits: description: The set of available configuration revisions returned: when present type: list ansible_net_hostname: description: The configured system hostname returned: always type: str ansible_net_model: description: The device model string returned: always type: str ansible_net_serialnum: description: The serial number of the device returned: always type: str ansible_net_version: description: The version of the software running returned: always type: str ansible_net_neighbors: description: The set of LLDP neighbors returned: when interface is configured type: list ansible_net_gather_subset: description: The list of subsets gathered by the module returned: always type: list ansible_net_api: description: The name of the transport returned: always type: str ansible_net_python_version: description: The Python version Ansible controller is using returned: always type: str ansible_net_gather_network_resources: description: The list of fact resource subsets collected from the device returned: always type: list """ from ansible.module_utils.basic import AnsibleModule from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.facts.facts import ( FactsArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, + FACT_RESOURCE_SUBSETS, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def main(): """ Main entry point for module execution :returns: ansible_facts """ argument_spec = FactsArgs.argument_spec argument_spec.update(vyos_argument_spec) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True ) warnings = [] if module.params["gather_subset"] == "!config": warnings.append( "default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards" ) + ansible_facts = {} + if module.params.get("available_network_resources"): + ansible_facts["available_network_resources"] = sorted( + FACT_RESOURCE_SUBSETS.keys() + ) result = Facts(module).get_facts() - - ansible_facts, additional_warnings = result + additional_facts, additional_warnings = result + ansible_facts.update(additional_facts) warnings.extend(additional_warnings) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) if __name__ == "__main__": main() diff --git a/tests/integration/targets/vyos_facts/tests/cli/basic_facts.yaml b/tests/integration/targets/vyos_facts/tests/cli/basic_facts.yaml index 5802f73..ede7ca2 100644 --- a/tests/integration/targets/vyos_facts/tests/cli/basic_facts.yaml +++ b/tests/integration/targets/vyos_facts/tests/cli/basic_facts.yaml @@ -1,42 +1,55 @@ --- - name: get host name register: vyos_host vyos.vyos.vyos_command: commands: - show host name - name: get version info register: vyos_version vyos.vyos.vyos_command: commands: - show version - name: collect all facts from the device register: result vyos.vyos.vyos_facts: gather_subset: all - name: check that hostname is present assert: that: - result.ansible_facts.ansible_net_hostname == vyos_host.stdout[0] - name: check that subsets are present assert: that: - "'neighbors' in result.ansible_facts.ansible_net_gather_subset" - "'default' in result.ansible_facts.ansible_net_gather_subset" - "'config' in result.ansible_facts.ansible_net_gather_subset" - name: check that version info is present assert: that: - result.ansible_facts.ansible_net_version in vyos_version.stdout_lines[0][0] - result.ansible_facts.ansible_net_model in vyos_version.stdout_lines[0][9] - result.ansible_facts.ansible_net_serialnum in vyos_version.stdout_lines[0][10] - name: check that config info is present assert: that: - result.ansible_facts.ansible_net_commits is defined - result.ansible_facts.ansible_net_config is defined + +- name: Get list of avaliable network resources for VyOS + register: result + vyos.vyos.vyos_facts: + available_network_resources: true + gather_network_resources: all + +- name: Assert that correct available_resources_list returned + assert: + that: + - result.changed == false + - "{{ result['ansible_facts']['available_network_resources'] | symmetric_difference(result['ansible_facts']['ansible_net_gather_network_resources']) |length\ + \ == 0 }}" diff --git a/tests/unit/modules/network/vyos/test_vyos_ping.py b/tests/unit/modules/network/vyos/test_vyos_ping.py index e307610..f3ba2ee 100644 --- a/tests/unit/modules/network/vyos/test_vyos_ping.py +++ b/tests/unit/modules/network/vyos/test_vyos_ping.py @@ -1,107 +1,107 @@ # (c) 2016 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 . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible_collections.vyos.vyos.tests.unit.compat.mock import patch from ansible_collections.vyos.vyos.plugins.modules import vyos_ping from ansible_collections.vyos.vyos.tests.unit.modules.utils import ( set_module_args, ) from .vyos_module import TestVyosModule, load_fixture class TestVyosPingModule(TestVyosModule): module = vyos_ping def setUp(self): super(TestVyosPingModule, self).setUp() self.mock_run_commands = patch( "ansible_collections.vyos.vyos.plugins.modules.vyos_ping.run_commands" ) self.run_commands = self.mock_run_commands.start() def tearDown(self): super(TestVyosPingModule, self).tearDown() self.mock_run_commands.stop() def load_fixtures(self, commands=None): def load_from_file(*args, **kwargs): commands = kwargs["commands"] output = list() for command in commands: filename = str(command).split(" | ")[0].replace(" ", "_") output.append(load_fixture("vyos_ping_%s" % filename)) return output self.run_commands.side_effect = load_from_file def test_vyos_ping_expected_success(self): - """ Test for successful pings when destination should be reachable """ + """Test for successful pings when destination should be reachable""" set_module_args(dict(count=2, dest="10.10.10.10")) self.execute_module() def test_vyos_ping_expected_failure(self): - """ Test for unsuccessful pings when destination should not be reachable """ + """Test for unsuccessful pings when destination should not be reachable""" set_module_args(dict(count=4, dest="10.10.10.20", state="absent")) self.execute_module() def test_vyos_ping_unexpected_success(self): - """ Test for successful pings when destination should not be reachable - FAIL. """ + """Test for successful pings when destination should not be reachable - FAIL.""" set_module_args(dict(count=2, dest="10.10.10.10", state="absent")) self.execute_module(failed=True) def test_vyos_ping_unexpected_failure(self): - """ Test for unsuccessful pings when destination should be reachable - FAIL. """ + """Test for unsuccessful pings when destination should be reachable - FAIL.""" set_module_args(dict(count=4, dest="10.10.10.20")) self.execute_module(failed=True) def test_vyos_ping_failure_stats(self): """Test for asserting stats when ping fails""" set_module_args(dict(count=4, dest="10.10.10.20")) result = self.execute_module(failed=True) self.assertEqual(result["packet_loss"], "100%") self.assertEqual(result["packets_rx"], 0) self.assertEqual(result["packets_tx"], 4) def test_vyos_ping_success_stats(self): """Test for asserting stats when ping passes""" set_module_args(dict(count=2, dest="10.10.10.10")) result = self.execute_module() self.assertEqual(result["packet_loss"], "0%") self.assertEqual(result["packets_rx"], 2) self.assertEqual(result["packets_tx"], 2) self.assertEqual(result["rtt"]["min"], 12) self.assertEqual(result["rtt"]["avg"], 17) self.assertEqual(result["rtt"]["max"], 22) self.assertEqual(result["rtt"]["mdev"], 10) def test_vyos_ping_success_stats_with_options(self): set_module_args(dict(count=10, ttl=128, size=512, dest="10.10.10.11")) result = self.execute_module() self.assertEqual(result["packet_loss"], "0%") self.assertEqual(result["packets_rx"], 10) self.assertEqual(result["packets_tx"], 10) self.assertEqual(result["rtt"]["min"], 1) self.assertEqual(result["rtt"]["avg"], 3) self.assertEqual(result["rtt"]["max"], 21) self.assertEqual(result["rtt"]["mdev"], 5)