diff options
author | Nilashish Chakraborty <nilashishchakraborty8@gmail.com> | 2019-02-18 15:05:40 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-02-18 15:05:40 +0100 |
commit | bc403dbcda1edb8ebeee275fa3344ab88bc54ab1 (patch) | |
tree | 5064e5552bc51c9f33a8bef8d1cb63c431ea3576 | |
parent | fix typo in example (#52475) (diff) | |
download | ansible-bc403dbcda1edb8ebeee275fa3344ab88bc54ab1.tar.xz ansible-bc403dbcda1edb8ebeee275fa3344ab88bc54ab1.zip |
Added new module - frr_facts (#51804)
* Add new module frr_facts
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
* Fix return value
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
* Fix review comments
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
* Fix review comments
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
* Handle empty row for mpls ldp neighbors
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
* Fix review comments
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
* Fix CI
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
* Remove timestamp from cliconf pluging
* Updated examples
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
* Fix sanity tests
Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
-rw-r--r-- | lib/ansible/module_utils/network/frr/__init__.py | 0 | ||||
-rw-r--r-- | lib/ansible/module_utils/network/frr/frr.py | 40 | ||||
-rw-r--r-- | lib/ansible/modules/network/frr/__init__.py | 0 | ||||
-rw-r--r-- | lib/ansible/modules/network/frr/frr_facts.py | 400 | ||||
-rw-r--r-- | lib/ansible/plugins/cliconf/frr.py | 10 | ||||
-rw-r--r-- | test/units/modules/network/frr/__init__.py | 0 | ||||
-rw-r--r-- | test/units/modules/network/frr/fixtures/__init__.py | 0 | ||||
-rw-r--r-- | test/units/modules/network/frr/fixtures/frr_facts_show_interface | 36 | ||||
-rw-r--r-- | test/units/modules/network/frr/fixtures/frr_facts_show_memory | 82 | ||||
-rw-r--r-- | test/units/modules/network/frr/fixtures/frr_facts_show_version | 16 | ||||
-rw-r--r-- | test/units/modules/network/frr/frr_module.py | 75 | ||||
-rw-r--r-- | test/units/modules/network/frr/test_frr_facts.py | 114 |
12 files changed, 765 insertions, 8 deletions
diff --git a/lib/ansible/module_utils/network/frr/__init__.py b/lib/ansible/module_utils/network/frr/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/ansible/module_utils/network/frr/__init__.py diff --git a/lib/ansible/module_utils/network/frr/frr.py b/lib/ansible/module_utils/network/frr/frr.py new file mode 100644 index 0000000000..30b3b268d8 --- /dev/null +++ b/lib/ansible/module_utils/network/frr/frr.py @@ -0,0 +1,40 @@ +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +import json + +from ansible.module_utils.connection import Connection, ConnectionError +from ansible.module_utils._text import to_text + + +def get_capabilities(module): + if hasattr(module, '_frr_capabilities'): + return module._frr_capabilities + try: + capabilities = Connection(module._socket_path).get_capabilities() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) + module._frr_capabilities = json.loads(capabilities) + return module._frr_capabilities + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + try: + return connection.run_commands(commands=commands, check_rc=check_rc) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def get_connection(module): + if hasattr(module, '_frr_connection'): + return module._frr_connection + + capabilities = get_capabilities(module) + network_api = capabilities.get('network_api') + if network_api == 'cliconf': + module._frr_connection = Connection(module._socket_path) + else: + module.fail_json(msg='Invalid connection type %s' % network_api) + + return module._frr_connection diff --git a/lib/ansible/modules/network/frr/__init__.py b/lib/ansible/modules/network/frr/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/ansible/modules/network/frr/__init__.py diff --git a/lib/ansible/modules/network/frr/frr_facts.py b/lib/ansible/modules/network/frr/frr_facts.py new file mode 100644 index 0000000000..be91ff5bcb --- /dev/null +++ b/lib/ansible/modules/network/frr/frr_facts.py @@ -0,0 +1,400 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2019, Ansible by Red Hat, inc +# 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': 'network'} + +DOCUMENTATION = """ +--- +module: frr_facts +version_added: "2.8" +author: "Nilashish Chakraborty (@nilashishc)" +short_description: Collect facts from remote devices running Free Range Routing (FRR). +description: + - Collects a base set of device facts from a remote device that + is running FRR. This module prepends all of the + base network fact keys with C(ansible_net_<fact>). 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 FRR 6.0. +options: + gather_subset: + description: + - When supplied, this argument restricts the facts collected + to a given subset. + - Possible values for this argument include + C(all), C(hardware), C(config), and C(interfaces). + - Specify a list of values to include a larger subset. + - Use a value with an initial C(!) to collect all facts except that subset. + required: false + default: '!config' +""" + +EXAMPLES = """ +- name: Collect all facts from the device + frr_facts: + gather_subset: all + +- name: Collect only the config and default facts + frr_facts: + gather_subset: + - config + +- name: Collect the config and hardware facts + frr_facts: + gather_subset: + - config + - hardware + +- name: Do not collect hardware facts + frr_facts: + gather_subset: + - "!hardware" +""" + +RETURN = """ +ansible_net_gather_subset: + description: The list of fact subsets collected from the device + returned: always + type: list + +# default +ansible_net_hostname: + description: The configured hostname of the device + returned: always + type: str +ansible_net_version: + description: The operating system version running on the remote device + returned: always + type: str + +# hardware +ansible_net_mem_stats: + description: The memory statistics fetched from the device + returned: when hardware is configured + type: dict + +# config +ansible_net_config: + description: The current active config from the device + returned: when config is configured + type: str + +# interfaces +ansible_net_all_ipv4_addresses: + description: All IPv4 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_all_ipv6_addresses: + description: All IPv6 addresses configured on the device + returned: when interfaces is configured + type: list +ansible_net_interfaces: + description: A hash of all interfaces running on the system + returned: when interfaces is configured + type: dict +ansible_net_mpls_ldp_neighbors: + description: The list of MPLS LDP neighbors from the remote device + returned: when interfaces is configured and LDP daemon is running on the device + type: dict +""" + +import re + +from ansible.module_utils.network.frr.frr import run_commands, get_capabilities +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems + + +class FactsBase(object): + + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.responses = None + self._capabilities = get_capabilities(self.module) + + def populate(self): + self.responses = run_commands(self.module, commands=self.COMMANDS, check_rc=False) + + def run(self, cmd): + return run_commands(commands=cmd, check_rc=False) + + def parse_facts(self, pattern, data): + value = None + match = re.search(pattern, data, re.M) + if match: + value = match.group(1) + return value + + +class Default(FactsBase): + + COMMANDS = ['show version'] + + def populate(self): + super(Default, self).populate() + self.facts.update(self.platform_facts()) + + def platform_facts(self): + platform_facts = {} + + resp = self._capabilities + device_info = resp['device_info'] + + platform_facts['system'] = device_info['network_os'] + + for item in ('version', 'hostname'): + val = device_info.get('network_os_%s' % item) + if val: + platform_facts[item] = val + + return platform_facts + + +class Hardware(FactsBase): + + COMMANDS = ['show memory'] + + def _parse_daemons(self, data): + match = re.search(r'Memory statistics for (\w+)', data, re.M) + if match: + return match.group(1) + + def gather_memory_facts(self, data): + mem_details = data.split('\n\n') + mem_stats = {} + mem_counters = { + 'total_heap_allocated': r'Total heap allocated:(?:\s*)(.*)', + 'holding_block_headers': r'Holding block headers:(?:\s*)(.*)', + 'used_small_blocks': r'Used small blocks:(?:\s*)(.*)', + 'used_ordinary_blocks': r'Used ordinary blocks:(?:\s*)(.*)', + 'free_small_blocks': r'Free small blocks:(?:\s*)(.*)', + 'free_ordinary_blocks': r'Free ordinary blocks:(?:\s*)(.*)', + 'ordinary_blocks': r'Ordinary blocks:(?:\s*)(.*)', + 'small_blocks': r'Small blocks:(?:\s*)(.*)', + 'holding_blocks': r'Holding blocks:(?:\s*)(.*)' + } + + for item in mem_details: + daemon = self._parse_daemons(item) + mem_stats[daemon] = {} + for fact, pattern in iteritems(mem_counters): + mem_stats[daemon][fact] = self.parse_facts(pattern, item) + + return mem_stats + + def populate(self): + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts['mem_stats'] = self.gather_memory_facts(data) + + +class Config(FactsBase): + + COMMANDS = ['show running-config'] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + data = re.sub(r'^Building configuration...\s+Current configuration:', '', data, flags=re.MULTILINE) + self.facts['config'] = data + + +class Interfaces(FactsBase): + + COMMANDS = ['show interface'] + + def populate(self): + ldp_supported = self._capabilities['supported_protocols']['ldp'] + + if ldp_supported: + self.COMMANDS.append('show mpls ldp discovery') + + super(Interfaces, self).populate() + data = self.responses[0] + + self.facts['all_ipv4_addresses'] = list() + self.facts['all_ipv6_addresses'] = list() + + if data: + interfaces = self.parse_interfaces(data) + self.facts['interfaces'] = self.populate_interfaces(interfaces) + self.populate_ipv4_interfaces(interfaces) + self.populate_ipv6_interfaces(interfaces) + + if ldp_supported: + data = self.responses[1] + if data: + self.facts['mpls_ldp_neighbors'] = self.populate_mpls_ldp_neighbors(data) + + def parse_interfaces(self, data): + parsed = dict() + key = '' + for line in data.split('\n'): + if len(line) == 0: + continue + elif line[0] == ' ': + parsed[key] += '\n%s' % line + else: + match = re.match(r'^Interface (\S+)', line) + if match: + key = match.group(1) + parsed[key] = line + return parsed + + def populate_interfaces(self, interfaces): + facts = dict() + counters = { + 'description': r'Description: (.+)', + 'macaddress': r'HWaddr: (\S+)', + 'type': r'Type: (\S+)', + 'vrf': r'vrf: (\S+)', + 'mtu': r'mtu (\d+)', + 'bandwidth': r'bandwidth (\d+)', + 'lineprotocol': r'line protocol is (\S+)', + 'operstatus': r'^(?:.+) is (.+),' + } + + for key, value in iteritems(interfaces): + intf = dict() + for fact, pattern in iteritems(counters): + intf[fact] = self.parse_facts(pattern, value) + facts[key] = intf + return facts + + def populate_ipv4_interfaces(self, data): + for key, value in data.items(): + self.facts['interfaces'][key]['ipv4'] = list() + primary_address = addresses = [] + primary_address = re.findall(r'inet (\S+) broadcast (?:\S+)(?:\s{2,})', value, re.M) + addresses = re.findall(r'inet (\S+) broadcast (?:\S+)(?:\s+)secondary', value, re.M) + if len(primary_address) == 0: + continue + addresses.append(primary_address[0]) + for address in addresses: + addr, subnet = address.split("/") + ipv4 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv4') + self.facts['interfaces'][key]['ipv4'].append(ipv4) + + def populate_ipv6_interfaces(self, data): + for key, value in data.items(): + self.facts['interfaces'][key]['ipv6'] = list() + addresses = re.findall(r'inet6 (\S+)', value, re.M) + for address in addresses: + addr, subnet = address.split("/") + ipv6 = dict(address=addr.strip(), subnet=subnet.strip()) + self.add_ip_address(addr.strip(), 'ipv6') + self.facts['interfaces'][key]['ipv6'].append(ipv6) + + def add_ip_address(self, address, family): + if family == 'ipv4': + self.facts['all_ipv4_addresses'].append(address) + else: + self.facts['all_ipv6_addresses'].append(address) + + def populate_mpls_ldp_neighbors(self, data): + facts = {} + entries = data.splitlines() + for x in entries: + if x.startswith('AF'): + continue + x = x.split() + if len(x) > 0: + ldp = {} + ldp['neighbor'] = x[1] + ldp['source'] = x[3] + facts[ldp['source']] = [] + facts[ldp['source']].append(ldp) + + return facts + + +FACT_SUBSETS = dict( + default=Default, + hardware=Hardware, + config=Config, + interfaces=Interfaces +) + +VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) + + +def main(): + """main entry point for module execution + """ + argument_spec = dict( + gather_subset=dict(default=['!config'], type='list') + ) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + gather_subset = module.params['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: + module.fail_json(msg='Subset must be one of [%s], got %s' % (', '.join(VALID_SUBSETS), 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) + runable_subsets.add('default') + + facts = dict() + facts['gather_subset'] = list(runable_subsets) + + instances = list() + for key in runable_subsets: + instances.append(FACT_SUBSETS[key](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 + + module.exit_json(ansible_facts=ansible_facts) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/plugins/cliconf/frr.py b/lib/ansible/plugins/cliconf/frr.py index be80b04aec..37ba5ba4e0 100644 --- a/lib/ansible/plugins/cliconf/frr.py +++ b/lib/ansible/plugins/cliconf/frr.py @@ -187,12 +187,11 @@ class Cliconf(CliconfBase): return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, check_all=check_all) - def run_commands(self, commands=None, check_rc=True, return_timestamps=False): + def run_commands(self, commands=None, check_rc=True): if commands is None: raise ValueError("'commands' value is required") responses = list() - timestamps = list() for cmd in to_list(commands): if not isinstance(cmd, Mapping): cmd = {'command': cmd} @@ -203,16 +202,11 @@ class Cliconf(CliconfBase): try: out = self.send_command(**cmd) - timestamp = get_timestamp() except AnsibleConnectionFailure as e: if check_rc: raise out = getattr(e, 'err', to_text(e)) responses.append(out) - timestamps.append(timestamp) - if return_timestamps: - return responses, timestamps - else: - return responses + return responses diff --git a/test/units/modules/network/frr/__init__.py b/test/units/modules/network/frr/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/units/modules/network/frr/__init__.py diff --git a/test/units/modules/network/frr/fixtures/__init__.py b/test/units/modules/network/frr/fixtures/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/units/modules/network/frr/fixtures/__init__.py diff --git a/test/units/modules/network/frr/fixtures/frr_facts_show_interface b/test/units/modules/network/frr/fixtures/frr_facts_show_interface new file mode 100644 index 0000000000..62f08881f5 --- /dev/null +++ b/test/units/modules/network/frr/fixtures/frr_facts_show_interface @@ -0,0 +1,36 @@ +Interface eth0 is up, line protocol is up + Link ups: 0 last: (never) + Link downs: 0 last: (never) + vrf: Default-IP-Routing-Table + index 2 metric 0 mtu 1450 speed 0 + flags: <UP,BROADCAST,RUNNING,MULTICAST> + Type: Ethernet + HWaddr: fa:16:3e:d4:32:b2 + bandwidth 4048 Mbps + inet 192.168.1.8/24 broadcast 192.168.1.255 + inet6 fe80::f816:3eff:fed4:32b2/64 + Interface Type Other +Interface eth1 is up, line protocol is up + Link ups: 0 last: (never) + Link downs: 0 last: (never) + vrf: Default-IP-Routing-Table + Description: Secondary Interface + index 3 metric 0 mtu 1500 speed 0 + flags: <UP,BROADCAST,RUNNING,MULTICAST> + Type: Ethernet + HWaddr: fa:16:3e:95:88:f7 + inet 192.168.1.21/24 broadcast 192.168.1.255 + inet 192.168.1.19/24 broadcast 192.168.1.255 secondary + inet 192.168.1.18/24 broadcast 192.168.1.255 secondary + inet6 fe80::f816:3eff:fe95:88f7/64 + inet6 3ffe:506::1/48 + inet6 3ffe:504::1/48 + Interface Type Other +Interface lo is up, line protocol is up + Link ups: 0 last: (never) + Link downs: 0 last: (never) + vrf: Default-IP-Routing-Table + index 1 metric 0 mtu 65536 speed 0 + flags: <UP,LOOPBACK,RUNNING> + Type: Loopback + Interface Type Other diff --git a/test/units/modules/network/frr/fixtures/frr_facts_show_memory b/test/units/modules/network/frr/fixtures/frr_facts_show_memory new file mode 100644 index 0000000000..d64a9a4c5c --- /dev/null +++ b/test/units/modules/network/frr/fixtures/frr_facts_show_memory @@ -0,0 +1,82 @@ +Memory statistics for zebra: +System allocator statistics: + Total heap allocated: 4200 KiB + Holding block headers: 0 bytes + Used small blocks: 0 bytes + Used ordinary blocks: 2927 KiB + Free small blocks: 2096 bytes + Free ordinary blocks: 1273 KiB + Ordinary blocks: 14 + Small blocks: 60 + Holding blocks: 0 +(see system documentation for 'mallinfo' for meaning) +--- qmem libfrr --- +Buffer : 18 24 448 +Buffer data : 1 4120 4120 +Host config : 4 (variably sized) 96 +Command Tokens : 3162 72 228128 +Command Token Text : 2362 (variably sized) 77344 +Command Token Help : 2362 (variably sized) 56944 +Command Argument : 2 (variably sized) 48 +Command Argument Name : 534 (variably sized) 12912 +FRR POSIX Thread : 28 (variably sized) 2016 +POSIX synchronization primitives: 28 (variably sized) 1344 +Graph : 24 8 576 +Graph Node : 3744 32 150112 +Hash : 2495 (variably sized) 119880 +Hash Bucket : 778 32 31296 +Hash Index : 1248 (variably sized) 363040 +Hook entry : 12 48 672 +Interface : 3 248 744 +Connected : 8 40 320 +Informational Link Parameters : 1 96 104 +Link List : 43 40 1720 +Link Node : 1308 24 31456 +Logging : 1 80 88 +Temporary memory : 23 (variably sized) 42584 +Nexthop : 27 112 3256 +NetNS Context : 2 (variably sized) 128 +NetNS Name : 1 18 24 +Priority queue : 15 32 600 +Priority queue data : 15 256 3960 +Prefix : 12 48 672 +Privilege information : 2 (variably sized) 80 +Stream : 36 (variably sized) 591264 +Stream FIFO : 28 64 2016 +Route table : 14 48 784 +Route node : 43 (variably sized) 4920 +Thread : 51 176 9384 +Thread master : 59 (variably sized) 251016 +Thread Poll Info : 30 8192 246000 +Thread stats : 52 64 3744 +Vector : 7543 16 181432 +Vector index : 7543 (variably sized) 246776 +VRF : 1 184 184 +VRF bit-map : 28 8 672 +VTY : 6 (variably sized) 19440 +Work queue : 2 (variably sized) 224 +Work queue name string : 1 22 24 +Redistribution instance IDs : 1 2 24 +--- qmem Label Manager --- +Label Manager Chunk : 1 16 24 +--- qmem zebra --- +ZEBRA VRF : 1 656 664 +Route Entry : 27 80 2392 +RIB destination : 19 48 1080 +RIB table info : 4 16 96 +Nexthop tracking object : 2 200 400 +Zebra Name Space : 1 312 312 +PTM BFD process registration table.: 3 32 120 +--- qmem Table Manager --- + +Memory statistics for ripd: +System allocator statistics: + Total heap allocated: 936 KiB + Holding block headers: 0 bytes + Used small blocks: 0 bytes + Used ordinary blocks: 901 KiB + Free small blocks: 1504 bytes + Free ordinary blocks: 35 KiB + Ordinary blocks: 4 + Small blocks: 44 + Holding blocks: 0 diff --git a/test/units/modules/network/frr/fixtures/frr_facts_show_version b/test/units/modules/network/frr/fixtures/frr_facts_show_version new file mode 100644 index 0000000000..daec039dfc --- /dev/null +++ b/test/units/modules/network/frr/fixtures/frr_facts_show_version @@ -0,0 +1,16 @@ +FRRouting 6.0 (rtr1). +Copyright 1996-2005 Kunihiro Ishiguro, et al. +configured with: + '--build=x86_64-redhat-linux-gnu' '--host=x86_64-redhat-linux-gnu' '--program-prefix=' + '--disable-dependency-tracking' '--prefix=/usr' '--exec-prefix=/usr' '--bindir=/usr/bin' + '--sysconfdir=/etc' '--datadir=/usr/share' '--includedir=/usr/include' '--libdir=/usr/lib64' + '--libexecdir=/usr/libexec' '--localstatedir=/var' '--sharedstatedir=/var/lib' '--mandir=/usr/share/man' + '--infodir=/usr/share/info' '--sbindir=/usr/lib/frr' '--sysconfdir=/etc/frr' '--localstatedir=/var/run/frr' + '--disable-static' '--disable-werror' '--enable-irdp' '--enable-multipath=256' '--enable-vtysh' + '--enable-ospfclient' '--enable-ospfapi' '--enable-rtadv' '--enable-ldpd' '--enable-pimd' + '--enable-pbrd' '--enable-nhrpd' '--enable-eigrpd' '--enable-babeld' '--enable-user=frr' + '--enable-group=frr' '--enable-vty-group=frrvty' '--enable-fpm' '--enable-watchfrr' '--disable-bgp-vnc' + '--enable-isisd' '--enable-systemd' '--disable-rpki' '--enable-bfdd' 'build_alias=x86_64-redhat-linux-gnu' + 'host_alias=x86_64-redhat-linux-gnu' 'PKG_CONFIG_PATH=:/usr/lib64/pkgconfig:/usr/share/pkgconfig' + 'CFLAGS=-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong + --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic' 'LDFLAGS=-Wl,-z,relro ' diff --git a/test/units/modules/network/frr/frr_module.py b/test/units/modules/network/frr/frr_module.py new file mode 100644 index 0000000000..58eba95dc1 --- /dev/null +++ b/test/units/modules/network/frr/frr_module.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +# (c) 2019, Ansible by Red Hat, inc +# 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 + +import os +import json + +from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase + + +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 Exception: + pass + + fixture_data[path] = data + return data + + +class TestFrrModule(ModuleTestCase): + + def execute_module(self, failed=False, changed=False, commands=None, sort=True, defaults=False): + + self.load_fixtures(commands) + + if failed: + result = self.failed() + self.assertTrue(result['failed'], result) + else: + result = self.changed(changed) + self.assertEqual(result['changed'], changed, result) + + if commands is not None: + if sort: + self.assertEqual(sorted(commands), sorted(result['commands']), result['commands']) + else: + self.assertEqual(commands, result['commands'], result['commands']) + + return result + + def failed(self): + with self.assertRaises(AnsibleFailJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertTrue(result['failed'], result) + return result + + def changed(self, changed=False): + with self.assertRaises(AnsibleExitJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], changed, result) + return result + + def load_fixtures(self, commands=None): + pass diff --git a/test/units/modules/network/frr/test_frr_facts.py b/test/units/modules/network/frr/test_frr_facts.py new file mode 100644 index 0000000000..8e15311c4c --- /dev/null +++ b/test/units/modules/network/frr/test_frr_facts.py @@ -0,0 +1,114 @@ +# (c) 2019, Ansible by Red Hat, inc +# 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 + +from units.compat.mock import patch +from ansible.modules.network.frr import frr_facts +from units.modules.utils import set_module_args +from .frr_module import TestFrrModule, load_fixture + + +class TestFrrFactsModule(TestFrrModule): + + module = frr_facts + + def setUp(self): + super(TestFrrFactsModule, self).setUp() + self.mock_run_commands = patch('ansible.modules.network.frr.frr_facts.run_commands') + self.run_commands = self.mock_run_commands.start() + + self.mock_get_capabilities = patch('ansible.modules.network.frr.frr_facts.get_capabilities') + self.get_capabilities = self.mock_get_capabilities.start() + self.get_capabilities.return_value = { + 'device_info': { + 'network_os': 'frr', + 'network_os_hostname': 'rtr1', + 'network_os_version': '6.0', + }, + 'supported_protocols': { + 'ldp': False + }, + 'network_api': 'cliconf' + } + + def tearDown(self): + super(TestFrrFactsModule, self).tearDown() + self.mock_run_commands.stop() + self.mock_get_capabilities.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('frr_facts_%s' % filename)) + return output + + self.run_commands.side_effect = load_from_file + + def test_frr_facts_default(self): + set_module_args(dict(gather_subset='default')) + result = self.execute_module() + self.assertEqual( + result['ansible_facts']['ansible_net_hostname'], 'rtr1' + ) + self.assertEqual( + result['ansible_facts']['ansible_net_version'], '6.0' + ) + self.assertEqual( + result['ansible_facts']['ansible_net_system'], 'frr' + ) + + def test_frr_facts_interfaces(self): + set_module_args(dict(gather_subset='interfaces')) + result = self.execute_module() + self.assertEqual( + result['ansible_facts']['ansible_net_interfaces']['eth0']['macaddress'], 'fa:16:3e:d4:32:b2' + ) + self.assertEqual( + result['ansible_facts']['ansible_net_interfaces']['eth1']['macaddress'], 'fa:16:3e:95:88:f7' + ) + self.assertEqual( + result['ansible_facts']['ansible_net_interfaces']['eth0']['ipv4'], [{"address": "192.168.1.8", + "subnet": "24"}] + ) + self.assertEqual( + result['ansible_facts']['ansible_net_interfaces']['eth0']['ipv6'], [{"address": "fe80::f816:3eff:fed4:32b2", + "subnet": "64"}] + ) + self.assertEqual( + sorted(result['ansible_facts']['ansible_net_all_ipv4_addresses']), sorted(["192.168.1.19", + "192.168.1.18", + "192.168.1.21", + "192.168.1.8"]) + ) + self.assertEqual( + sorted(result['ansible_facts']['ansible_net_all_ipv6_addresses']), sorted(["fe80::f816:3eff:fe95:88f7", + "3ffe:506::1", + "3ffe:504::1", + "fe80::f816:3eff:fed4:32b2"]) + ) + + def test_frr_facts_hardware(self): + set_module_args(dict(gather_subset='hardware')) + result = self.execute_module() + + self.assertEqual( + result['ansible_facts']['ansible_net_mem_stats']["zebra"]['total_heap_allocated'], '4200 KiB' + ) + + self.assertEqual( + result['ansible_facts']['ansible_net_mem_stats']["ripd"]['total_heap_allocated'], '936 KiB' + ) + + self.assertEqual( + result['ansible_facts']['ansible_net_mem_stats']["ripd"]['used_ordinary_blocks'], '901 KiB' + ) + + self.assertEqual( + result['ansible_facts']['ansible_net_mem_stats']["ripd"]['holding_block_headers'], '0 bytes' + ) |