From 943107b1b9ac9b551532b13517578fdd98843ded Mon Sep 17 00:00:00 2001 From: Samer Deeb Date: Mon, 8 Jan 2018 06:29:16 -0800 Subject: [PATCH] Add new module mlnxos_facts for retrieving facts of MLNX-OS Mellanox network devices (#34287) * Add new module mlnxos_facts for retrieving facts of MLNX-OS Mellanox network devices Signed-off-by: Samer Deeb * Fix Documenation and Examples Signed-off-by: Samer Deeb --- .../modules/network/mlnxos/mlnxos_facts.py | 238 ++++++++++++++++++ .../mlnxos_facts_show_interfaces_ethernet.cfg | 20 ++ .../fixtures/mlnxos_facts_show_module.cfg | 7 + .../fixtures/mlnxos_facts_show_version.cfg | 19 ++ .../network/mlnxos/test_mlnxos_facts.py | 71 ++++++ 5 files changed, 355 insertions(+) create mode 100644 lib/ansible/modules/network/mlnxos/mlnxos_facts.py create mode 100644 test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_interfaces_ethernet.cfg create mode 100644 test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_module.cfg create mode 100644 test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_version.cfg create mode 100644 test/units/modules/network/mlnxos/test_mlnxos_facts.py diff --git a/lib/ansible/modules/network/mlnxos/mlnxos_facts.py b/lib/ansible/modules/network/mlnxos/mlnxos_facts.py new file mode 100644 index 00000000000..ce09c6b8222 --- /dev/null +++ b/lib/ansible/modules/network/mlnxos/mlnxos_facts.py @@ -0,0 +1,238 @@ +#!/usr/bin/python +# +# Copyright: Ansible Project +# 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 + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +--- +module: mlnxos_facts +version_added: "2.5" +author: "Waleed Mousa (@waleedym), Samer Deeb (@samerd)" +short_description: Collect facts from Mellanox MLNX-OS network devices +description: + - Collects a base set of device facts from a MLNX-OS Mellanox network devices + This module prepends all of the base network fact keys with + C(ansible_net_). The facts module will always collect a base set of + facts from the device and can enable or disable collection of additional + facts. +notes: + - Tested against MLNX-OS 3.6 +options: + gather_subset: + description: + - When supplied, this argument will restrict the facts collected + to a given subset. Possible values for this argument include + all, version, module, and 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. + required: false + default: version +""" + +EXAMPLES = """ +--- +- name: Collect all facts from the device + mlnxos_facts: + gather_subset: all + +- name: Collect only the interfaces facts + mlnxos_facts: + gather_subset: + - interfaces + +- name: Do not collect version facts + mlnxos_facts: + gather_subset: + - "!version" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# version +ansible_net_version: + description: A hash of all curently running system image information + returned: when version is configured or when no gather_subset is provided + type: dict + +# modules +ansible_net_modules: + description: A hash of all modules on the systeme with status + returned: when modules is configured + type: dict + +# interfaces +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + +from ansible.module_utils.network.mlnxos.mlnxos import BaseMlnxosModule +from ansible.module_utils.network.mlnxos.mlnxos import show_cmd + + +class MlnxosFactsModule(BaseMlnxosModule): + + def get_runable_subset(self, gather_subset): + runable_subsets = set() + exclude_subsets = set() + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(VALID_SUBSETS) + continue + + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(VALID_SUBSETS) + continue + exclude = True + else: + exclude = False + + if subset not in VALID_SUBSETS: + self._module.fail_json(msg='Bad subset') + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(VALID_SUBSETS) + + runable_subsets.difference_update(exclude_subsets) + if not runable_subsets: + runable_subsets.add('version') + return runable_subsets + + def init_module(self): + """ module intialization + """ + argument_spec = dict( + gather_subset=dict(default=['version'], type='list') + ) + self._module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True) + + def run(self): + self.init_module() + gather_subset = self._module.params['gather_subset'] + runable_subsets = self.get_runable_subset(gather_subset) + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + facter_cls = FACT_SUBSETS[key] + instances.append(facter_cls(self._module)) + + for inst in instances: + inst.populate() + facts.update(inst.facts) + + ansible_facts = dict() + for key, value in iteritems(facts): + key = 'ansible_net_%s' % key + ansible_facts[key] = value + self._module.exit_json(ansible_facts=ansible_facts) + + +class FactsBase(object): + + COMMANDS = [''] + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + + def _show_cmd(self, cmd): + return show_cmd(self.module, cmd, json_fmt=True) + + def populate(self): + self.responses = [] + for cmd in self.COMMANDS: + self.responses.append(self._show_cmd(cmd)) + + +class Version(FactsBase): + + COMMANDS = ['show version'] + + def populate(self): + super(Version, self).populate() + data = self.responses[0] + if data: + self.facts['version'] = data + + +class Module(FactsBase): + + COMMANDS = ['show module'] + + def populate(self): + super(Module, self).populate() + data = self.responses[0] + if data: + self.facts['modules'] = data + + +class Interfaces(FactsBase): + + COMMANDS = ['show interfaces ethernet'] + + def populate(self): + super(Interfaces, self).populate() + + data = self.responses[0] + if data: + self.facts['interfaces'] = self.populate_interfaces(data) + + def populate_interfaces(self, interfaces): + interfaces_dict = dict() + for if_data in interfaces: + if_dict = dict() + if_dict["MAC Address"] = if_data["Mac address"] + if_dict["Actual Speed"] = if_data["Actual speed"] + if_dict["MTU"] = if_data["MTU"] + if_dict["Admin State"] = if_data["Admin state"] + if_dict["Operational State"] = if_data["Operational state"] + if_name = if_dict["Interface Name"] = if_data["header"] + interfaces_dict[if_name] = if_dict + return interfaces_dict + + +FACT_SUBSETS = dict( + version=Version, + modules=Module, + interfaces=Interfaces +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """ main entry point for module execution + """ + MlnxosFactsModule.main() + + +if __name__ == '__main__': + main() diff --git a/test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_interfaces_ethernet.cfg b/test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_interfaces_ethernet.cfg new file mode 100644 index 00000000000..03128f1e018 --- /dev/null +++ b/test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_interfaces_ethernet.cfg @@ -0,0 +1,20 @@ +[ + { + "Fec": "auto", + "Mac address": "7c:fe:90:e5:ca:3c", + "Actual speed": "100 Gbps", + "MTU": "1500 bytes(Maximum packet size 1522 bytes)", + "header": "Eth1/1", + "Admin state": "Enabled", + "Operational state": "Down" + }, + { + "Fec": "auto", + "Mac address": "7c:fe:90:e5:ca:3e", + "Actual speed": "100 Gbps", + "MTU": "1500 bytes(Maximum packet size 1522 bytes)", + "header": "Eth1/2", + "Admin state": "Enabled", + "Operational state": "Down" + } +] \ No newline at end of file diff --git a/test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_module.cfg b/test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_module.cfg new file mode 100644 index 00000000000..a82fcf75198 --- /dev/null +++ b/test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_module.cfg @@ -0,0 +1,7 @@ +{ + "MGMT": [ + { + "Status": "ready" + } + ] +} diff --git a/test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_version.cfg b/test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_version.cfg new file mode 100644 index 00000000000..9a486e8c4e8 --- /dev/null +++ b/test/units/modules/network/mlnxos/fixtures/mlnxos_facts_show_version.cfg @@ -0,0 +1,19 @@ +{ + "Uptime": "2d 13h 40m 34.992s", + "Product model": "x86onie", + "Build date": "2017-11-10 18:14:32", + "Target arch": "x86_64", + "Target hw": "x86_64", + "Number of CPUs": "4", + "Build ID": "#1-dev", + "CPU load averages": "0.21 / 0.07 / 0.06", + "Host ID": "248A07B0141C", + "System serial num": "MT1708X07233", + "System UUID": "03d242b6-1a24-11e7-8000-248a07f55400", + "Swap": "0 MB used / 0 MB free / 0 MB total", + "Product name": "MLNX-OS", + "Built by": "jenkins@cc45f26cd083", + "System memory": "2597 MB used / 5213 MB free / 7810 MB total", + "Version summary": "X86_64 3.6.5000 2017-11-10 18:14:32 x86_64", + "Product release": "3.6.5000" +} diff --git a/test/units/modules/network/mlnxos/test_mlnxos_facts.py b/test/units/modules/network/mlnxos/test_mlnxos_facts.py new file mode 100644 index 00000000000..02200b6fe04 --- /dev/null +++ b/test/units/modules/network/mlnxos/test_mlnxos_facts.py @@ -0,0 +1,71 @@ +# +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.compat.tests.mock import patch +from units.modules.utils import set_module_args +from .mlnxos_module import TestMlnxosModule, load_fixture +from ansible.modules.network.mlnxos import mlnxos_facts + + +class TestMlnxosFacts(TestMlnxosModule): + + module = mlnxos_facts + + def setUp(self): + super(TestMlnxosFacts, self).setUp() + + self.mock_run_command = patch.object( + mlnxos_facts.FactsBase, "_show_cmd") + self.run_command = self.mock_run_command.start() + + def tearDown(self): + super(TestMlnxosFacts, self).tearDown() + + self.mock_run_command.stop() + + def load_fixtures(self, commands=None, transport=None): + + def load_from_file(*args, **kwargs): + command = args[0] + filename = "mlnxos_facts_%s.cfg" % command + filename = filename.replace(' ', '_') + filename = filename.replace('/', '7') + output = load_fixture(filename) + return output + + self.run_command.side_effect = load_from_file + + def test_mlnxos_facts_version(self): + set_module_args(dict(gather_subset='version')) + result = self.execute_module() + facts = result.get('ansible_facts') + self.assertEqual(len(facts), 2) + version = facts['ansible_net_version'] + self.assertEqual(version['Product name'], 'MLNX-OS') + + def test_mlnxos_facts_modules(self): + set_module_args(dict(gather_subset='modules')) + result = self.execute_module() + facts = result.get('ansible_facts') + self.assertEqual(len(facts), 2) + modules = facts['ansible_net_modules'] + self.assertIn("MGMT", modules) + + def test_mlnxos_facts_interfaces(self): + set_module_args(dict(gather_subset='interfaces')) + result = self.execute_module() + facts = result.get('ansible_facts') + self.assertEqual(len(facts), 2) + interfaces = facts['ansible_net_interfaces'] + self.assertEqual(len(interfaces), 2) + + def test_mlnxos_facts_all(self): + set_module_args(dict(gather_subset='all')) + result = self.execute_module() + facts = result.get('ansible_facts') + self.assertEqual(len(facts), 4)