diff --git a/lib/ansible/modules/network/iosxr/iosxr_facts.py b/lib/ansible/modules/network/iosxr/iosxr_facts.py index 9f93d06125..359836d5da 100644 --- a/lib/ansible/modules/network/iosxr/iosxr_facts.py +++ b/lib/ansible/modules/network/iosxr/iosxr_facts.py @@ -23,7 +23,7 @@ DOCUMENTATION = """ --- module: iosxr_facts version_added: "2.2" -author: "Peter Sprygada (@privateip)" +author: "Ricardo Carrillo Cruz (@rcarrillocruz)" short_description: Collect facts from remote devices running IOS-XR description: - Collects a base set of device facts from a remote device that @@ -121,43 +121,31 @@ ansible_net_neighbors: """ import re -import ansible.module_utils.iosxr -from ansible.module_utils.netcli import CommandRunner, AddCommandError -from ansible.module_utils.network import NetworkModule +from ansible.module_utils.iosxr import run_commands +from ansible.module_utils.local import LocalAnsibleModule from ansible.module_utils.six import iteritems from ansible.module_utils.six.moves import zip -def add_command(runner, command): - try: - runner.add_command(command) - except AddCommandError: - # AddCommandError is raised for any issue adding a command to - # the runner. Silently ignore the exception in this case - pass - class FactsBase(object): - def __init__(self, runner): - self.runner = runner + def __init__(self): self.facts = dict() - self.commands() def commands(self): raise NotImplementedError + class Default(FactsBase): def commands(self): - add_command(self.runner, 'show version brief') + return(['show version brief']) - def populate(self): - data = self.runner.get_command('show version brief') - - self.facts['version'] = self.parse_version(data) - self.facts['image'] = self.parse_image(data) - self.facts['hostname'] = self.parse_hostname(data) + def populate(self, results): + self.facts['version'] = self.parse_version(results['show version brief']) + self.facts['image'] = self.parse_image(results['show version brief']) + self.facts['hostname'] = self.parse_hostname(results['show version brief']) def parse_version(self, data): match = re.search(r'Version (\S+)$', data, re.M) @@ -178,15 +166,14 @@ class Default(FactsBase): class Hardware(FactsBase): def commands(self): - add_command(self.runner, 'dir /all | include Directory') - add_command(self.runner, 'show memory summary') + return(['dir /all | include Directory', 'show memory summary']) - def populate(self): - data = self.runner.get_command('dir /all | include Directory') - self.facts['filesystems'] = self.parse_filesystems(data) + def populate(self, results): + self.facts['filesystems'] = self.parse_filesystems( + results['dir /all | include Directory']) - data = self.runner.get_command('show memory summary') - match = re.search(r'Physical Memory (\d+)M total \((\d+)', data) + match = re.search(r'Physical Memory (\d+)M total \((\d+)', + results['show memory summary']) if match: self.facts['memtotal_mb'] = int(match[0]) self.facts['memfree_mb'] = int(match[1]) @@ -198,35 +185,32 @@ class Hardware(FactsBase): class Config(FactsBase): def commands(self): - add_command(self.runner, 'show running-config') + return(['show running-config']) - def populate(self): - self.facts['config'] = self.runner.get_command('show running-config') + def populate(self, results): + self.facts['config'] = results['show running-config'] class Interfaces(FactsBase): def commands(self): - add_command(self.runner, 'show interfaces') - add_command(self.runner, 'show ipv6 interface') - add_command(self.runner, 'show lldp') - add_command(self.runner, 'show lldp neighbors detail') + return(['show interfaces', 'show ipv6 interface', + 'show lldp', 'show lldp neighbors detail']) - def populate(self): + def populate(self, results): self.facts['all_ipv4_addresses'] = list() self.facts['all_ipv6_addresses'] = list() - data = self.runner.get_command('show interfaces') - interfaces = self.parse_interfaces(data) + interfaces = self.parse_interfaces(results['show interfaces']) self.facts['interfaces'] = self.populate_interfaces(interfaces) - data = self.runner.get_command('show ipv6 interface') + data = results['show ipv6 interface'] if len(data) > 0: data = self.parse_interfaces(data) self.populate_ipv6_interfaces(data) - if 'LLDP is not enabled' not in self.runner.get_command('show lldp'): - neighbors = self.runner.get_command('show lldp neighbors detail') + if 'LLDP is not enabled' not in results['show lldp']: + neighbors = results['show lldp neighbors detail'] self.facts['neighbors'] = self.parse_neighbors(neighbors) def populate_interfaces(self, interfaces): @@ -369,12 +353,13 @@ FACT_SUBSETS = dict( VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + def main(): spec = dict( gather_subset=dict(default=['!config'], type='list') ) - module = NetworkModule(argument_spec=spec, supports_check_mode=True) + module = LocalAnsibleModule(argument_spec=spec, supports_check_mode=True) gather_subset = module.params['gather_subset'] @@ -412,20 +397,19 @@ 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)) - - runner.run() + instances.append(FACT_SUBSETS[key]()) try: for inst in instances: - inst.populate() + commands = inst.commands() + responses = run_commands(module, commands) + results = dict(zip(commands, responses)) + inst.populate(results) facts.update(inst.facts) except Exception: - module.exit_json(out=module.from_json(runner.items)) + module.exit_json(out=module.from_json(results)) ansible_facts = dict() for key, value in iteritems(facts): diff --git a/test/units/modules/network/iosxr/fixtures/dir_7all_1_include_Directory b/test/units/modules/network/iosxr/fixtures/dir_7all_1_include_Directory new file mode 100644 index 0000000000..38a518f9ac --- /dev/null +++ b/test/units/modules/network/iosxr/fixtures/dir_7all_1_include_Directory @@ -0,0 +1 @@ +lollll diff --git a/test/units/modules/network/iosxr/fixtures/show_interfaces b/test/units/modules/network/iosxr/fixtures/show_interfaces new file mode 100644 index 0000000000..d68907c0b0 --- /dev/null +++ b/test/units/modules/network/iosxr/fixtures/show_interfaces @@ -0,0 +1,41 @@ +Loopback0 is up, line protocol is up + Interface state transitions: 1 + Hardware is Loopback interface(s) + Description: Loopback + Internet address is 192.168.0.3/32 + MTU 1500 bytes, BW 0 Kbit + reliability Unknown, txload Unknown, rxload Unknown + Encapsulation Loopback, loopback not set, + Last link flapped 12w1d + Last input Unknown, output Unknown + Last clearing of "show interface" counters Unknown + Input/output data rate is disabled. + +GigabitEthernet0/0/0/0 is up, line protocol is up + Interface state transitions: 1 + Hardware is GigabitEthernet, address is fa16.3e6c.20bd (bia fa16.3e6c.20bd) + Description: to nxos01 + Internet address is 10.0.0.5/30 + MTU 1514 bytes, BW 1000000 Kbit (Max: 1000000 Kbit) + reliability 255/255, txload 0/255, rxload 0/255 + Encapsulation ARPA, + Full-duplex, 1000Mb/s, unknown, link type is force-up + output flow control is off, input flow control is off + Carrier delay (up) is 10 msec + loopback not set, + Last link flapped 12w1d + ARP type ARPA, ARP timeout 04:00:00 + Last input 00:00:44, output 00:12:45 + Last clearing of "show interface" counters never + 5 minute input rate 0 bits/sec, 0 packets/sec + 5 minute output rate 0 bits/sec, 0 packets/sec + 150700 packets input, 36897055 bytes, 0 total input drops + 0 drops for unrecognized upper-level protocol + Received 1 broadcast packets, 150445 multicast packets + 0 runts, 0 giants, 0 throttles, 0 parity + 0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored, 0 abort + 11691 packets output, 2904632 bytes, 0 total output drops + Output 1 broadcast packets, 11436 multicast packets + 0 output errors, 0 underruns, 0 applique, 0 resets + 0 output buffer failures, 0 output buffers swapped out + 1 carrier transitions diff --git a/test/units/modules/network/iosxr/fixtures/show_ipv6_interface b/test/units/modules/network/iosxr/fixtures/show_ipv6_interface new file mode 100644 index 0000000000..971d1f65e7 --- /dev/null +++ b/test/units/modules/network/iosxr/fixtures/show_ipv6_interface @@ -0,0 +1,5 @@ +Loopback0 is Up, ipv6 protocol is Up, Vrfid is default (0x60000000) + IPv6 is disabled, link-local address unassigned + No global unicast address is configured +GigabitEthernet0/0/0/0 is Up, ipv6 protocol is Up, Vrfid is default (0x60000000) + IPv6 is disabled, link-local address unassigned diff --git a/test/units/modules/network/iosxr/fixtures/show_lldp b/test/units/modules/network/iosxr/fixtures/show_lldp new file mode 100644 index 0000000000..60ab287f87 --- /dev/null +++ b/test/units/modules/network/iosxr/fixtures/show_lldp @@ -0,0 +1 @@ +% LLDP is not enabled diff --git a/test/units/modules/network/iosxr/fixtures/show_lldp_neighbors_detail b/test/units/modules/network/iosxr/fixtures/show_lldp_neighbors_detail new file mode 100644 index 0000000000..60ab287f87 --- /dev/null +++ b/test/units/modules/network/iosxr/fixtures/show_lldp_neighbors_detail @@ -0,0 +1 @@ +% LLDP is not enabled diff --git a/test/units/modules/network/iosxr/fixtures/show_memory_summary b/test/units/modules/network/iosxr/fixtures/show_memory_summary new file mode 100644 index 0000000000..b26abeae36 --- /dev/null +++ b/test/units/modules/network/iosxr/fixtures/show_memory_summary @@ -0,0 +1,5 @@ +Physical Memory: 3095M total (1499M available) + Application Memory : 2893M (1499M available) + Image: 73M (bootram: 73M) + Reserved: 128M, IOMem: 0, flashfsys: 0 + Total shared window: 23M diff --git a/test/units/modules/network/iosxr/fixtures/show_running-config b/test/units/modules/network/iosxr/fixtures/show_running-config new file mode 100644 index 0000000000..085baef473 --- /dev/null +++ b/test/units/modules/network/iosxr/fixtures/show_running-config @@ -0,0 +1,43 @@ +hostname iosxr01 +service timestamps log datetime msec +service timestamps debug datetime msec +telnet vrf default ipv4 server max-servers 10 +telnet vrf Mgmt-intf ipv4 server max-servers 10 +domain name eng.ansible.com +domain lookup disable +vrf Mgmt-intf + address-family ipv4 unicast + ! + address-family ipv6 unicast + ! +! +line template vty + timestamp + exec-timeout 720 0 +! +line console + exec-timeout 0 0 +! +line default + exec-timeout 720 0 +! +vty-pool default 0 50 +control-plane + management-plane + inband + interface all + allow all + ! + ! + ! +! +interface Loopback0 + description Loopback + ipv4 address 192.168.0.1 255.255.255.255 +! +interface GigabitEthernet0/0/0/0 + description to nxos01 + cdp + ipv4 address 10.0.0.1 255.255.255.252 +! +end diff --git a/test/units/modules/network/iosxr/fixtures/show_version_brief b/test/units/modules/network/iosxr/fixtures/show_version_brief new file mode 100644 index 0000000000..7f82039ff1 --- /dev/null +++ b/test/units/modules/network/iosxr/fixtures/show_version_brief @@ -0,0 +1,18 @@ +Cisco IOS XR Software, Version 6.0.0[Default] +Copyright (c) 2015 by Cisco Systems, Inc. + +ROM: GRUB, Version 1.99(0), DEV RELEASE + +iosxr01 uptime is 11 weeks, 6 days, 2 hours, 2 minutes +System image file is "bootflash:disk0/xrvr-os-mbi-6.0.0/mbixrvr-rp.vm" + +cisco IOS XRv Series (Pentium Celeron Stepping 3) processor with 3169911K bytes +of memory. +Pentium Celeron Stepping 3 processor at 3836MHz, Revision 2.174 +IOS XRv Chassis + +1 Management Ethernet +6 GigabitEthernet +97070k bytes of non-volatile configuration memory. +866M bytes of hard disk. +2321392k bytes of disk0: (Sector size 512 bytes). diff --git a/test/units/modules/network/iosxr/test_iosxr_facts.py b/test/units/modules/network/iosxr/test_iosxr_facts.py new file mode 100644 index 0000000000..78efba5688 --- /dev/null +++ b/test/units/modules/network/iosxr/test_iosxr_facts.py @@ -0,0 +1,121 @@ +# (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 +from ansible.errors import AnsibleModuleExit +from ansible.modules.network.iosxr import iosxr_facts +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + + +def set_module_args(args): + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') +fixture_data = {} + + +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 TestIosxrFacts(unittest.TestCase): + + def setUp(self): + self.mock_run_commands = patch( + 'ansible.modules.network.iosxr.iosxr_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(' ', '_') + filename = filename.replace('/', '7') + filename = filename.replace('|', '1') + output.append(load_fixture(filename)) + return output + + self.run_commands.side_effect = load_from_file + + with self.assertRaises(AnsibleModuleExit) as exc: + iosxr_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_iosxr_facts_gather_subset_default(self): + set_module_args(dict()) + result = self.execute_module() + ansible_facts = result['ansible_facts'] + self.assertIn('hardware', ansible_facts['ansible_net_gather_subset']) + self.assertIn('default', ansible_facts['ansible_net_gather_subset']) + self.assertIn('interfaces', ansible_facts['ansible_net_gather_subset']) + self.assertEquals('iosxr01', ansible_facts['ansible_net_hostname']) + self.assertEquals([], ansible_facts['ansible_net_filesystems']) + self.assertIn('GigabitEthernet0/0/0/0', + ansible_facts['ansible_net_interfaces'].keys()) + + def test_iosxr_facts_gather_subset_config(self): + set_module_args({'gather_subset': 'config'}) + result = self.execute_module() + ansible_facts = result['ansible_facts'] + self.assertIn('default', ansible_facts['ansible_net_gather_subset']) + self.assertIn('config', ansible_facts['ansible_net_gather_subset']) + self.assertEquals('iosxr01', ansible_facts['ansible_net_hostname']) + self.assertIn('ansible_net_config', ansible_facts)