diff --git a/lib/ansible/modules/network/vyos/vyos_facts.py b/lib/ansible/modules/network/vyos/vyos_facts.py index ff081f8abc..2df5770c5c 100644 --- a/lib/ansible/modules/network/vyos/vyos_facts.py +++ b/lib/ansible/modules/network/vyos/vyos_facts.py @@ -15,15 +15,17 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # -ANSIBLE_METADATA = {'status': ['preview'], - 'supported_by': 'community', - 'version': '1.0'} +ANSIBLE_METADATA = { + 'status': ['preview'], + 'supported_by': 'core', + 'version': '1.0', +} DOCUMENTATION = """ --- module: vyos_facts version_added: "2.2" -author: "Peter Sprygada (@privateip)" +author: "Nathaniel Case (@qalthos)" short_description: Collect facts from remote devices running OS description: - Collects a base set of device facts from a remote device that @@ -31,13 +33,12 @@ description: base network fact keys with U(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. -extends_documentation_fragment: vyos options: gather_subset: description: - When supplied, this argument will restrict the facts collected to a given subset. Possible values for this argument include - all, hardware, config, and interfaces. Can specify a list of + 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. @@ -46,15 +47,6 @@ options: """ EXAMPLES = """ -# Note: examples below use the following provider dict to handle -# transport and authentication to the node -vars: - cli: - host: "{{ inventory_hostname }}" - username: vyos - password: vyos - transport: cli - - name: collect all facts from the device vyos_facts: gather_subset: all @@ -104,39 +96,40 @@ ansible_net_gather_subset: """ import re -import ansible.module_utils.vyos -from ansible.module_utils.pycompat24 import get_exception -from ansible.module_utils.netcli import CommandRunner -from ansible.module_utils.network import NetworkModule +from ansible.module_utils.local import LocalAnsibleModule from ansible.module_utils.six import iteritems +from ansible.module_utils.vyos import run_commands class FactsBase(object): - def __init__(self, runner): - self.runner = runner + COMMANDS = frozenset() + + def __init__(self, module): + self.module = module self.facts = dict() + self.responses = None - self.commands() - - def commands(self): - raise NotImplementedError + def populate(self): + self.responses = run_commands(self.module, list(self.COMMANDS)) class Default(FactsBase): - def commands(self): - self.runner.add_command('show version') - self.runner.add_command('show host name') + COMMANDS = [ + 'show version', + 'show host name', + ] def populate(self): - data = self.runner.get_command('show version') + super(Default, self).populate() + data = self.responses[0] self.facts['version'] = self.parse_version(data) self.facts['serialnum'] = self.parse_serialnum(data) self.facts['model'] = self.parse_model(data) - self.facts['hostname'] = self.runner.get_command('show host name') + self.facts['hostname'] = self.responses[1] def parse_version(self, data): match = re.search(r'Version:\s*(\S+)', data) @@ -156,16 +149,17 @@ class Default(FactsBase): class Config(FactsBase): - def commands(self): - self.runner.add_command('show configuration commands') - self.runner.add_command('show system commit') + COMMANDS = [ + 'show configuration commands', + 'show system commit', + ] def populate(self): + super(Config, self).populate() - config = self.runner.get_command('show configuration commands') - self.facts['config'] = str(config).split('\n') + self.facts['config'] = self.responses - commits = self.runner.get_command('show system commit') + commits = self.responses[1] entries = list() entry = None @@ -188,15 +182,18 @@ class Config(FactsBase): class Neighbors(FactsBase): - def commands(self): - self.runner.add_command('show lldp neighbors') - self.runner.add_command('show lldp neighbors detail') + COMMANDS = [ + 'show lldp neighbors', + 'show lldp neighbors detail', + ] def populate(self): - all_neighbors = self.runner.get_command('show lldp neighbors') + super(Neighbors, self).populate() + + all_neighbors = self.responses[0] if 'LLDP not configured' not in all_neighbors: neighbors = self.parse( - self.runner.get_command('show lldp neighbors detail') + self.responses[1] ) self.facts['neighbors'] = self.parse_neighbors(neighbors) @@ -204,7 +201,7 @@ class Neighbors(FactsBase): parsed = list() values = None for line in data.split('\n'): - if len(line) == 0: + if not line: continue elif line[0] == ' ': values += '\n%s' % line @@ -250,11 +247,11 @@ VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) def main(): - spec = dict( + argument_spec = dict( gather_subset=dict(default=['!config'], type='list') ) - module = NetworkModule(argument_spec=spec, supports_check_mode=True) + module = LocalAnsibleModule(argument_spec=argument_spec, supports_check_mode=True) gather_subset = module.params['gather_subset'] @@ -276,7 +273,8 @@ def main(): exclude = False if subset not in VALID_SUBSETS: - module.fail_json(msg='Bad subset') + module.fail_json(msg='Subset must be one of [%s], got %s' % + (', '.join(VALID_SUBSETS), subset)) if exclude: exclude_subsets.add(subset) @@ -292,21 +290,13 @@ def main(): facts = dict() facts['gather_subset'] = list(runable_subsets) - runner = CommandRunner(module) - instances = list() for key in runable_subsets: - instances.append(FACT_SUBSETS[key](runner)) + instances.append(FACT_SUBSETS[key](module)) - runner.run() - - try: - for inst in instances: - inst.populate() - facts.update(inst.facts) - except Exception: - exc = get_exception() - module.fail_json(msg='unknown failure', output=runner.items, exc=str(exc)) + for inst in instances: + inst.populate() + facts.update(inst.facts) ansible_facts = dict() for key, value in iteritems(facts): diff --git a/test/units/modules/network/vyos/fixtures/show_host_name b/test/units/modules/network/vyos/fixtures/show_host_name new file mode 100644 index 0000000000..e89bc06462 --- /dev/null +++ b/test/units/modules/network/vyos/fixtures/show_host_name @@ -0,0 +1 @@ +vyos01 diff --git a/test/units/modules/network/vyos/test_vyos_facts.py b/test/units/modules/network/vyos/test_vyos_facts.py new file mode 100644 index 0000000000..faf67be8b0 --- /dev/null +++ b/test/units/modules/network/vyos/test_vyos_facts.py @@ -0,0 +1,125 @@ +# (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 + +import os +import json + +from ansible.compat.tests import unittest +from ansible.compat.tests.mock import patch, MagicMock +from ansible.errors import AnsibleModuleExit +from ansible.modules.network.vyos import vyos_facts +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + + +fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') +fixture_data = {} + + +def set_module_args(args): + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +def load_fixture(name): + path = os.path.join(fixture_path, name) + + if path in fixture_data: + return fixture_data[path] + + with open(path) as f: + data = f.read() + + try: + data = json.loads(data) + except: + pass + + fixture_data[path] = data + return data + + +class TestVyosFactsModule(unittest.TestCase): + + def setUp(self): + self.mock_run_commands = patch('ansible.modules.network.vyos.vyos_facts.run_commands') + self.run_commands = self.mock_run_commands.start() + + def tearDown(self): + self.mock_run_commands.stop() + + def execute_module(self, failed=False, changed=False): + + def load_from_file(*args, **kwargs): + module, commands = args + output = list() + + for item in commands: + try: + obj = json.loads(item) + command = obj['command'] + except ValueError: + command = item + filename = str(command).replace(' ', '_') + output.append(load_fixture(filename)) + return output + + self.run_commands.side_effect = load_from_file + + with self.assertRaises(AnsibleModuleExit) as exc: + vyos_facts.main() + + result = exc.exception.result + + if failed: + self.assertTrue(result.get('failed')) + else: + self.assertEqual(result.get('changed'), changed, result) + + return result + + def test_vyos_facts_default(self): + set_module_args(dict(gather_subset='default')) + result = self.execute_module() + facts = result.get('ansible_facts') + self.assertEqual(len(facts), 5) + self.assertEqual(facts['ansible_net_hostname'].strip(), 'vyos01') + self.assertEqual(facts['ansible_net_version'], 'VyOS') + + def test_vyos_facts_not_all(self): + set_module_args(dict(gather_subset='!all')) + result = self.execute_module() + facts = result.get('ansible_facts') + self.assertEqual(len(facts), 5) + self.assertEqual(facts['ansible_net_hostname'].strip(), 'vyos01') + self.assertEqual(facts['ansible_net_version'], 'VyOS') + + def test_vyos_facts_exclude_most(self): + set_module_args(dict(gather_subset=['!neighbors', '!config'])) + result = self.execute_module() + facts = result.get('ansible_facts') + self.assertEqual(len(facts), 5) + self.assertEqual(facts['ansible_net_hostname'].strip(), 'vyos01') + self.assertEqual(facts['ansible_net_version'], 'VyOS') + + def test_vyos_facts_invalid_subset(self): + set_module_args(dict(gather_subset='cereal')) + result = self.execute_module(failed=True)