diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/cisco/ios/tests/unit/modules | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/ios/tests/unit/modules')
82 files changed, 20901 insertions, 0 deletions
diff --git a/ansible_collections/cisco/ios/tests/unit/modules/__init__.py b/ansible_collections/cisco/ios/tests/unit/modules/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/__init__.py diff --git a/ansible_collections/cisco/ios/tests/unit/modules/conftest.py b/ansible_collections/cisco/ios/tests/unit/modules/conftest.py new file mode 100644 index 000000000..41465c300 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/conftest.py @@ -0,0 +1,32 @@ +# Copyright (c) 2017 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 + +import json + +import pytest + +from ansible.module_utils._text import to_bytes +from ansible.module_utils.common._collections_compat import MutableMapping +from ansible.module_utils.six import string_types + + +@pytest.fixture +def patch_ansible_module(request, mocker): + if isinstance(request.param, string_types): + args = request.param + elif isinstance(request.param, MutableMapping): + if "ANSIBLE_MODULE_ARGS" not in request.param: + request.param = {"ANSIBLE_MODULE_ARGS": request.param} + if "_ansible_remote_tmp" not in request.param["ANSIBLE_MODULE_ARGS"]: + request.param["ANSIBLE_MODULE_ARGS"]["_ansible_remote_tmp"] = "/tmp" + if "_ansible_keep_remote_files" not in request.param["ANSIBLE_MODULE_ARGS"]: + request.param["ANSIBLE_MODULE_ARGS"]["_ansible_keep_remote_files"] = False + args = json.dumps(request.param) + else: + raise Exception("Malformed data to the patch_ansible_module pytest fixture") + + mocker.patch("ansible.module_utils.basic._ANSIBLE_ARGS", to_bytes(args)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/__init__.py b/ansible_collections/cisco/ios/tests/unit/modules/network/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/__init__.py diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/__init__.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/__init__.py diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/__init__.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/__init__.py diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/configure_terminal b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/configure_terminal new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/configure_terminal diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_banner_show_banner.txt b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_banner_show_banner.txt new file mode 100644 index 000000000..a134a3175 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_banner_show_banner.txt @@ -0,0 +1,3 @@ +this is a sample +mulitline banner +used for testing diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_banner_show_running_config_ios12.txt b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_banner_show_running_config_ios12.txt new file mode 100644 index 000000000..004387a9b --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_banner_show_running_config_ios12.txt @@ -0,0 +1,15 @@ +banner exec ^C +this is a sample +mulitline banner +used for testing +^C +banner login ^C +this is a sample +mulitline banner +used for testing +^C +! +dummy +end +of +config diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_bgp_config.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_bgp_config.cfg new file mode 100644 index 000000000..9fec934aa --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_bgp_config.cfg @@ -0,0 +1,24 @@ +! +router bgp 64496 + bgp router-id 192.0.2.1 + bgp log-neighbor-changes + neighbor 192.51.100.1 remote-as 64496 + neighbor 192.51.100.1 timers 120 360 360 + neighbor 198.51.100.3 remote-as 64498 + neighbor 203.0.113.5 remote-as 500 + neighbor 203.0.113.5 description EBGP_PEER + ! + address-family ipv4 + network 192.0.2.0 mask 255.255.254.0 route-map RMAP_1 + network 198.51.100.0 mask 255.255.255.128 route-map RMAP_2 + redistribute static metric 100 + redistribute eigrp metric 10 route-map RMAP_3 + neighbor 203.0.113.1 remove-private-as + neighbor 203.0.113.1 maximum-prefix 100 + exit-address-family + ! + address-family ipv4 multicast + network 203.0.113.0 mask 255.255.255.224 route-map RMAP_1 + network 192.0.2.0 mask 255.255.255.192 route-map RMAP_2 + exit-address-family +! diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_config.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_config.cfg new file mode 100644 index 000000000..afad9d08a --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_config.cfg @@ -0,0 +1,12 @@ +! +hostname router +! +interface GigabitEthernet0/0 + ip address 1.2.3.4 255.255.255.0 + description test string +! +interface GigabitEthernet0/1 + ip address 6.7.8.9 255.255.255.0 + description test string + shutdown +! diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_defaults.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_defaults.cfg new file mode 100644 index 000000000..e54645ab1 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_defaults.cfg @@ -0,0 +1,13 @@ +! +hostname router +! +interface GigabitEthernet0/0 + ip address 1.2.3.4 255.255.255.0 + description test string + no shutdown +! +interface GigabitEthernet0/1 + ip address 6.7.8.9 255.255.255.0 + description test string + shutdown +! diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_src.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_src.cfg new file mode 100644 index 000000000..b3d8961a9 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_src.cfg @@ -0,0 +1,11 @@ +! +hostname foo +! +interface GigabitEthernet0/0 + no ip address +! +interface GigabitEthernet0/1 + ip address 6.7.8.9 255.255.255.0 + description test string + shutdown +! diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_dir b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_dir new file mode 100644 index 000000000..3adc44adb --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_dir @@ -0,0 +1,23 @@ +Directory of bootflash:/ + + 11 drwx 16384 Jun 1 2017 13:03:27 +00:00 lost+found +325121 drwx 4096 Jun 1 2017 13:03:54 +00:00 .super.iso.dir + 12 -rw- 31 Jun 22 2018 15:17:06 +00:00 .CsrLxc_LastInstall + 13 -rw- 69 Jun 1 2017 13:05:53 +00:00 virtual-instance.conf +438913 drwx 4096 Jun 1 2017 13:04:57 +00:00 core + 15 -rw- 125736960 Jun 1 2017 13:03:54 +00:00 iosxe-remote-mgmt.16.03.04.ova +105667 -rw- 292164568 Jun 1 2017 13:04:04 +00:00 csr1000v-mono-universalk9.16.03.04.SPA.pkg +105668 -rw- 34370768 Jun 1 2017 13:04:10 +00:00 csr1000v-rpboot.16.03.04.SPA.pkg +105666 -rw- 5317 Jun 1 2017 13:04:10 +00:00 packages.conf +195073 drwx 4096 Jun 1 2017 13:04:51 +00:00 .prst_sync +414529 drwx 4096 Jun 1 2017 13:04:57 +00:00 .rollback_timer + 16 -rw- 0 Jun 1 2017 13:05:00 +00:00 tracelogs.kZn +16257 drwx 24576 Jun 22 2018 16:03:11 +00:00 tracelogs +349505 drwx 4096 Jun 1 2017 13:05:08 +00:00 .installer +292609 drwx 4096 Jun 1 2017 13:05:59 +00:00 virtual-instance + 17 -rw- 30 Jun 22 2018 15:17:59 +00:00 throughput_monitor_params +48769 drwx 4096 Jun 1 2017 13:06:04 +00:00 onep + 19 -rw- 376 Jun 22 2018 15:18:11 +00:00 csrlxc-cfg.log + 20 -rw- 0 Jun 22 2018 15:17:59 +00:00 cvac.log + +7897796608 bytes total (6608056320 bytes free) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_cdp b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_cdp new file mode 100644 index 000000000..c5fff7d27 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_cdp @@ -0,0 +1,4 @@ +Global CDP information: + Sending CDP packets every 60 seconds + Sending a holdtime value of 180 seconds + Sending CDPv2 advertisements is enabled diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_cdp_neighbors_detail b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_cdp_neighbors_detail new file mode 100644 index 000000000..775604482 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_cdp_neighbors_detail @@ -0,0 +1,40 @@ +------------------------- +Device ID: R2 +Entry address(es): + IP address: 10.0.0.3 +Platform: cisco CSR1000V, Capabilities: Router IGMP +Interface: GigabitEthernet1, Port ID (outgoing port): GigabitEthernet0/1 +Holdtime : 149 sec + +Version : +Cisco IOS Software [Everest], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.6.4, RELEASE SOFTWARE (fc3) +Technical Support: http://www.cisco.com/techsupport +Copyright (c) 1986-2018 by Cisco Systems, Inc. +Compiled Sun 08-Jul-18 04:30 by mcpre + +advertisement version: 2 +Duplex: full +Management address(es): + IP address: 10.0.0.3 + +------------------------- +Device ID: R3 +Entry address(es): + IP address: 10.0.0.4 +Platform: cisco CSR1000V, Capabilities: Router IGMP +Interface: GigabitEthernet1, Port ID (outgoing port): GigabitEthernet3 +Holdtime : 149 sec + +Version : +Cisco IOS Software [Everest], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.6.4, RELEASE SOFTWARE (fc3) +Technical Support: http://www.cisco.com/techsupport +Copyright (c) 1986-2018 by Cisco Systems, Inc. +Compiled Sun 08-Jul-18 04:30 by mcpre + +advertisement version: 2 +Duplex: full +Management address(es): + IP address: 10.0.0.4 + + +Total cdp entries displayed : 2 diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_interfaces b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_interfaces new file mode 100644 index 000000000..9888c8e22 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_interfaces @@ -0,0 +1,91 @@ +GigabitEthernet0/0 is up, line protocol is up + Hardware is iGbE, address is 5e00.0003.0000 (bia 5e00.0003.0000) + Description: OOB Management + Internet address is 10.8.38.66/24 + MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec, + reliability 253/255, txload 1/255, rxload 1/255 + Encapsulation ARPA, loopback not set + Keepalive set (10 sec) + Full Duplex, Auto Speed, link type is auto, media type is RJ45 + output flow-control is unsupported, input flow-control is unsupported + ARP type: ARPA, ARP Timeout 04:00:00 + Last input 00:00:00, output 00:00:00, output hang never + Last clearing of "show interface" counters never + Input queue: 0/75/0/0 (size/max/drops/flushes); Total output drops: 0 + Queueing strategy: fifo + Output queue: 0/40 (size/max) + 5 minute input rate 3000 bits/sec, 2 packets/sec + 5 minute output rate 2000 bits/sec, 2 packets/sec + 2226666 packets input, 398288440 bytes, 0 no buffer + Received 156442 broadcasts (0 IP multicasts) + 25440 runts, 0 giants, 0 throttles + 25440 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored + 0 watchdog, 0 multicast, 0 pause input + 1304895 packets output, 119337031 bytes, 0 underruns + 0 output errors, 0 collisions, 3 interface resets + 1083697 unknown protocol drops + 0 babbles, 0 late collision, 0 deferred + 1 lost carrier, 0 no carrier, 0 pause output + 0 output buffer failures, 0 output buffers swapped out +GigabitEthernet1 is up, line protocol is up + Hardware is CSR vNIC, address is 5e00.0006.0000 (bia 5e00.0006.0000) + Description: OOB Management + Internet address is 10.8.38.67/24 + MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec, + reliability 255/255, txload 1/255, rxload 1/255 + Encapsulation ARPA, loopback not set + Keepalive set (10 sec) + Full Duplex, 1000Mbps, link type is auto, media type is RJ45 + output flow-control is unsupported, input flow-control is unsupported + ARP type: ARPA, ARP Timeout 04:00:00 + Last input 00:00:01, output 00:00:07, output hang never + Last clearing of "show interface" counters never + Input queue: 0/375/0/0 (size/max/drops/flushes); Total output drops: 0 + Queueing strategy: fifo + Output queue: 0/40 (size/max) + 5 minute input rate 3000 bits/sec, 3 packets/sec + 5 minute output rate 3000 bits/sec, 3 packets/sec + 8463791 packets input, 1445150230 bytes, 0 no buffer + Received 0 broadcasts (0 IP multicasts) + 0 runts, 0 giants, 0 throttles + 0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored + 0 watchdog, 0 multicast, 0 pause input + 3521571 packets output, 348781823 bytes, 0 underruns + 0 output errors, 0 collisions, 1 interface resets + 4150764 unknown protocol drops + 0 babbles, 0 late collision, 0 deferred + 0 lost carrier, 0 no carrier, 0 pause output + 0 output buffer failures, 0 output buffers swapped out +Tunnel1110 is up, line protocol is up + Hardware is Tunnel + Internet address is 10.10.10.2/30 +TenGigabitEthernet2/5/5 is down, line protocol is down (notconnect) + Hardware is C6k 10000Mb 802.3, address is xxxx.xxxx.xxxx (bia xxxx.xxxx.xxxx) + Description: free + MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec, + reliability 255/255, txload 1/255, rxload 1/255 + Encapsulation ARPA, loopback not set + Keepalive set (10 sec) + Full-duplex, 1000Mb/s, media type is 1000BaseLH + input flow-control is off, output flow-control is desired + Clock mode is auto + ARP type: ARPA, ARP Timeout 04:00:00 + Last input never, output never, output hang never + Last clearing of "show interface" counters never + Input queue: 0/2000/0/0 (size/max/drops/flushes); Total output drops: 0 + Queueing strategy: fifo + Output queue: 0/40 (size/max) + 5 minute input rate 0 bits/sec, 0 packets/sec + 5 minute output rate 0 bits/sec, 0 packets/sec + 0 packets input, 0 bytes, 0 no buffer + Received 0 broadcasts (0 multicasts) + 0 runts, 0 giants, 0 throttles + 0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored + 0 watchdog, 0 multicast, 0 pause input + 0 input packets with dribble condition detected + 0 packets output, 0 bytes, 0 underruns + 0 output errors, 0 collisions, 3 interface resets + 0 unknown protocol drops + 0 babbles, 0 late collision, 0 deferred + 0 lost carrier, 0 no carrier, 0 pause output + 0 output buffer failures, 0 output buffers swapped out diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_inventory b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_inventory new file mode 100644 index 000000000..18afcf276 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_inventory @@ -0,0 +1,101 @@ +NAME: "Switch1 System", DESCR: "Cisco Systems, Inc. WS-C4500X-16 2 slot switch " +PID: , VID: , SN: JAE19270BQF + +NAME: "Switch1 Supervisor 1 (virtual slot 1)", DESCR: "4500X-16 10GE (SFP+)" +PID: WS-C4500X-16 , VID: V03 , SN: JAE19270BQF + +NAME: "TenGigabitEthernet1/1/1", DESCR: "10GBase-CU-1M" +PID: SFP-H10GB-CU1M , VID: V03 , SN: TED1822A14H + +NAME: "TenGigabitEthernet1/1/2", DESCR: "10GBase-CU-1M" +PID: SFP-H10GB-CU1M , VID: V03 , SN: TED1822A149 + +NAME: "TenGigabitEthernet1/1/3", DESCR: "10GBase-CU-1M" +PID: SFP-H10GB-CU1M , VID: V03 , SN: TED1822A0R3 + +NAME: "TenGigabitEthernet1/1/4", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V03 , SN: AGD1822V24L + +NAME: "TenGigabitEthernet1/1/5", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V02 , SN: AGD1519V0G3 + +NAME: "TenGigabitEthernet1/1/6", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V02 , SN: AGD1519V0FT + +NAME: "TenGigabitEthernet1/1/7", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V02 , SN: AGD1519V0MY + +NAME: "TenGigabitEthernet1/1/8", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V01 , SN: AGD1350Z010 + +NAME: "TenGigabitEthernet1/1/9", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V01 , SN: AGD1350Z091 + +NAME: "TenGigabitEthernet1/1/10", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V03 , SN: AGD1932V0SN + +NAME: "TenGigabitEthernet1/1/11", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V03 , SN: AGD1932V17U + +NAME: "TenGigabitEthernet1/1/13", DESCR: "SFP-10Gbase-LR" +PID: SFP-10G-LR , VID: V02 , SN: F176CO12831 + +NAME: "TenGigabitEthernet1/1/16", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V03 , SN: AGD1822V23U + +NAME: "Switch1 Power Supply 1", DESCR: "Power Supply ( AC 750W )" +PID: C4KX-PWR-750AC-R , VID: V01 , SN: ART1923X19J + +NAME: "Switch1 Power Supply 2", DESCR: "Power Supply ( AC 750W )" +PID: C4KX-PWR-750AC-R , VID: V01 , SN: ART1944X1N7 + +NAME: "Switch2 System", DESCR: "Cisco Systems, Inc. WS-C4500X-16 2 slot switch " +PID: , VID: , SN: JAE18280APG + +NAME: "Switch2 Supervisor 1 (virtual slot 11)", DESCR: "4500X-16 10GE (SFP+)" +PID: WS-C4500X-16 , VID: V03 , SN: JAE18280APG + +NAME: "TenGigabitEthernet2/1/1", DESCR: "10GBase-CU-1M" +PID: SFP-H10GB-CU1M , VID: V03 , SN: TED1822A14H + +NAME: "TenGigabitEthernet2/1/2", DESCR: "10GBase-CU-1M" +PID: SFP-H10GB-CU1M , VID: V03 , SN: TED1822A149 + +NAME: "TenGigabitEthernet2/1/3", DESCR: "10GBase-CU-1M" +PID: SFP-H10GB-CU1M , VID: V03 , SN: TED1822A15D + +NAME: "TenGigabitEthernet2/1/4", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V03 , SN: AGD1822V23L + +NAME: "TenGigabitEthernet2/1/5", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V03 , SN: AGD1822V1NF + +NAME: "TenGigabitEthernet2/1/6", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V03 , SN: AGD1822V23R + +NAME: "TenGigabitEthernet2/1/7", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V02 , SN: AGD1519V0N1 + +NAME: "TenGigabitEthernet2/1/8", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V02 , SN: AGD1519V0G2 + +NAME: "TenGigabitEthernet2/1/9", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V01 , SN: AGD1350Z08Y + +NAME: "TenGigabitEthernet2/1/10", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V03 , SN: AGD1933V20T + +NAME: "TenGigabitEthernet2/1/11", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V02 , SN: AGD1519V0J9 + +NAME: "TenGigabitEthernet2/1/12", DESCR: "SFP-10Gbase-LRM" +PID: SFP-10G-LRM , VID: V02 , SN: AGD1519V0CC + +NAME: "TenGigabitEthernet2/1/13", DESCR: "SFP-10Gbase-LR" +PID: SFP-10G-LR , VID: V02 , SN: F176CO12838 + +NAME: "Switch2 Power Supply 1", DESCR: "Power Supply ( AC 750W )" +PID: C4KX-PWR-750AC-R , VID: V01 , SN: ART1828X1NH + +NAME: "Switch2 Power Supply 2", DESCR: "Power Supply ( AC 750W )" +PID: C4KX-PWR-750AC-R , VID: V01 , SN: ART1828X1NL diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_ip_interface b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_ip_interface new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_ip_interface diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_ipv6_interface b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_ipv6_interface new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_ipv6_interface diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_lldp b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_lldp new file mode 100644 index 000000000..09847c318 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_lldp @@ -0,0 +1,6 @@ + +Global LLDP Information: + Status: ACTIVE + LLDP advertisements are sent every 30 seconds + LLDP hold time advertised is 120 seconds + LLDP interface reinitialisation delay is 2 seconds diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_lldp_neighbors_detail b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_lldp_neighbors_detail new file mode 100644 index 000000000..affcaed21 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_lldp_neighbors_detail @@ -0,0 +1,48 @@ +------------------------------------------------ +Local Intf: Gi1 +Chassis id: 001e.14d4.5300 +Port id: Gi3 +Port Description: GigabitEthernet3 +System Name: R3 + +System Description: +Cisco IOS Software [Everest], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.6.4, RELEASE SOFTWARE (fc3) +Technical Support: http://www.cisco.com/techsupport +Copyright (c) 1986-2018 by Cisco Systems, Inc. +Compiled Sun 08-Jul-18 04:30 by + +Time remaining: 116 seconds +System Capabilities: B,R +Enabled Capabilities: R +Management Addresses: + IP: 10.0.0.4 +Auto Negotiation - not supported +Physical media capabilities - not advertised +Media Attachment Unit type - not advertised +Vlan ID: - not advertised + +------------------------------------------------ +Local Intf: Gi3 +Chassis id: 001e.e6c9.6d00 +Port id: Gi1 +Port Description: GigabitEthernet1 +System Name: Rtest + +System Description: +Cisco IOS Software [Everest], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.6.4, RELEASE SOFTWARE (fc3) +Technical Support: http://www.cisco.com/techsupport +Copyright (c) 1986-2018 by Cisco Systems, Inc. +Compiled Sun 08-Jul-18 04:30 by + +Time remaining: 116 seconds +System Capabilities: B,R +Enabled Capabilities: R +Management Addresses: + IP: 10.3.0.3 +Auto Negotiation - not supported +Physical media capabilities - not advertised +Media Attachment Unit type - not advertised +Vlan ID: - not advertised + + +Total entries displayed: 2 diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_memory_statistics b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_memory_statistics new file mode 100644 index 000000000..e0f63108a --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_memory_statistics @@ -0,0 +1,4 @@ + Head Total(b) Used(b) Free(b) Lowest(b) Largest(b) +Processor 7F682B66B048 732743660 253648024 479095636 476877492 477826896 +reserve P 7F682B66B0A0 102404 92 102312 102312 102312 + lsmpi_io 7F6829D501A8 3149400 3148576 824 824 412 diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_switch_virtual b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_switch_virtual new file mode 100644 index 000000000..42adb0807 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_switch_virtual @@ -0,0 +1,6 @@ +Switch mode : Virtual Switch +Virtual switch domain number : 10 +Local switch number : 1 +Local switch operational role: Virtual Switch Active +Peer switch number : 2 +Peer switch operational role : Virtual Switch Standby diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_version b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_version new file mode 100644 index 000000000..58322c80b --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_version @@ -0,0 +1,68 @@ +Cisco Internetwork Operating System Software +IOS (tm) C3750 Software (C3750-I5-M), Version 12.1(14)EA1, RELEASE SOFTWARE (fc1) +Copyright (c) 1986-2003 by cisco Systems, Inc. +Compiled Tue 22-Jul-03 13:17 by antonino +Image text-base: 0x00003000, data-base: 0x008F0CF8 + +ROM: Bootstrap program is C3750 boot loader +BOOTLDR: C3750 Boot Loader (C3750-HBOOT-M) Version 12.1(11r)AX, RELEASE SOFTWARE (fc1) + +3750RJ uptime is 1 hour, 29 minutes +System returned to ROM by power-on +System image file is "flash:c3750-i5-mz.121.14-EA1/c3750-i5-mz.121.14-EA1.bin" + +cisco WS-C3750-24TS (PowerPC405) processor (revision A0) with 120822K/10240K bytes of memory. +Processor board ID CAT0726R0ZU +Last reset from power-on +Bridging software. +2 Virtual Ethernet/IEEE 802.3 interface(s) +48 FastEthernet/IEEE 802.3 interface(s) +16 Gigabit Ethernet/IEEE 802.3 interface(s) +The password-recovery mechanism is enabled. + +512K bytes of flash-simulated non-volatile configuration memory. +Base ethernet MAC Address : 00:0D:29:B4:18:00 +Motherboard assembly number : 73-7055-06 +Power supply part number : 341-0034-01 +Motherboard serial number : CAT0726043V +Power supply serial number : PHI0708009K +Model revision number : A0 +Motherboard revision number : A0 +Model number : WS-C3750-24TS-E +System serial number : CAT0726R0ZU + +Switch Ports Model SW Version SW Image +------ ----- ----- ---------- ---------- +* 1 26 WS-C3750-24TS 12.1(14)EA1 C3750-I5-M +2 26 WS-C3750-24TS 12.1(14)EA1 C3750-I5-M +3 12 WS-C3750G-12S 12.1(14)EA1 C3750-I5-M + +Switch 02 +--------- +Switch Uptime : 1 hour, 29 minutes +Base ethernet MAC Address : 00:0D:29:B4:3F:00 +Motherboard assembly number : 73-7055-06 +Power supply part number : 341-0034-01 +Motherboard serial number : CAT07260438 +Power supply serial number : PHI0708008X +Model revision number : A0 +Motherboard revision number : A0 +Model number : WS-C3750-24TS-E +System serial number : CAT0726R10A + +Switch 03 +--------- +Switch Uptime : 1 hour, 29 minutes +Base ethernet MAC Address : 00:0D:BD:6A:3E:00 +Motherboard assembly number : 73-8307-06 +Power supply part number : 341-0048-01 +Motherboard serial number : CAT073205S2 +Power supply serial number : DTH0731055Z +Model revision number : A0 +Motherboard revision number : A0 +Model number : WS-C3750G-12S-E +System serial number : CAT0732R0M4 +Top assembly part number : 800-23419-01 +Top assembly revision number : A0 + +Configuration register is 0xF diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_virtual_switch b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_virtual_switch new file mode 100644 index 000000000..b4d8c8a43 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_virtual_switch @@ -0,0 +1,20 @@ + +Executing the command on VSS member switch role = VSS Active, id = 1 + + +Switch mode : Virtual Switch +Virtual switch domain number : 102 +Local switch number : 1 +Local switch operational role: Virtual Switch Active +Peer switch number : 2 +Peer switch operational role : Virtual Switch Standby + +Executing the command on VSS member switch role = VSS Standby, id = 2 + + +Switch mode : Virtual Switch +Virtual switch domain number : 102 +Local switch number : 2 +Local switch operational role: Virtual Switch Standby +Peer switch number : 1 +Peer switch operational role : Virtual Switch Active diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_l2_interfaces.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_l2_interfaces.cfg new file mode 100644 index 000000000..2fb623c43 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_l2_interfaces.cfg @@ -0,0 +1,19 @@ +interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 +interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk +interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 +interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_l3_interfaces.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_l3_interfaces.cfg new file mode 100644 index 000000000..1ecb46b66 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_l3_interfaces.cfg @@ -0,0 +1,16 @@ +interface GigabitEthernet0/1 + description Configured by Ansible + duplex auto + speed auto +interface GigabitEthernet0/2 + description This is test + duplex auto + speed 1000 +interface GigabitEthernet0/3 + description Configured by Ansible Network + ipv6 address FD5D:12C9:2201:1::1/64 +interface GigabitEthernet0/3.100 + encapsulation dot1Q 20 + ip address 192.168.0.3 255.255.255.0 +interface Serial1/0 + description Configured by PAUL diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_config.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_config.cfg new file mode 100644 index 000000000..8a51afa7e --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_config.cfg @@ -0,0 +1,11 @@ +! +logging buffered 5000 +logging console informational +logging facility local0 +no logging monitor +logging host 1.2.3.4 transport tcp +logging host 1.2.3.4 +logging host 2.3.4.5 +logging host 1.2.3.4 transport tcp port 1000 +logging host 1.2.3.4 transport udp port 1000 +! diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_config_ios12.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_config_ios12.cfg new file mode 100644 index 000000000..58be36dff --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_config_ios12.cfg @@ -0,0 +1,6 @@ +! +logging buffered 5000 +logging console informational +logging facility local0 +logging 2.3.4.5 +! diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_global.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_global.cfg new file mode 100644 index 000000000..2f1ec5c77 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_global.cfg @@ -0,0 +1,35 @@ +logging on +logging count +logging buginf +logging userinfo +logging esm config +logging server-arp +logging trap errors +logging delimiter tcp +logging reload alerts +logging host 10.0.1.1 +logging exception 4099 +logging history alerts +logging facility local5 +logging snmp-trap errors +logging monitor warnings +logging origin-id hostname +logging host 10.0.1.11 xml +logging cns-events warnings +logging queue-limit esm 150 +logging dmvpn rate-limit 10 +logging message-counter log +logging console xml critical +logging message-counter debug +logging persistent batch 4444 +logging host 10.0.1.25 filtered +logging source-interface GBit1/0 +logging source-interface CTunnel2 +logging policy-firewall rate-limit 10 +logging buffered xml 5099 notifications +logging rate-limit all 2 except warnings +logging host 10.0.1.10 filtered stream 10 +logging host 10.0.1.13 transport tcp port 514 +logging discriminator msglog01 severity includes 5 +logging filter tftp://10.0.2.18/ESM/elate.tcl args TESTInst2 +logging filter tftp://10.0.2.14/ESM/escalate.tcl args TESTInst diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ntp_config.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ntp_config.cfg new file mode 100644 index 000000000..159430a51 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ntp_config.cfg @@ -0,0 +1,7 @@ +ntp logging +ntp authentication-key 10 md5 15435A030726242723273C21181319000A 7 +ntp authenticate +ntp trusted-key 10 +ntp source Loopback0 +ntp access-group peer NTP_ACL +ntp server vrf my_mgmt_vrf 10.75.32.5 diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ospfv2.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ospfv2.cfg new file mode 100644 index 000000000..5a5ca4f9f --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ospfv2.cfg @@ -0,0 +1,10 @@ +router ospf 200 vrf blue + auto-cost reference-bandwidth 4 + distribute-list 10 out + distribute-list 123 in + domain-id 192.0.3.1 + max-metric router-lsa on-startup 100 + area 10 capability default-exclusion + passive-interface default + no passive-interface GigabitEthernet0/1 + no passive-interface GigabitEthernet0/2 diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ospfv3.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ospfv3.cfg new file mode 100644 index 000000000..a3c07f069 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ospfv3.cfg @@ -0,0 +1,8 @@ +router ospfv3 1 + max-metric router-lsa on-startup 110 + area 10 nssa default-information-originate metric 10 + ! + address-family ipv4 unicast vrf blue + adjacency stagger 50 50 + area 25 nssa default-information-originate metric 25 nssa-only + exit-address-family diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_10.255.255.250_repeat_2 b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_10.255.255.250_repeat_2 new file mode 100644 index 000000000..9b25d6454 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_10.255.255.250_repeat_2 @@ -0,0 +1,4 @@ +Type escape sequence to abort. +Sending 2, 100-byte ICMP Echos to 10.255.255.250, timeout is 2 seconds: +.. +Success rate is 0 percent (0/2) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_repeat_2 b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_repeat_2 new file mode 100644 index 000000000..4dddd76b0 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_repeat_2 @@ -0,0 +1,4 @@ +Type escape sequence to abort. +Sending 2, 100-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds: +!! +Success rate is 100 percent (2/2), round-trip min/avg/max = 25/25/25 ms diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_size_1400 b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_size_1400 new file mode 100644 index 000000000..2e8d64cd8 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_size_1400 @@ -0,0 +1,4 @@ +Type escape sequence to abort. +Sending 5, 1400-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds: +!!!!! +Success rate is 100 percent (5/5), round-trip min/avg/max = 25/25/25 ms diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_size_1400_df-bit b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_size_1400_df-bit new file mode 100644 index 000000000..fe84e2606 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_size_1400_df-bit @@ -0,0 +1,5 @@ +Type escape sequence to abort. +Sending 5, 1400-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds: +Packet sent with the DF bit set +!!!!! +Success rate is 100 percent (5/5), round-trip min/avg/max = 25/25/25 ms diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_prefix_lists.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_prefix_lists.cfg new file mode 100644 index 000000000..713d24499 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_prefix_lists.cfg @@ -0,0 +1,12 @@ +ip prefix-list 10 description this is test description +ip prefix-list 10 seq 5 deny 1.0.0.0/8 le 15 +ip prefix-list 10 seq 10 deny 35.0.0.0/8 ge 10 +ip prefix-list 10 seq 15 deny 12.0.0.0/8 ge 15 +ip prefix-list 10 seq 20 deny 14.0.0.0/8 ge 20 le 21 +ip prefix-list test description this is test +ip prefix-list test seq 50 deny 12.0.0.0/8 ge 15 +ip prefix-list test_prefix description this is for prefix-list +ip prefix-list test_prefix seq 5 deny 35.0.0.0/8 ge 10 le 15 +ip prefix-list test_prefix seq 10 deny 35.0.0.0/8 ge 20 +ipv6 prefix-list test_ipv6 description this is ipv6 prefix-list +ipv6 prefix-list test_ipv6 seq 10 deny 2001:DB8:0:4::/64 ge 80 diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_route_maps.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_route_maps.cfg new file mode 100644 index 000000000..8b0620dff --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_route_maps.cfg @@ -0,0 +1,45 @@ +route-map test_1 deny 10 + description this is test + match ip address 10 100 + match clns address test_osi + match track 100 + match interface GigabitEthernet0/1 + match security-group destination tag 100 + match metric external 100 500 + match tag list test_tag + match source-protocol ospfv3 10000 static + match local-preference 100 + match route-type external type-1 + match rpki invalid + match additional-paths advertise-set all + match as-path 100 120 + match community 99 98 test_1 test_2 exact-match + match extcommunity 110 130 + match policy-list ip_policy + match mpls-label + match mdt-group 25 30 + match length 1000 10000 + match ipv6 route-source test_ipv6 + continue 100 +route-map test_1 deny 20 + set metric +100 + set metric-type type-1 + set tag 50529027 + set automatic-tag + set level level-1-2 + set clns next-hop 11.1111 + set local-preference 100 + set aigp-metric 1000 + set weight 100 + set traffic-index 10 + set origin igp + set as-path prepend last-as 10 + set dampening 10 100 100 10 + set comm-list test_comm delete + set community internet additive + set extcomm-list test_excomm delete + set extcommunity vpn-distinguisher 192.0.2.1:12 + set mpls-label + set global + set interface GigabitEthernet0/2 GigabitEthernet0/1 + set lisp locator-set test_lisp diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_system_config.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_system_config.cfg new file mode 100644 index 000000000..3330b4aa3 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_system_config.cfg @@ -0,0 +1,14 @@ +! +hostname ios01 +! +ip domain list vrf management example.net +ip domain list example.net +ip domain list example.com +ip domain lookup source-interface GigabitEthernet0/0 +ip domain name vrf management eng.example.net +ip domain name eng.example.net +ip name-server vrf management 8.8.8.8 +ip name-server 8.8.8.8 +! +vrf definition test +! diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_user_config.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_user_config.cfg new file mode 100644 index 000000000..dd5b2095f --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_user_config.cfg @@ -0,0 +1,2 @@ +username admin view network-admin secret 5 $1$mdQIUxjg$3t3lzBpfKfITKvFm1uEIY. +username ansible view network-admin secret 5 $1$3yWSXiIi$VdzV59ChiurrNdGxlDeAW/ diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vlan_config.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vlan_config.cfg new file mode 100644 index 000000000..9497ae868 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vlan_config.cfg @@ -0,0 +1,9 @@ +VLAN Name Status Ports +---- -------------------------------- --------- ------------------------------- +1 default active Gi1/0/4, Gi1/0/5 + Gi1/0/52 + Gi1/0/54 +2 vlan2 active Gi1/0/6, Gi1/0/7 +9 vlan9 active Gi1/0/6 +1002 fddi-default act/unsup +1003 fddo-default act/unsup diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vlans_config.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vlans_config.cfg new file mode 100644 index 000000000..041b6856e --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vlans_config.cfg @@ -0,0 +1,38 @@ +VLAN Name Status Ports +---- -------------------------------- --------- ------------------------------- +1 default active Gi0/1, Gi0/2 +123 RemoteIsInMyName act/unsup Fa0/1, Fa0/4, Fa0/5, Fa0/6, Fa0/7, Fa0/8, Fa0/9, Fa0/10, Fa0/11, Fa0/12 + Fa0/13, Fa0/14, Fa0/15, Fa0/16, Fa0/17, Fa0/18, Fa0/19, Fa0/20, Fa0/21 + Fa0/22, Fa0/23, Fa0/24, Fa0/25, Fa0/26, Fa0/27, Fa0/28, Fa0/29, Fa0/30 + Fa0/31, Fa0/32, Fa0/33, Fa0/34, Fa0/35, Fa0/36, Fa0/37, Fa0/38, Fa0/39 + Fa0/40, Fa0/41, Fa0/42, Fa0/43, Fa0/44, Fa0/45, Fa0/46, Fa0/47, Fa0/48 +150 VLAN0150 active +888 a_very_long_vlan_name_a_very_long_vlan_name + active +1002 fddi-default act/unsup +1003 trcrf-default act/unsup +1004 fddinet-default act/unsup +1005 trbrf-default act/unsup + +VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2 +---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------ +1 enet 100001 1500 - - - - - 0 0 +123 enet 100123 610 - - - - - 0 0 +150 enet 100150 1500 - - - - - 0 0 +888 enet 100888 1500 - - - - - 0 0 +1002 fddi 101002 1500 - - - - - 0 0 +1003 trcrf 101003 4472 1005 3276 - - srb 0 0 +1004 fdnet 101004 1500 - - - ieee - 0 0 +1005 trbrf 101005 4472 - - 15 ibm - 0 0 + + +VLAN AREHops STEHops Backup CRF +---- ------- ------- ---------- +1003 7 7 off + +Remote SPAN VLANs +------------------------------------------------------------------------------ +150 + +Primary Secondary Type Ports +------- --------- ----------------- ------------------------------------------ diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vrf_config.cfg b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vrf_config.cfg new file mode 100644 index 000000000..0a2d35f8f --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vrf_config.cfg @@ -0,0 +1,81 @@ +! +vrf definition test_1 + description test vrf 1 + rd 1:100 +! +vrf definition test_2 + description test vrf 2 +! +vrf definition test_3 +! +vrf definition test_17 + rd 2:100 + ! + address-family ipv4 + exit-address-family + ! + address-family ipv6 + route-target export 168.0.0.15:100 + route-target export 4:100 + route-target export 2:100 + route-target export 168.0.0.13:100 + route-target import 168.0.0.14:100 + route-target import 2:100 + route-target import 168.0.0.13:100 + exit-address-family +! +vrf definition test_18 + rd 168.0.0.9:100 + ! + address-family ipv4 + route-target export 168.0.0.10:100 + route-target export 168.0.0.9:100 + route-target export 3:100 + route-target import 168.0.0.9:100 + route-target import 3:100 + route-target import 168.0.0.10:600 + exit-address-family + ! + address-family ipv6 + exit-address-family +! +vrf definition test_19 + rd 10:700 + route-target export 2:102 + route-target export 2:103 + route-target export 2:100 + route-target export 2:101 + route-target import 2:104 + route-target import 2:105 + route-target import 2:100 + route-target import 2:101 + ! + address-family ipv4 + route-target export 2:102 + route-target export 2:103 + route-target export 2:100 + route-target export 2:101 + route-target import 2:104 + route-target import 2:105 + route-target import 2:100 + route-target import 2:101 + exit-address-family + ! + address-family ipv6 + route-target export 2:102 + route-target export 2:103 + route-target export 2:100 + route-target export 2:101 + route-target import 2:104 + route-target import 2:105 + route-target import 2:100 + route-target import 2:101 + exit-address-family +! +interface Ethernet1 + ip address 1.2.3.4/5 +! +interface Ethernet2 + ip address 1.2.3.4/5 + vrf forwarding test_1 +! diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/show_version b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/show_version new file mode 100644 index 000000000..d5420b8c7 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/show_version @@ -0,0 +1,43 @@ +Cisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.6(1)T, RELEASE SOFTWARE (fc1) +Technical Support: http://www.cisco.com/techsupport +Copyright (c) 1986-2015 by Cisco Systems, Inc. +Compiled Fri 20-Nov-15 13:39 by prod_rel_team + + +ROM: Bootstrap program is IOSv + +ios01 uptime is 7 weeks, 5 days, 11 hours, 14 minutes +System returned to ROM by reload +System image file is "flash0:/vios-adventerprisek9-m" +Last reload reason: Unknown reason + + + +This product contains cryptographic features and is subject to United +States and local country laws governing import, export, transfer and +use. Delivery of Cisco cryptographic products does not imply +third-party authority to import, export, distribute or use encryption. +Importers, exporters, distributors and users are responsible for +compliance with U.S. and local country laws. By using this product you +agree to comply with applicable laws and regulations. If you are unable +to comply with U.S. and local laws, return this product immediately. + +A summary of U.S. laws governing Cisco cryptographic products may be found at: +http://www.cisco.com/wwl/export/crypto/tool/stqrg.html + +If you require further assistance please contact us by sending email to +export@cisco.com. + +Cisco IOSv (revision 1.0) with with 472441K/50176K bytes of memory. +Processor board ID 99I10YFMUCJ3JEZMV4DQB +3 Gigabit Ethernet interfaces +DRAM configuration is 72 bits wide with parity disabled. +256K bytes of non-volatile configuration memory. +2097152K bytes of ATA System CompactFlash 0 (Read/Write) +0K bytes of ATA CompactFlash 1 (Read/Write) +0K bytes of ATA CompactFlash 2 (Read/Write) +10080K bytes of ATA CompactFlash 3 (Read/Write) + + + +Configuration register is 0x0 diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/ios_module.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/ios_module.py new file mode 100644 index 000000000..4c48934bc --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/ios_module.py @@ -0,0 +1,92 @@ +# (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 <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import json +import os + +from ansible_collections.cisco.ios.tests.unit.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 TestIosModule(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/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_acl_interfaces.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_acl_interfaces.py new file mode 100644 index 000000000..5d97c5890 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_acl_interfaces.py @@ -0,0 +1,483 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_acl_interfaces +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosAclInterfacesModule(TestIosModule): + module = ios_acl_interfaces + + def setUp(self): + super(TestIosAclInterfacesModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.acl_interfaces.acl_interfaces." + "Acl_interfacesFacts.get_acl_interfaces_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosAclInterfacesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + # def load_fixtures(self, commands=None): + # def load_from_file(*args, **kwargs): + # return load_fixture("ios_acl_interfaces.cfg") + + # self.execute_show_command.side_effect = load_from_file + + def test_ios_acl_interfaces_merged(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + ip access-group 110 in + ip access-group 123 out + ipv6 traffic-filter temp_v6 in + ipv6 traffic-filter test_v6 out + interface GigabitEthernet0/2 + ip access-group 110 in + ip access-group 123 out + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/1", + access_groups=[ + dict( + afi="ipv4", + acls=[ + dict(name="merge_110", direction="in"), + dict(name="merge_123", direction="out"), + ], + ), + dict( + afi="ipv6", + acls=[ + dict(name="merge_temp_v6", direction="in"), + dict(name="merge_test_v6", direction="out"), + ], + ), + ], + ), + dict( + name="GigabitEthernet0/2", + access_groups=[ + dict( + afi="ipv4", + acls=[ + dict(name="merge_110", direction="in"), + dict(name="merge_123", direction="out"), + ], + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "interface GigabitEthernet0/1", + "ip access-group merge_110 in", + "ip access-group merge_123 out", + "ipv6 traffic-filter merge_temp_v6 in", + "ipv6 traffic-filter merge_test_v6 out", + "interface GigabitEthernet0/2", + "ip access-group merge_110 in", + "ip access-group merge_123 out", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_acl_interfaces_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + ip access-group 110 in + ip access-group 123 out + ipv6 traffic-filter temp_v6 in + ipv6 traffic-filter test_v6 out + interface GigabitEthernet0/2 + ip access-group 110 in + ip access-group 123 out + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/1", + access_groups=[ + dict( + afi="ipv4", + acls=[ + dict(name="110", direction="in"), + dict(name="123", direction="out"), + ], + ), + dict( + afi="ipv6", + acls=[ + dict(name="test_v6", direction="out"), + dict(name="temp_v6", direction="in"), + ], + ), + ], + ), + dict( + name="GigabitEthernet0/2", + access_groups=[ + dict( + afi="ipv4", + acls=[ + dict(name="110", direction="in"), + dict(name="123", direction="out"), + ], + ), + ], + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_acl_interfaces_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + ip access-group 110 in + ip access-group 123 out + ipv6 traffic-filter temp_v6 in + ipv6 traffic-filter test_v6 out + interface GigabitEthernet0/2 + ip access-group 110 in + ip access-group 123 out + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/1", + access_groups=[ + dict( + afi="ipv4", + acls=[ + dict(name="replace_100", direction="out"), + dict(name="110", direction="in"), + ], + ), + ], + ), + ], + state="replaced", + ), + ) + commands = [ + "interface GigabitEthernet0/1", + "ip access-group replace_100 out", + "no ipv6 traffic-filter temp_v6 in", + "no ipv6 traffic-filter test_v6 out", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_acl_interfaces_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + ip access-group 110 in + ip access-group 123 out + ipv6 traffic-filter temp_v6 in + ipv6 traffic-filter test_v6 out + interface GigabitEthernet0/2 + ip access-group 110 in + ip access-group 123 out + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/1", + access_groups=[ + dict( + afi="ipv4", + acls=[ + dict(name="110", direction="in"), + dict(name="123", direction="out"), + ], + ), + dict( + afi="ipv6", + acls=[ + dict(name="test_v6", direction="out"), + dict(name="temp_v6", direction="in"), + ], + ), + ], + ), + ], + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_acl_interfaces_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + ip access-group 110 in + ip access-group 123 out + ipv6 traffic-filter temp_v6 in + ipv6 traffic-filter test_v6 out + interface GigabitEthernet0/2 + ip access-group 110 in + ip access-group 123 out + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/1", + access_groups=[ + dict( + afi="ipv4", + acls=[ + dict(name="100", direction="out"), + dict(name="110", direction="in"), + ], + ), + ], + ), + ], + state="overridden", + ), + ) + + commands = [ + "interface GigabitEthernet0/2", + "no ip access-group 110 in", + "no ip access-group 123 out", + "interface GigabitEthernet0/1", + "ip access-group 100 out", + "no ipv6 traffic-filter temp_v6 in", + "no ipv6 traffic-filter test_v6 out", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_acl_interfaces_overridden_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + ip access-group 110 in + ip access-group 123 out + ipv6 traffic-filter temp_v6 in + ipv6 traffic-filter test_v6 out + interface GigabitEthernet0/2 + ip access-group 110 in + ip access-group 123 out + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/1", + access_groups=[ + dict( + afi="ipv4", + acls=[ + dict(name="110", direction="in"), + dict(name="123", direction="out"), + ], + ), + dict( + afi="ipv6", + acls=[ + dict(name="test_v6", direction="out"), + dict(name="temp_v6", direction="in"), + ], + ), + ], + ), + dict( + name="GigabitEthernet0/2", + access_groups=[ + dict( + afi="ipv4", + acls=[ + dict(name="110", direction="in"), + dict(name="123", direction="out"), + ], + ), + ], + ), + ], + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_acl_interfaces_deleted_interface(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + ip access-group 110 in + ip access-group 123 out + ipv6 traffic-filter temp_v6 in + ipv6 traffic-filter test_v6 out + interface GigabitEthernet0/2 + ip access-group 110 in + ip access-group 123 out + """, + ) + set_module_args(dict(config=[dict(name="GigabitEthernet0/1")], state="deleted")) + commands = [ + "interface GigabitEthernet0/1", + "no ip access-group 110 in", + "no ip access-group 123 out", + "no ipv6 traffic-filter test_v6 out", + "no ipv6 traffic-filter temp_v6 in", + ] + self.execute_module(changed=True, commands=commands) + + def test_ios_acl_interfaces_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + ip access-group 110 in + ip access-group 123 out + ipv6 traffic-filter temp_v6 in + ipv6 traffic-filter test_v6 out + interface GigabitEthernet0/2 + ip access-group 110 in + ip access-group 123 out + """, + ) + set_module_args(dict(config=[dict(name="GigabitEthernet0/1")], state="deleted")) + commands = [ + "interface GigabitEthernet0/1", + "no ip access-group 110 in", + "no ip access-group 123 out", + "no ipv6 traffic-filter test_v6 out", + "no ipv6 traffic-filter temp_v6 in", + ] + self.execute_module(changed=True, commands=commands) + + def test_ios_acl_interfaces_parsed(self): + set_module_args( + dict( + running_config="interface GigabitEthernet0/1\n ip access-group 110 in\n ipv6 traffic-filter test_v6 out", + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [ + { + "name": "GigabitEthernet0/1", + "access_groups": [ + {"afi": "ipv4", "acls": [{"name": 110, "direction": "in"}]}, + {"afi": "ipv6", "acls": [{"name": "test_v6", "direction": "out"}]}, + ], + }, + ] + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_acl_interfaces_rendered(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + ip access-group 110 in + ip access-group 123 out + ipv6 traffic-filter temp_v6 in + ipv6 traffic-filter test_v6 out + interface GigabitEthernet0/2 + ip access-group 110 in + ip access-group 123 out + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/1", + access_groups=[ + dict( + afi="ipv4", + acls=[ + dict(name="110", direction="in"), + dict(name="123", direction="out"), + ], + ), + dict( + afi="ipv6", + acls=[ + dict(name="temp_v6", direction="in"), + dict(name="test_v6", direction="out"), + ], + ), + ], + ), + ], + state="rendered", + ), + ) + commands = [ + "interface GigabitEthernet0/1", + "ip access-group 110 in", + "ip access-group 123 out", + "ipv6 traffic-filter temp_v6 in", + "ipv6 traffic-filter test_v6 out", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), commands) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_acls.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_acls.py new file mode 100644 index 000000000..e383080f6 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_acls.py @@ -0,0 +1,1481 @@ +# +# (c) 2021, 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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_acls +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosAclsModule(TestIosModule): + module = ios_acls + + def setUp(self): + super(TestIosAclsModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.acls.acls." + "AclsFacts.get_acl_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosAclsModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_acls_merged(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + Extended IP access list 110 + 10 permit tcp 198.51.100.0 0.0.0.255 any eq 22 log (tag = testLog) + 20 deny icmp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 echo dscp ef ttl eq 10 + 30 deny icmp object-group test_network_og any dscp ef ttl eq 10 + IPv6 access list R1_TRAFFIC + deny tcp any eq www any eq telnet ack dscp af11 sequence 10 + Extended IP access list test_pre + 10 permit ip any any precedence internet + """, + ) + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + acls=[ + dict( + aces=[ + dict( + destination=dict(any=True), + grant="permit", + precedence="immediate", + protocol="ip", + sequence=20, + source=dict(any=True), + ), + ], + acl_type="extended", + name="test_pre", + ), + dict( + name="std_acl", + acl_type="standard", + aces=[ + dict( + grant="deny", + source=dict(address="192.0.2.0", wildcard_bits="0.0.0.255"), + ), + ], + ), + dict( + name="in_to_out", + acl_type="extended", + aces=[ + dict( + grant="permit", + protocol="tcp", + source=dict(host="10.1.1.2"), + destination=dict( + host="172.16.1.1", + port_protocol=dict(eq="telnet"), + ), + ), + dict( + grant="deny", + log_input=dict(user_cookie="test_logInput"), + protocol="ip", + source=dict(any=True), + destination=dict(any=True), + ), + ], + ), + dict( + name="test_acl_merge", + acl_type="extended", + aces=[ + dict( + grant="permit", + destination=dict( + address="192.0.2.0", + wildcard_bits="0.0.0.255", + port_protocol=dict(eq="80"), + ), + protocol="tcp", + sequence=100, + source=dict(host="192.0.2.1"), + ), + dict( + grant="deny", + protocol_options=dict(tcp=dict(ack="true")), + sequence="200", + source=dict(object_group="test_network_og"), + destination=dict(object_group="test_network_og"), + dscp="ef", + ttl=dict(eq=10), + ), + ], + ), + ], + ), + ], + state="merged", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "ip access-list standard std_acl", + "deny 192.0.2.0 0.0.0.255", + "ip access-list extended test_acl_merge", + "100 permit tcp host 192.0.2.1 192.0.2.0 0.0.0.255 eq www", + "200 deny tcp object-group test_network_og object-group test_network_og ack dscp ef ttl eq 10", + "ip access-list extended in_to_out", + "permit tcp host 10.1.1.2 host 172.16.1.1 eq telnet", + "deny ip any any log-input test_logInput", + "ip access-list extended test_pre", + "20 permit ip any any precedence immediate", + ] + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_acls_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + Extended IP access list 110 + 10 permit tcp 198.51.100.0 0.0.0.255 any eq 22 log (tag = testLog) + 20 deny icmp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 dscp ef ttl eq 10 + 30 deny icmp object-group test_network_og any dscp ef ttl eq 10 + IPv6 access list R1_TRAFFIC + deny tcp any eq www any eq telnet ack dscp af11 sequence 10 + """, + ) + + set_module_args( + dict( + config=[ + { + "afi": "ipv4", + "acls": [ + {"name": "110", "acl_type": "extended"}, + {"name": "test_acl", "acl_type": "standard"}, + ], + }, + { + "afi": "ipv6", + "acls": [ + { + "name": "R1_TRAFFIC", + "aces": [ + { + "sequence": 10, + "grant": "deny", + "protocol": "tcp", + "source": {"any": True, "port_protocol": {"eq": "www"}}, + "destination": { + "any": True, + "port_protocol": {"eq": "telnet"}, + }, + "dscp": "af11", + "protocol_options": {"tcp": {"ack": True}}, + }, + ], + }, + ], + }, + ], + state="merged", + ), + ) + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), []) + # self.execute_module(changed=False, commands=[], sort=True) + + def test_ios_acls_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + Extended IP access list 110 + 10 permit tcp 198.51.100.0 0.0.0.255 any eq 22 log (tag = testLog) + 20 deny icmp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 echo dscp ef ttl eq 10 + 30 deny icmp object-group test_network_og any dscp ef ttl eq 10 + IPv6 access list R1_TRAFFIC + deny tcp any eq www any eq telnet ack dscp af11 sequence 10 + ip access-list standard test_acl + remark remark check 1 + remark some random remark 2 + """, + ) + + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + acls=[ + dict( + name="replace_acl", + acl_type="extended", + aces=[ + dict( + grant="deny", + protocol_options=dict(tcp=dict(ack="true")), + source=dict( + address="198.51.100.0", + wildcard_bits="0.0.0.255", + ), + destination=dict( + address="198.51.101.0", + wildcard_bits="0.0.0.255", + port_protocol=dict(eq="telnet"), + ), + tos=dict(min_monetary_cost=True), + ), + ], + ), + dict( + name="test_acl", + acl_type="standard", + aces=[dict(remarks=["Another remark here"])], + ), + ], + ), + ], + state="replaced", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "ip access-list extended replace_acl", + "deny tcp 198.51.100.0 0.0.0.255 198.51.101.0 0.0.0.255 eq telnet ack tos min-monetary-cost", + "ip access-list standard test_acl", + "remark Another remark here", + "no remark remark check 1", + "no remark some random remark 2", + ] + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_acls_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + Extended IP access list 110 + 10 permit tcp 198.51.100.0 0.0.0.255 any eq 22 log (tag = testLog) + 20 deny icmp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 echo dscp ef ttl eq 10 + 30 deny icmp object-group test_network_og any dscp ef ttl eq 10 + IPv6 access list R1_TRAFFIC + deny tcp any eq www any eq telnet ack dscp af11 sequence 10 + Extended IP access list test_pre + 10 permit ip any any precedence internet + """, + ) + set_module_args( + dict( + config=[ + { + "afi": "ipv4", + "acls": [ + { + "name": "110", + "acl_type": "extended", + "aces": [ + { + "sequence": 10, + "grant": "permit", + "protocol": "tcp", + "source": { + "address": "198.51.100.0", + "wildcard_bits": "0.0.0.255", + }, + "destination": {"any": True, "port_protocol": {"eq": "22"}}, + "log": {"user_cookie": "testLog"}, + }, + { + "sequence": 20, + "grant": "deny", + "protocol": "icmp", + "source": { + "address": "192.0.2.0", + "wildcard_bits": "0.0.0.255", + }, + "destination": { + "address": "192.0.3.0", + "wildcard_bits": "0.0.0.255", + }, + "dscp": "ef", + "ttl": {"eq": 10}, + "protocol_options": {"icmp": {"echo": True}}, + }, + { + "sequence": 30, + "grant": "deny", + "protocol": "icmp", + "source": {"object_group": "test_network_og"}, + "destination": {"any": True}, + "dscp": "ef", + "ttl": {"eq": 10}, + }, + ], + }, + {"name": "test_acl", "acl_type": "standard"}, + { + "name": "test_pre", + "acl_type": "extended", + "aces": [ + { + "sequence": 10, + "grant": "permit", + "protocol": "ip", + "source": {"any": True}, + "destination": {"any": True}, + "precedence": "internet", + }, + ], + }, + ], + }, + { + "afi": "ipv6", + "acls": [ + { + "name": "R1_TRAFFIC", + "aces": [ + { + "sequence": 10, + "grant": "deny", + "protocol": "tcp", + "source": {"any": True, "port_protocol": {"eq": "www"}}, + "destination": { + "any": True, + "port_protocol": {"eq": "telnet"}, + }, + "dscp": "af11", + "protocol_options": {"tcp": {"ack": True}}, + }, + ], + }, + ], + }, + ], + state="replaced", + ), + ) + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), []) + + def test_ios_acls_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + Extended IP access list 110 + 10 permit tcp 198.51.100.0 0.0.0.255 any eq 22 log (tag = testLog) + 20 deny icmp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 echo dscp ef ttl eq 10 + 30 deny icmp object-group test_network_og any dscp ef ttl eq 10 + IPv6 access list R1_TRAFFIC + deny tcp any eq www any eq telnet ack dscp af11 sequence 10 + """, + ) + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + acls=[ + dict( + name="150", + aces=[ + dict( + grant="deny", + protocol_options=dict(tcp=dict(syn="true")), + source=dict( + address="198.51.100.0", + wildcard_bits="0.0.0.255", + port_protocol=dict(eq="telnet"), + ), + destination=dict( + address="198.51.110.0", + wildcard_bits="0.0.0.255", + port_protocol=dict(eq="telnet"), + ), + dscp="ef", + ttl=dict(eq=10), + ), + ], + ), + ], + ), + ], + state="overridden", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "no ipv6 access-list R1_TRAFFIC", + "no ip access-list standard test_acl", + "no ip access-list extended 110", + "ip access-list extended 150", + "deny tcp 198.51.100.0 0.0.0.255 eq telnet 198.51.110.0 0.0.0.255 eq telnet syn dscp ef ttl eq 10", + ] + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_acls_overridden_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + Extended IP access list 110 + 10 permit tcp 198.51.100.0 0.0.0.255 any eq 22 log (tag = testLog) + 20 deny icmp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 echo dscp ef ttl eq 10 + 30 deny icmp object-group test_network_og any dscp ef ttl eq 10 + Reflexive IP access list MIRROR + permit tcp host 0.0.0.0 eq 22 host 192.168.0.1 eq 50200 (2 matches) (time left 123) + permit tcp host 0.0.0.0 eq 22 host 192.168.0.1 eq 50201 (2 matches) (time left 345) + permit tcp host 0.0.0.0 eq 22 host 192.168.0.1 eq 50202 (2 matches) (time left 678) + IPv6 access list R1_TRAFFIC + deny tcp any eq www any eq telnet ack dscp af11 sequence 10 + """, + ) + set_module_args( + dict( + config=[ + { + "afi": "ipv4", + "acls": [ + { + "name": "110", + "acl_type": "extended", + "aces": [ + { + "sequence": 10, + "grant": "permit", + "protocol": "tcp", + "source": { + "address": "198.51.100.0", + "wildcard_bits": "0.0.0.255", + }, + "destination": {"any": True, "port_protocol": {"eq": "22"}}, + "log": {"user_cookie": "testLog"}, + }, + { + "sequence": 20, + "grant": "deny", + "protocol": "icmp", + "source": { + "address": "192.0.2.0", + "wildcard_bits": "0.0.0.255", + }, + "destination": { + "address": "192.0.3.0", + "wildcard_bits": "0.0.0.255", + }, + "dscp": "ef", + "ttl": {"eq": 10}, + "protocol_options": {"icmp": {"echo": True}}, + }, + { + "sequence": 30, + "grant": "deny", + "protocol": "icmp", + "source": {"object_group": "test_network_og"}, + "destination": {"any": True}, + "dscp": "ef", + "ttl": {"eq": 10}, + }, + ], + }, + {"name": "test_acl", "acl_type": "standard"}, + ], + }, + { + "afi": "ipv6", + "acls": [ + { + "name": "R1_TRAFFIC", + "aces": [ + { + "sequence": 10, + "grant": "deny", + "protocol": "tcp", + "source": {"any": True, "port_protocol": {"eq": "www"}}, + "destination": { + "any": True, + "port_protocol": {"eq": "telnet"}, + }, + "dscp": "af11", + "protocol_options": {"tcp": {"ack": True}}, + }, + ], + }, + ], + }, + ], + state="overridden", + ), + ) + result = self.execute_module(changed=False) + command = [] + self.assertEqual(sorted(result["commands"]), sorted(command)) + + def test_ios_acls_deleted_afi_based(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + Extended IP access list 110 + 10 permit tcp 198.51.100.0 0.0.0.255 any eq 22 log (tag = testLog) + 20 deny icmp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 echo dscp ef ttl eq 10 + 30 deny icmp object-group test_network_og any dscp ef ttl eq 10 + IPv6 access list R1_TRAFFIC + deny tcp any eq www any eq telnet ack dscp af11 sequence 10 + """, + ) + set_module_args(dict(config=[dict(afi="ipv4")], state="deleted")) + result = self.execute_module(changed=True) + commands = ["no ip access-list extended 110", "no ip access-list standard test_acl"] + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_acls_deleted_acl_based(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + Extended IP access list 110 + 10 permit tcp 198.51.100.0 0.0.0.255 any eq 22 log (tag = testLog) + 20 deny icmp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 echo dscp ef ttl eq 10 + 30 deny icmp object-group test_network_og any dscp ef ttl eq 10 + IPv6 access list R1_TRAFFIC + deny tcp any eq www any eq telnet ack dscp af11 sequence 10 + """, + ) + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + acls=[ + dict( + name="110", + aces=[ + dict( + grant="deny", + protocol_options=dict(icmp=dict(echo="true")), + sequence="10", + source=dict(address="192.0.2.0", wildcard_bits="0.0.0.255"), + destination=dict( + address="192.0.3.0", + wildcard_bits="0.0.0.255", + ), + dscp="ef", + ttl=dict(eq=10), + ), + ], + ), + ], + ), + dict( + afi="ipv6", + acls=[ + dict( + name="R1_TRAFFIC", + aces=[ + dict( + grant="deny", + protocol_options=dict(tcp=dict(ack="true")), + sequence="10", + source=dict(any="true", port_protocol=dict(eq="www")), + destination=dict( + any="true", + port_protocol=dict(eq="telnet"), + ), + dscp="af11", + ), + ], + ), + ], + ), + ], + state="deleted", + ), + ) + result = self.execute_module(changed=True) + commands = ["no ip access-list extended 110", "no ipv6 access-list R1_TRAFFIC"] + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_acls_rendered(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + acls=[ + dict( + name="110", + acl_type="extended", + aces=[ + dict( + grant="deny", + sequence="10", + remarks=["check for remark", "remark for acl 110"], + protocol_options=dict(tcp=dict(syn="true")), + source=dict(address="192.0.2.0", wildcard_bits="0.0.0.255"), + destination=dict( + address="192.0.3.0", + wildcard_bits="0.0.0.255", + port_protocol=dict(eq="www"), + ), + dscp="ef", + ttl=dict(eq=10), + ), + ], + ), + ], + ), + ], + state="rendered", + ), + ) + commands = [ + "ip access-list extended 110", + "10 deny tcp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 eq www syn dscp ef ttl eq 10", + "remark check for remark", + "remark remark for acl 110", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) + + def test_ios_acls_parsed(self): + set_module_args( + dict( + running_config="""IPv6 access list R1_TRAFFIC\n deny tcp any eq www any range 10 20 ack dscp af11 sequence 10 + 20 permit icmp host 192.0.2.1 host 192.0.2.2 echo\n 30 permit icmp host 192.0.2.3 host 192.0.2.4 echo-reply""", + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [ + { + "afi": "ipv6", + "acls": [ + { + "name": "R1_TRAFFIC", + "aces": [ + { + "sequence": 10, + "grant": "deny", + "protocol": "tcp", + "source": {"any": True, "port_protocol": {"eq": "www"}}, + "destination": { + "any": True, + "port_protocol": {"range": {"start": 10, "end": 20}}, + }, + "dscp": "af11", + "protocol_options": {"tcp": {"ack": True}}, + }, + { + "sequence": 20, + "grant": "permit", + "protocol": "icmp", + "source": {"host": "192.0.2.1"}, + "destination": {"host": "192.0.2.2"}, + "protocol_options": {"icmp": {"echo": True}}, + }, + { + "sequence": 30, + "grant": "permit", + "protocol": "icmp", + "source": {"host": "192.0.2.3"}, + "destination": {"host": "192.0.2.4"}, + "protocol_options": {"icmp": {"echo_reply": True}}, + }, + ], + }, + ], + }, + ] + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_acls_parsed_matches(self): + set_module_args( + dict( + running_config="""Standard IP access list R1_TRAFFIC\n10 permit 10.11.12.13 (2 matches)\n + 40 permit 128.0.0.0, wildcard bits 63.255.255.255 (2 matches)\n60 permit 134.107.136.0, wildcard bits 0.0.0.255 (1 match)""", + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [ + { + "afi": "ipv4", + "acls": [ + { + "name": "R1_TRAFFIC", + "acl_type": "standard", + "aces": [ + {"sequence": 10, "grant": "permit", "source": {"host": "10.11.12.13"}}, + { + "sequence": 40, + "grant": "permit", + "source": { + "address": "128.0.0.0", + "wildcard_bits": "63.255.255.255", + }, + }, + { + "grant": "permit", + "sequence": 60, + "source": { + "address": "134.107.136.0", + "wildcard_bits": "0.0.0.255", + }, + }, + ], + }, + ], + }, + ] + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_acls_overridden_remark(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + Extended IP access list 110 + 10 permit tcp 198.51.100.0 0.0.0.255 any eq 22 log (tag = testLog) + 20 deny icmp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 echo dscp ef ttl eq 10 + 30 deny icmp object-group test_network_og any dscp ef ttl eq 10 + access-list 110 remark test ab. + access-list 110 remark test again ab. + """, + ) + set_module_args( + dict( + config=[ + { + "afi": "ipv4", + "acls": [ + { + "name": "110", + "acl_type": "extended", + "aces": [{"remarks": ["test ab.", "test again ab."]}], + }, + {"name": "test_acl", "acl_type": "standard"}, + ], + }, + ], + state="overridden", + ), + ) + result = self.execute_module(changed=True, sort=True) + cmds = [ + "ip access-list extended 110", + "no 10 permit tcp 198.51.100.0 0.0.0.255 any eq 22 log testLog", + "no 20 deny icmp 192.0.2.0 0.0.0.255 192.0.3.0 0.0.0.255 echo dscp ef ttl eq 10", + "no 30 deny icmp object-group test_network_og any dscp ef ttl eq 10", + ] + self.assertEqual(sorted(result["commands"]), cmds) + + def test_ios_acls_overridden_option(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + ip access-list standard test_acl + remark remark check 1 + remark some random remark 2 + """, + ) + + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + acls=[ + dict( + name="113", + acl_type="extended", + aces=[ + dict( + grant="deny", + protocol_options=dict(tcp=dict(ack="true")), + source=dict( + address="198.51.100.0", + wildcard_bits="0.0.0.255", + ), + destination=dict( + address="198.51.101.0", + wildcard_bits="0.0.0.255", + port_protocol=dict(eq="telnet"), + ), + tos=dict(min_monetary_cost=True), + ), + dict( + grant="permit", + protocol_options=dict(protocol_number=433), + source=dict( + address="198.51.101.0", + wildcard_bits="0.0.0.255", + ), + destination=dict( + address="198.51.102.0", + wildcard_bits="0.0.0.255", + port_protocol=dict(eq="telnet"), + ), + log=dict(user_cookie="check"), + tos=dict(max_throughput=True), + ), + dict( + grant="permit", + source=dict( + address="198.51.102.0", + wildcard_bits="0.0.0.255", + ), + destination=dict( + address="198.51.103.0", + wildcard_bits="0.0.0.255", + ), + precedence=10, + tos=dict(normal=True), + ), + dict( + grant="permit", + source=dict( + address="198.51.105.0", + wildcard_bits="0.0.0.255", + ), + destination=dict( + address="198.51.106.0", + wildcard_bits="0.0.0.255", + ), + time_range=20, + tos=dict(max_throughput=True), + ), + ], + ), + dict( + name="test_acl", + acl_type="standard", + aces=[dict(remarks=["Another remark here"])], + ), + ], + ), + ], + state="overridden", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "ip access-list extended 113", + "deny tcp 198.51.100.0 0.0.0.255 198.51.101.0 0.0.0.255 eq telnet ack tos min-monetary-cost", + "permit 198.51.102.0 0.0.0.255 198.51.103.0 0.0.0.255 precedence 10 tos normal", + "permit 433 198.51.101.0 0.0.0.255 198.51.102.0 0.0.0.255 eq telnet log check tos max-throughput", + "permit 198.51.105.0 0.0.0.255 198.51.106.0 0.0.0.255 time-range 20 tos max-throughput", + "ip access-list standard test_acl", + "remark Another remark here", + "no remark remark check 1", + "no remark some random remark 2", + ] + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_acls_overridden_clear(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + acls=[ + dict( + name="113", + acl_type="extended", + aces=[ + dict( + grant="deny", + source=dict( + address="198.51.100.0", + wildcard_bits="0.0.0.255", + ), + destination=dict( + address="198.51.101.0", + wildcard_bits="0.0.0.255", + ), + tos=dict(max_reliability=True), + enable_fragments=True, + ), + dict( + remarks=["extended ACL remark"], + grant="permit", + source=dict( + address="198.51.101.0", + wildcard_bits="0.0.0.255", + ), + destination=dict( + address="198.51.102.0", + wildcard_bits="0.0.0.255", + ), + log=dict(user_cookie="check"), + tos=dict(service_value="119"), + ), + ], + ), + dict( + name="23", + acl_type="standard", + aces=[dict(remarks=["check remark here"])], + ), + ], + ), + ], + state="overridden", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "ip access-list extended 113", + "deny 198.51.100.0 0.0.0.255 198.51.101.0 0.0.0.255 fragments tos max-reliability", + "permit 198.51.101.0 0.0.0.255 198.51.102.0 0.0.0.255 log check tos 119", + "remark extended ACL remark", + "ip access-list standard 23", + "remark check remark here", + ] + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_delete_acl(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list 2 + 30 permit 172.16.1.11 + 20 permit 172.16.1.10 log + 10 permit 172.16.1.2 + """, + ) + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + acls=[ + dict( + name="2", + aces=[ + dict( + grant="permit", + source=dict(host="192.0.2.1"), + sequence=10, + ), + ], + ), + ], + ), + ], + state="deleted", + ), + ) + + result = self.execute_module(changed=True) + commands = ["no ip access-list standard 2"] + self.assertEqual(result["commands"], commands) + + def test_ios_failed_extra_param_standard_acl(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + ip access-list standard test_acl + remark remark check 1 + remark some random remark 2 + """, + ) + + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + acls=[ + dict( + name="test_acl", + acl_type="standard", + aces=[ + dict( + grant="deny", + sequence=10, + source=dict( + address="198.51.100.0", + wildcard_bits="0.0.0.255", + ), + destination=dict(any="True"), + ), + ], + ), + ], + ), + ], + state="overridden", + ), + ) + result = self.execute_module(failed=True) + self.assertEqual(result, {"failed": True}) + + def test_ios_failed_update_with_merged(self): + self.execute_show_command.return_value = dedent( + """\ + Standard IP access list test_acl + 30 permit 172.16.1.11 + 20 permit 172.16.1.10 log + 10 permit 172.16.1.2 + """, + ) + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + acls=[ + dict( + name="test_acl", + aces=[ + dict( + grant="permit", + source=dict(host="192.0.2.1"), + sequence=10, + ), + ], + ), + ], + ), + ], + state="merged", + ), + ) + + result = self.execute_module(failed=True) + self.assertEqual( + result, + { + "msg": "Cannot update existing sequence 10 of ACLs test_acl with state merged. Please use state replaced or overridden.", + "failed": True, + }, + ) + + def test_ios_acls_parsed_multioption(self): + set_module_args( + dict( + running_config=dedent( + """\ + Standard IP access list 2 + 30 permit 172.16.1.11 + 20 permit 172.16.1.10 + 10 permit 172.16.1.2 + Extended IP access list 101 + 15 permit tcp any host 172.16.2.9 + 18 permit tcp any host 172.16.2.11 + 20 permit udp host 172.16.1.21 any + 30 permit udp host 172.16.1.22 any + 40 deny icmp any 10.1.1.0 0.0.0.255 echo + 50 permit ip any 10.1.1.0 0.0.0.255 + 60 permit tcp any host 10.1.1.1 eq telnet + 70 permit tcp 10.1.1.0 0.0.0.255 172.16.1.0 0.0.0.255 eq telnet time-range EVERYOTHERDAY (active) + Extended IP access list outboundfilters + 10 permit icmp 10.1.1.0 0.0.0.255 172.16.1.0 0.0.0.255 + Extended IP access list test + 10 permit ip host 10.2.2.2 host 10.3.3.3 + 20 permit tcp host 10.1.1.1 host 10.5.5.5 eq www + 30 permit icmp any any + 40 permit udp host 10.6.6.6 10.10.10.0 0.0.0.255 eq domain + Extended MAC access list system-cpp-bpdu-range + permit any 0180.c200.0000 0000.0000.0003 + Extended MAC access list system-cpp-cdp + permit any host 0100.0ccc.cccc + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [ + { + "afi": "ipv4", + "acls": [ + { + "name": "101", + "acl_type": "extended", + "aces": [ + { + "sequence": 15, + "grant": "permit", + "protocol": "tcp", + "source": {"any": True}, + "destination": {"host": "172.16.2.9"}, + }, + { + "sequence": 18, + "grant": "permit", + "protocol": "tcp", + "source": {"any": True}, + "destination": {"host": "172.16.2.11"}, + }, + { + "sequence": 20, + "grant": "permit", + "protocol": "udp", + "source": {"host": "172.16.1.21"}, + "destination": {"any": True}, + }, + { + "sequence": 30, + "grant": "permit", + "protocol": "udp", + "source": {"host": "172.16.1.22"}, + "destination": {"any": True}, + }, + { + "sequence": 40, + "grant": "deny", + "protocol": "icmp", + "source": {"any": True}, + "destination": { + "address": "10.1.1.0", + "wildcard_bits": "0.0.0.255", + }, + "protocol_options": {"icmp": {"echo": True}}, + }, + { + "sequence": 50, + "grant": "permit", + "protocol": "ip", + "source": {"any": True}, + "destination": { + "address": "10.1.1.0", + "wildcard_bits": "0.0.0.255", + }, + }, + { + "sequence": 60, + "grant": "permit", + "protocol": "tcp", + "source": {"any": True}, + "destination": { + "host": "10.1.1.1", + "port_protocol": {"eq": "telnet"}, + }, + }, + { + "sequence": 70, + "grant": "permit", + "protocol": "tcp", + "source": {"address": "10.1.1.0", "wildcard_bits": "0.0.0.255"}, + "destination": { + "address": "172.16.1.0", + "port_protocol": {"eq": "telnet"}, + "wildcard_bits": "0.0.0.255", + }, + "time_range": "EVERYOTHERDAY", + }, + ], + }, + { + "name": "2", + "acl_type": "standard", + "aces": [ + {"sequence": 30, "grant": "permit", "source": {"host": "172.16.1.11"}}, + {"sequence": 20, "grant": "permit", "source": {"host": "172.16.1.10"}}, + {"sequence": 10, "grant": "permit", "source": {"host": "172.16.1.2"}}, + ], + }, + { + "name": "outboundfilters", + "acl_type": "extended", + "aces": [ + { + "sequence": 10, + "grant": "permit", + "protocol": "icmp", + "source": {"address": "10.1.1.0", "wildcard_bits": "0.0.0.255"}, + "destination": { + "address": "172.16.1.0", + "wildcard_bits": "0.0.0.255", + }, + }, + ], + }, + { + "name": "test", + "acl_type": "extended", + "aces": [ + { + "sequence": 10, + "grant": "permit", + "protocol": "ip", + "source": {"host": "10.2.2.2"}, + "destination": {"host": "10.3.3.3"}, + }, + { + "sequence": 20, + "grant": "permit", + "protocol": "tcp", + "source": {"host": "10.1.1.1"}, + "destination": {"host": "10.5.5.5", "port_protocol": {"eq": "www"}}, + }, + { + "sequence": 30, + "grant": "permit", + "protocol": "icmp", + "source": {"any": True}, + "destination": {"any": True}, + }, + { + "sequence": 40, + "grant": "permit", + "protocol": "udp", + "source": {"host": "10.6.6.6"}, + "destination": { + "address": "10.10.10.0", + "port_protocol": {"eq": "domain"}, + "wildcard_bits": "0.0.0.255", + }, + }, + ], + }, + ], + }, + ] + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_acls_rendered_muiltioption(self): + set_module_args( + dict( + config=[ + { + "afi": "ipv4", + "acls": [ + { + "name": "101", + "acl_type": "extended", + "aces": [ + { + "sequence": 15, + "grant": "permit", + "protocol": "tcp", + "source": {"any": True}, + "destination": {"host": "172.16.2.9"}, + }, + { + "sequence": 18, + "grant": "permit", + "protocol": "tcp", + "source": {"any": True}, + "destination": {"host": "172.16.2.11"}, + }, + { + "sequence": 20, + "grant": "permit", + "protocol": "udp", + "source": {"host": "172.16.1.21"}, + "destination": {"any": True}, + }, + { + "sequence": 30, + "grant": "permit", + "protocol": "udp", + "source": {"host": "172.16.1.22"}, + "destination": {"any": True}, + }, + { + "sequence": 40, + "grant": "deny", + "protocol": "icmp", + "source": {"any": True}, + "destination": { + "address": "10.1.1.0", + "wildcard_bits": "0.0.0.255", + }, + "protocol_options": {"icmp": {"echo": True}}, + }, + { + "sequence": 50, + "grant": "permit", + "protocol": "ip", + "source": {"any": True}, + "destination": { + "address": "10.1.1.0", + "wildcard_bits": "0.0.0.255", + }, + }, + { + "sequence": 60, + "grant": "permit", + "protocol": "tcp", + "source": {"any": True}, + "destination": { + "host": "10.1.1.1", + "port_protocol": {"eq": "telnet"}, + }, + }, + { + "sequence": 70, + "grant": "permit", + "protocol": "tcp", + "source": { + "address": "10.1.1.0", + "wildcard_bits": "0.0.0.255", + }, + "destination": { + "address": "172.16.1.0", + "port_protocol": {"eq": "telnet"}, + "wildcard_bits": "0.0.0.255", + }, + "time_range": "EVERYOTHERDAY", + }, + ], + }, + { + "name": "2", + "acl_type": "standard", + "aces": [ + { + "sequence": 30, + "grant": "permit", + "source": {"host": "172.16.1.11"}, + }, + { + "sequence": 20, + "grant": "permit", + "source": {"host": "172.16.1.10"}, + }, + { + "sequence": 10, + "grant": "permit", + "source": {"host": "172.16.1.2"}, + }, + ], + }, + { + "name": "outboundfilters", + "acl_type": "extended", + "aces": [ + { + "sequence": 10, + "grant": "permit", + "protocol": "icmp", + "source": { + "address": "10.1.1.0", + "wildcard_bits": "0.0.0.255", + }, + "destination": { + "address": "172.16.1.0", + "wildcard_bits": "0.0.0.255", + }, + }, + ], + }, + { + "name": "test", + "acl_type": "extended", + "aces": [ + { + "sequence": 10, + "grant": "permit", + "protocol": "ip", + "source": {"host": "10.2.2.2"}, + "destination": {"host": "10.3.3.3"}, + }, + { + "sequence": 20, + "grant": "permit", + "protocol": "tcp", + "source": {"host": "10.1.1.1"}, + "destination": { + "host": "10.5.5.5", + "port_protocol": {"eq": "www"}, + }, + }, + { + "sequence": 30, + "grant": "permit", + "protocol": "icmp", + "source": {"any": True}, + "destination": {"any": True}, + }, + { + "sequence": 40, + "grant": "permit", + "protocol": "udp", + "source": {"host": "10.6.6.6"}, + "destination": { + "address": "10.10.10.0", + "port_protocol": {"eq": "domain"}, + "wildcard_bits": "0.0.0.255", + }, + }, + ], + }, + ], + }, + ], + state="rendered", + ), + ) + commands = [ + "ip access-list extended 101", + "15 permit tcp any host 172.16.2.9", + "18 permit tcp any host 172.16.2.11", + "20 permit udp host 172.16.1.21 any", + "30 permit udp host 172.16.1.22 any", + "40 deny icmp any 10.1.1.0 0.0.0.255 echo", + "50 permit ip any 10.1.1.0 0.0.0.255", + "60 permit tcp any host 10.1.1.1 eq telnet", + "70 permit tcp 10.1.1.0 0.0.0.255 172.16.1.0 0.0.0.255 eq telnet time-range EVERYOTHERDAY", + "ip access-list standard 2", + "30 permit host 172.16.1.11", + "20 permit host 172.16.1.10", + "10 permit host 172.16.1.2", + "ip access-list extended outboundfilters", + "10 permit icmp 10.1.1.0 0.0.0.255 172.16.1.0 0.0.0.255", + "ip access-list extended test", + "10 permit ip host 10.2.2.2 host 10.3.3.3", + "20 permit tcp host 10.1.1.1 host 10.5.5.5 eq www", + "30 permit icmp any any", + "40 permit udp host 10.6.6.6 10.10.10.0 0.0.0.255 eq domain", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_banner.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_banner.py new file mode 100644 index 000000000..85e2736f0 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_banner.py @@ -0,0 +1,96 @@ +# 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 <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.cisco.ios.plugins.modules import ios_banner +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosBannerModule(TestIosModule): + module = ios_banner + + def setUp(self): + super(TestIosBannerModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_banner.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_banner.load_config", + ) + self.load_config = self.mock_load_config.start() + + def tearDown(self): + super(TestIosBannerModule, self).tearDown() + self.mock_get_config.stop() + self.mock_load_config.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + return load_fixture("ios_banner_show_running_config_ios12.txt") + + self.get_config.side_effect = load_from_file + + def test_ios_banner_create(self): + for banner_type in ("login", "motd", "exec", "incoming", "slip-ppp"): + set_module_args(dict(banner=banner_type, text="test\nbanner\nstring")) + commands = ["banner {0} @\ntest\nbanner\nstring\n@".format(banner_type)] + self.execute_module(changed=True, commands=commands) + + def test_ios_banner_remove(self): + set_module_args(dict(banner="login", state="absent")) + commands = ["no banner login"] + self.execute_module(changed=True, commands=commands) + + def test_ios_banner_nochange(self): + banner_text = load_fixture("ios_banner_show_banner.txt") + set_module_args(dict(banner="login", text=banner_text[:-1])) + self.execute_module() + + def test_ios_banner_idemp(self): + banner_text = "" + set_module_args(dict(banner="login", text=banner_text)) + self.execute_module() + + def test_ios_banner_create_delimiter(self): + for banner_type in ("login", "motd", "exec", "incoming", "slip-ppp"): + set_module_args( + dict(banner=banner_type, text="test\nbanner\nstring", multiline_delimiter="c"), + ) + commands = ["banner {0} c\ntest\nbanner\nstring\nc".format(banner_type)] + self.execute_module(changed=True, commands=commands) + + +class TestIosBannerIos12Module(TestIosBannerModule): + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + return load_fixture("ios_banner_show_running_config_ios12.txt") + + self.get_config.side_effect = load_from_file + + def test_ios_banner_nochange(self): + banner_text = load_fixture("ios_banner_show_banner.txt") + set_module_args(dict(banner="exec", text=banner_text[:-1])) + self.execute_module() diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp.py new file mode 100644 index 000000000..0145a3c6a --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp.py @@ -0,0 +1,344 @@ +# +# (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 ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.cli.config.bgp.process import ( + Provider, +) +from ansible_collections.cisco.ios.plugins.modules import ios_bgp + +from .ios_module import TestIosModule, load_fixture + + +class TestIosBgpModule(TestIosModule): + module = ios_bgp + + def setUp(self): + super(TestIosBgpModule, self).setUp() + self._bgp_config = load_fixture("ios_bgp_config.cfg") + + def test_ios_bgp(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + router_id="192.0.2.2", + networks=None, + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual(commands, ["router bgp 64496", "bgp router-id 192.0.2.2", "exit"]) + + def test_ios_bgp_idempotent(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + router_id="192.0.2.1", + networks=None, + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_ios_bgp_remove(self): + obj = Provider( + params=dict( + config=dict(bgp_as=64496, networks=None, address_family=None), + operation="delete", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual(commands, ["no router bgp 64496"]) + + def test_ios_bgp_neighbor(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + neighbors=[dict(neighbor="192.51.100.2", remote_as=64496)], + networks=None, + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual( + commands, + ["router bgp 64496", "neighbor 192.51.100.2 remote-as 64496", "exit"], + ) + + def test_ios_bgp_neighbor_idempotent(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + neighbors=[ + dict( + neighbor="192.51.100.1", + remote_as=64496, + timers=dict(keepalive=120, holdtime=360, min_neighbor_holdtime=360), + ), + ], + networks=None, + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_ios_bgp_network(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + networks=[dict(prefix="192.0.1.0", masklen=23, route_map="RMAP_1")], + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual( + sorted(commands), + sorted( + [ + "router bgp 64496", + "network 192.0.1.0 mask 255.255.254.0 route-map RMAP_1", + "exit", + ], + ), + ) + + def test_ios_bgp_network_idempotent(self): + obj = Provider( + params=dict( + config=dict( + bgp_as=64496, + networks=[ + dict(prefix="192.0.2.0", masklen=23, route_map="RMAP_1"), + dict(prefix="198.51.100.0", masklen=25, route_map="RMAP_2"), + ], + address_family=None, + ), + operation="merge", + ), + ) + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_ios_bgp_address_family_redistribute(self): + rd_1 = dict(protocol="ospf", id="233", metric=90, route_map=None) + + config = dict( + bgp_as=64496, + address_family=[dict(afi="ipv4", safi="unicast", redistribute=[rd_1])], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + cmd = [ + "router bgp 64496", + "address-family ipv4", + "redistribute ospf 233 metric 90", + "exit-address-family", + "exit", + ] + self.assertEqual(sorted(commands), sorted(cmd)) + + def test_ios_bgp_address_family_redistribute_idempotent(self): + rd_1 = dict(protocol="eigrp", metric=10, route_map="RMAP_3", id=None) + rd_2 = dict(protocol="static", metric=100, id=None, route_map=None) + + config = dict( + bgp_as=64496, + address_family=[dict(afi="ipv4", safi="unicast", redistribute=[rd_1, rd_2])], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_ios_bgp_address_family_neighbors(self): + af_nbr_1 = dict(neighbor="192.51.100.1", maximum_prefix=35, activate=True) + af_nbr_2 = dict(neighbor="192.51.100.3", route_reflector_client=True, activate=True) + + config = dict( + bgp_as=64496, + address_family=[dict(afi="ipv4", safi="multicast", neighbors=[af_nbr_1, af_nbr_2])], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + cmd = [ + "router bgp 64496", + "address-family ipv4 multicast", + "neighbor 192.51.100.1 activate", + "neighbor 192.51.100.1 maximum-prefix 35", + "neighbor 192.51.100.3 activate", + "neighbor 192.51.100.3 route-reflector-client", + "exit-address-family", + "exit", + ] + self.assertEqual(sorted(commands), sorted(cmd)) + + def test_ios_bgp_address_family_neighbors_idempotent(self): + af_nbr_1 = dict(neighbor="203.0.113.1", remove_private_as=True, maximum_prefix=100) + + config = dict( + bgp_as=64496, + address_family=[dict(afi="ipv4", safi="unicast", neighbors=[af_nbr_1])], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_ios_bgp_address_family_networks(self): + net = dict(prefix="1.0.0.0", masklen=8, route_map="RMAP_1") + net2 = dict(prefix="192.168.1.0", masklen=24, route_map="RMAP_2") + + config = dict( + bgp_as=64496, + address_family=[dict(afi="ipv4", safi="multicast", networks=[net, net2])], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + cmd = [ + "router bgp 64496", + "address-family ipv4 multicast", + "network 1.0.0.0 mask 255.0.0.0 route-map RMAP_1", + "network 192.168.1.0 mask 255.255.255.0 route-map RMAP_2", + "exit-address-family", + "exit", + ] + self.assertEqual(sorted(commands), sorted(cmd)) + + def test_ios_bgp_address_family_networks_idempotent(self): + net = dict(prefix="203.0.113.0", masklen=27, route_map="RMAP_1") + net2 = dict(prefix="192.0.2.0", masklen=26, route_map="RMAP_2") + + config = dict( + bgp_as=64496, + address_family=[dict(afi="ipv4", safi="multicast", networks=[net, net2])], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="merge")) + + commands = obj.render(self._bgp_config) + self.assertEqual(commands, []) + + def test_ios_bgp_operation_override(self): + net_1 = dict(prefix="1.0.0.0", masklen=8, route_map="RMAP_1") + net_2 = dict(prefix="192.168.1.0", masklen=24, route_map="RMAP_2") + nbr_1 = dict(neighbor="192.51.100.1", remote_as=64496, update_source="GigabitEthernet0/1") + nbr_2 = dict( + neighbor="192.51.100.3", + remote_as=64496, + timers=dict(keepalive=300, holdtime=360, min_neighbor_holdtime=360), + ) + af_nbr_1 = dict(neighbor="192.51.100.1", maximum_prefix=35) + af_nbr_2 = dict(neighbor="192.51.100.3", route_reflector_client=True) + + af_1 = dict(afi="ipv4", safi="unicast", neighbors=[af_nbr_1, af_nbr_2]) + af_2 = dict(afi="ipv4", safi="multicast", networks=[net_1, net_2]) + config = dict( + bgp_as=64496, + neighbors=[nbr_1, nbr_2], + address_family=[af_1, af_2], + networks=None, + ) + + obj = Provider(params=dict(config=config, operation="override")) + commands = obj.render(self._bgp_config) + + cmd = [ + "no router bgp 64496", + "router bgp 64496", + "neighbor 192.51.100.1 remote-as 64496", + "neighbor 192.51.100.1 update-source GigabitEthernet0/1", + "neighbor 192.51.100.3 remote-as 64496", + "neighbor 192.51.100.3 timers 300 360 360", + "address-family ipv4", + "neighbor 192.51.100.1 maximum-prefix 35", + "neighbor 192.51.100.3 route-reflector-client", + "exit-address-family", + "address-family ipv4 multicast", + "network 1.0.0.0 mask 255.0.0.0 route-map RMAP_1", + "network 192.168.1.0 mask 255.255.255.0 route-map RMAP_2", + "exit-address-family", + "exit", + ] + + self.assertEqual(sorted(commands), sorted(cmd)) + + def test_ios_bgp_operation_replace(self): + rd = dict(protocol="ospf", id=223, metric=110, route_map=None) + net = dict(prefix="203.0.113.0", masklen=27, route_map="RMAP_1") + net2 = dict(prefix="192.0.2.0", masklen=26, route_map="RMAP_2") + + af_1 = dict(afi="ipv4", safi="unicast", redistribute=[rd]) + af_2 = dict(afi="ipv4", safi="multicast", networks=[net, net2]) + + config = dict(bgp_as=64496, address_family=[af_1, af_2], networks=None) + obj = Provider(params=dict(config=config, operation="replace")) + commands = obj.render(self._bgp_config) + + cmd = [ + "router bgp 64496", + "address-family ipv4", + "redistribute ospf 223 metric 110", + "no redistribute eigrp", + "no redistribute static", + "exit-address-family", + "exit", + ] + + self.assertEqual(sorted(commands), sorted(cmd)) + + def test_ios_bgp_operation_replace_with_new_as(self): + rd = dict(protocol="ospf", id=223, metric=110, route_map=None) + + af_1 = dict(afi="ipv4", safi="unicast", redistribute=[rd]) + + config = dict(bgp_as=64497, address_family=[af_1], networks=None) + obj = Provider(params=dict(config=config, operation="replace")) + commands = obj.render(self._bgp_config) + + cmd = [ + "no router bgp 64496", + "router bgp 64497", + "address-family ipv4", + "redistribute ospf 223 metric 110", + "exit-address-family", + "exit", + ] + + self.assertEqual(sorted(commands), sorted(cmd)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp_address_family.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp_address_family.py new file mode 100644 index 000000000..3bd293925 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp_address_family.py @@ -0,0 +1,1374 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_bgp_address_family +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosBgpAddressFamilyModule(TestIosModule): + module = ios_bgp_address_family + + def setUp(self): + super(TestIosBgpAddressFamilyModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.bgp_address_family.bgp_address_family." + "Bgp_address_familyFacts.get_bgp_address_family_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosBgpAddressFamilyModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_bgp_address_family_merged(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp log-neighbor-changes + bgp nopeerup-delay cold-boot 20 + neighbor TEST-PEER-GROUP peer-group + neighbor 2001:db8::1 peer-group TEST-PEER-GROUP + neighbor 2001:db8::1 description TEST-PEER-GROUP-DESCRIPTION + ! + address-family ipv4 + bgp redistribute-internal + redistribute connected + redistribute ospf 200 metric 100 match internal external 1 external 2 + neighbor TEST-PEER-GROUP send-community + neighbor TEST-PEER-GROUP next-hop-self all + neighbor 2001:db8::1 activate + ! + address-family ipv4 multicast + table-map test_tableMap filter + network 198.51.111.11 mask 255.255.255.255 route-map test + aggregate-address 192.0.3.1 255.255.255.255 as-confed-set + default-metric 12 + distance bgp 10 10 100 + exit-address-family + ! + address-family ipv4 mdt + bgp dampening 1 10 100 5 + bgp dmzlink-bw + bgp soft-reconfig-backup + exit-address-family + ! + address-family ipv4 multicast vrf blue + bgp aggregate-timer 10 + bgp slow-peer detection threshold 150 + bgp dampening 1 1 1 1 + network 198.51.110.10 mask 255.255.255.255 backdoor + aggregate-address 192.0.2.1 255.255.255.255 as-confed-set + neighbor 198.51.100.1 remote-as 10 + neighbor 198.51.100.1 local-as 20 + neighbor 198.51.100.1 activate + neighbor 198.51.100.1 next-hop-self all + neighbor 198.51.100.1 aigp send cost-community 100 poi igp-cost transitive + neighbor 198.51.100.1 route-server-client + neighbor 198.51.100.1 prefix-list AS65100-PREFIX-OUT out + neighbor 198.51.100.1 slow-peer detection threshold 150 + neighbor 198.51.100.1 route-map test-out out + exit-address-family + """, + ) + + set_module_args( + dict( + config=dict( + as_number="65000", + address_family=[ + dict( + afi="ipv4", + safi="multicast", + vrf="blue", + aggregate_addresses=[ + dict( + address="192.0.3.1", + netmask="255.255.255.255", + as_confed_set=True, + ), + ], + bgp=dict( + dampening=dict( + penalty_half_time=10, + reuse_route_val=10, + suppress_route_val=10, + max_suppress=10, + ), + ), + neighbors=[ + dict( + neighbor_address="198.51.100.1", + remote_as=65100, + route_maps=[dict(name="test-route-out", out="true")], + prefix_lists=[dict(name="AS65100-PREFIX-OUT", out="true")], + ), + ], + ), + dict( + afi="nsap", + bgp=dict(aggregate_timer=20, dmzlink_bw=True, scan_time=10), + default_metric=10, + networks=[dict(address="192.0.1.1", route_map="test_route")], + ), + ], + ), + state="merged", + ), + ) + commands = [ + "router bgp 65000", + "address-family ipv4 multicast vrf blue", + "bgp dampening 10 10 10 10", + "aggregate-address 192.0.3.1 255.255.255.255 as-confed-set", + "address-family nsap", + "bgp aggregate-timer 20", + "bgp dmzlink-bw", + "bgp scan-time 10", + "neighbor 198.51.100.1 remote-as 65100", + "neighbor 198.51.100.1 route-map test-route-out out", + "network 192.0.1.1 route-map test_route", + "default-metric 10", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_bgp_address_family_merged_2(self): + set_module_args( + dict( + config=dict( + as_number="65000", + address_family=[ + dict( + afi="ipv4", + safi="unicast", + vrf="blue", + neighbor=[ + dict( + address="192.0.3.1", + remote_as=65001, + soft_reconfiguration=True, + prefix_list=dict(name="PREFIX-OUT", out=True), + ), + ], + network=[dict(address="192.0.3.1", mask="255.255.255.0")], + ), + ], + ), + state="merged", + ), + ) + commands = [ + "router bgp 65000", + "address-family ipv4 unicast vrf blue", + "neighbor 192.0.3.1 remote-as 65001", + "neighbor 192.0.3.1 prefix-list PREFIX-OUT out", + "neighbor 192.0.3.1 soft-reconfiguration inbound", + "network 192.0.3.1 mask 255.255.255.0", + ] + + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_bgp_address_family_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp log-neighbor-changes + bgp nopeerup-delay cold-boot 20 + neighbor TEST-PEER-GROUP peer-group + neighbor 2001:db8::1 peer-group TEST-PEER-GROUP + neighbor 2001:db8::1 description TEST-PEER-GROUP-DESCRIPTION + ! + address-family ipv4 + bgp redistribute-internal + redistribute connected + redistribute ospf 200 metric 100 match internal external 1 external 2 + neighbor TEST-PEER-GROUP send-community + neighbor TEST-PEER-GROUP next-hop-self all + neighbor 2001:db8::1 activate + ! + address-family ipv4 multicast + table-map test_tableMap filter + network 198.51.111.11 mask 255.255.255.255 route-map test + aggregate-address 192.0.3.1 255.255.255.255 as-confed-set + default-metric 12 + distance bgp 10 10 100 + exit-address-family + ! + address-family ipv4 mdt + bgp dampening 1 10 100 5 + bgp dmzlink-bw + bgp soft-reconfig-backup + exit-address-family + ! + address-family ipv4 multicast vrf blue + bgp aggregate-timer 10 + bgp slow-peer detection threshold 150 + bgp dampening 1 1 1 1 + network 198.51.110.10 mask 255.255.255.255 backdoor + aggregate-address 192.0.2.1 255.255.255.255 as-confed-set + neighbor 198.51.100.1 remote-as 10 + neighbor 198.51.100.1 local-as 20 + neighbor 198.51.100.1 activate + neighbor 198.51.100.1 next-hop-self all + neighbor 198.51.100.1 aigp send cost-community 100 poi igp-cost transitive + neighbor 198.51.100.1 route-server-client + neighbor 198.51.100.1 prefix-list AS65100-PREFIX-OUT out + neighbor 198.51.100.1 slow-peer detection threshold 150 + neighbor 198.51.100.1 route-map test-out out + exit-address-family + """, + ) + set_module_args( + dict( + config={ + "address_family": [ + { + "afi": "ipv4", + "bgp": {"redistribute_internal": True}, + "neighbors": [ + { + "neighbor_address": "TEST-PEER-GROUP", + "nexthop_self": {"all": True}, + "send_community": {"set": True}, + }, + {"activate": True, "neighbor_address": "2001:db8::1"}, + ], + "redistribute": [{"connected": {"set": True}}], + }, + { + "afi": "ipv4", + "aggregate_addresses": [ + { + "address": "192.0.3.1", + "as_confed_set": True, + "netmask": "255.255.255.255", + }, + ], + "default_metric": 12, + "distance": {"external": 10, "internal": 10, "local": 100}, + "networks": [ + { + "address": "198.51.111.11", + "mask": "255.255.255.255", + "route_map": "test", + }, + ], + "safi": "multicast", + "table_map": {"filter": True, "name": "test_tableMap"}, + }, + { + "afi": "ipv4", + "bgp": { + "dampening": { + "max_suppress": 5, + "penalty_half_time": 1, + "reuse_route_val": 10, + "suppress_route_val": 100, + }, + "dmzlink_bw": True, + "soft_reconfig_backup": True, + }, + "safi": "mdt", + }, + { + "afi": "ipv4", + "aggregate_addresses": [ + { + "address": "192.0.2.1", + "as_confed_set": True, + "netmask": "255.255.255.255", + }, + ], + "bgp": { + "aggregate_timer": 10, + "dampening": { + "max_suppress": 1, + "penalty_half_time": 1, + "reuse_route_val": 1, + "suppress_route_val": 1, + }, + "slow_peer_options": {"detection": {"threshold": 150}}, + }, + "neighbors": [ + { + "activate": True, + "aigp": { + "send": { + "cost_community": { + "id": 100, + "poi": {"igp_cost": True, "transitive": True}, + }, + }, + }, + "local_as": {"number": 20, "set": True}, + "neighbor_address": "198.51.100.1", + "nexthop_self": {"all": True}, + "prefix_lists": [{"name": "AS65100-PREFIX-OUT", "out": True}], + "remote_as": 10, + "route_maps": [{"name": "test-out", "out": True}], + "route_server_client": True, + "slow_peer_options": {"detection": {"threshold": 150}}, + }, + ], + "networks": [ + { + "address": "198.51.110.10", + "backdoor": True, + "mask": "255.255.255.255", + }, + ], + "safi": "multicast", + "vrf": "blue", + }, + ], + "as_number": "65000", + }, + state="merged", + ), + ) + self.execute_module(changed=False) + + def test_ios_bgp_address_family_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp log-neighbor-changes + bgp nopeerup-delay cold-boot 20 + neighbor TEST-PEER-GROUP peer-group + neighbor 2001:db8::1 peer-group TEST-PEER-GROUP + neighbor 2001:db8::1 description TEST-PEER-GROUP-DESCRIPTION + ! + address-family ipv4 + bgp redistribute-internal + redistribute connected + redistribute ospf 200 metric 100 match internal external 1 external 2 + neighbor TEST-PEER-GROUP send-community + neighbor TEST-PEER-GROUP next-hop-self all + neighbor 2001:db8::1 activate + ! + address-family ipv4 multicast + table-map test_tableMap filter + network 198.51.111.11 mask 255.255.255.255 route-map test + aggregate-address 192.0.3.1 255.255.255.255 as-confed-set + default-metric 12 + distance bgp 10 10 100 + exit-address-family + ! + address-family ipv4 mdt + bgp dampening 1 10 100 5 + bgp dmzlink-bw + bgp soft-reconfig-backup + exit-address-family + ! + address-family ipv4 multicast vrf blue + bgp aggregate-timer 10 + bgp slow-peer detection threshold 150 + bgp dampening 1 1 1 1 + network 198.51.110.10 mask 255.255.255.255 backdoor + aggregate-address 192.0.2.1 255.255.255.255 as-confed-set + neighbor 198.51.100.1 remote-as 10 + neighbor 198.51.100.1 local-as 20 + neighbor 198.51.100.1 activate + neighbor 198.51.100.1 next-hop-self all + neighbor 198.51.100.1 aigp send cost-community 100 poi igp-cost transitive + neighbor 198.51.100.1 route-server-client + neighbor 198.51.100.1 prefix-list AS65100-PREFIX-OUT out + neighbor 198.51.100.1 slow-peer detection threshold 150 + neighbor 198.51.100.1 route-map test-out out + exit-address-family + """, + ) + + set_module_args( + dict( + config=dict( + as_number="65000", + address_family=[ + dict( + afi="ipv4", + safi="multicast", + vrf="blue", + aggregate_address=[ + dict( + address="192.0.2.1", + netmask="255.255.255.255", + as_confed_set=True, + ), + ], + bgp=dict( + aggregate_timer=10, + slow_peer=[dict(detection=dict(threshold=200))], + ), + redistribute=[dict(connected=dict(metric=10))], + neighbor=[ + dict( + address="198.51.110.1", + activate=True, + remote_as=200, + route_maps=[dict(name="test-replaced-route", out=True)], + ), + ], + ), + ], + ), + state="replaced", + ), + ) + commands = [ + "router bgp 65000", + "address-family ipv4 multicast vrf blue", + "no bgp dampening 1 1 1 1", + "bgp slow-peer detection threshold 200", + "redistribute connected metric 10", + "neighbor 198.51.110.1 activate", + "neighbor 198.51.110.1 remote-as 200", + "neighbor 198.51.110.1 route-map test-replaced-route out", + "no neighbor 198.51.100.1", + "no network 198.51.110.10 mask 255.255.255.255 backdoor", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_bgp_address_family_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp log-neighbor-changes + bgp nopeerup-delay cold-boot 20 + neighbor TEST-PEER-GROUP peer-group + neighbor 2001:db8::1 peer-group TEST-PEER-GROUP + neighbor 2001:db8::1 description TEST-PEER-GROUP-DESCRIPTION + ! + address-family ipv4 + bgp redistribute-internal + redistribute connected + redistribute ospf 200 metric 100 match internal external 1 external 2 + neighbor TEST-PEER-GROUP send-community + neighbor TEST-PEER-GROUP next-hop-self all + neighbor 2001:db8::1 activate + ! + address-family ipv4 multicast + table-map test_tableMap filter + network 198.51.111.11 mask 255.255.255.255 route-map test + aggregate-address 192.0.3.1 255.255.255.255 as-confed-set + default-metric 12 + distance bgp 10 10 100 + exit-address-family + ! + address-family ipv4 mdt + bgp dampening 1 10 100 5 + bgp dmzlink-bw + bgp soft-reconfig-backup + exit-address-family + ! + address-family ipv4 multicast vrf blue + bgp aggregate-timer 10 + bgp slow-peer detection threshold 150 + bgp dampening 1 1 1 1 + network 198.51.110.10 mask 255.255.255.255 backdoor + aggregate-address 192.0.2.1 255.255.255.255 as-confed-set + neighbor 198.51.100.1 remote-as 10 + neighbor 198.51.100.1 local-as 20 + neighbor 198.51.100.1 activate + neighbor 198.51.100.1 next-hop-self all + neighbor 198.51.100.1 aigp send cost-community 100 poi igp-cost transitive + neighbor 198.51.100.1 route-server-client + neighbor 198.51.100.1 prefix-list AS65100-PREFIX-OUT out + neighbor 198.51.100.1 slow-peer detection threshold 150 + neighbor 198.51.100.1 route-map test-out out + exit-address-family + """, + ) + set_module_args( + dict( + config=dict( + as_number="65000", + address_family=[ + dict( + afi="ipv4", + safi="multicast", + vrf="blue", + aggregate_address=[ + dict( + address="192.0.2.1", + netmask="255.255.255.255", + as_confed_set=True, + ), + ], + bgp=dict( + aggregate_timer=10, + dampening=dict( + penalty_half_time=1, + reuse_route_val=1, + suppress_route_val=1, + max_suppress=1, + ), + slow_peer=[dict(detection=dict(threshold=150))], + ), + neighbor=[ + dict( + activate=True, + address="198.51.100.1", + aigp=dict( + send=dict( + cost_community=dict( + id=100, + poi=dict(igp_cost=True, transitive=True), + ), + ), + ), + nexthop_self=dict(all=True), + prefix_lists=[dict(name="AS65100-PREFIX-OUT", out="true")], + slow_peer=[dict(detection=dict(threshold=150))], + remote_as=10, + local_as=dict(number=20), + route_maps=[dict(name="test-out", out=True)], + route_server_client=True, + ), + ], + network=[ + dict( + address="198.51.110.10", + mask="255.255.255.255", + backdoor=True, + ), + ], + ), + dict( + afi="ipv4", + safi="mdt", + bgp=dict( + dmzlink_bw=True, + dampening=dict( + penalty_half_time=1, + reuse_route_val=10, + suppress_route_val=100, + max_suppress=5, + ), + soft_reconfig_backup=True, + ), + ), + dict( + afi="ipv4", + safi="multicast", + aggregate_address=[ + dict( + address="192.0.3.1", + netmask="255.255.255.255", + as_confed_set=True, + ), + ], + default_metric=12, + distance=dict(external=10, internal=10, local=100), + network=[ + dict( + address="198.51.111.11", + mask="255.255.255.255", + route_map="test", + ), + ], + table_map=dict(name="test_tableMap", filter=True), + ), + ], + ), + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_bgp_address_family_overridden_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp log-neighbor-changes + bgp nopeerup-delay cold-boot 20 + neighbor TEST-PEER-GROUP peer-group + neighbor 2001:db8::1 peer-group TEST-PEER-GROUP + neighbor 2001:db8::1 description TEST-PEER-GROUP-DESCRIPTION + ! + address-family ipv4 + bgp redistribute-internal + redistribute connected + redistribute ospf 200 metric 100 match internal external 1 external 2 + neighbor TEST-PEER-GROUP send-community + neighbor TEST-PEER-GROUP next-hop-self all + neighbor 2001:db8::1 activate + ! + address-family ipv4 multicast + table-map test_tableMap filter + network 198.51.111.11 mask 255.255.255.255 route-map test + aggregate-address 192.0.3.1 255.255.255.255 as-confed-set + default-metric 12 + distance bgp 10 10 100 + exit-address-family + ! + address-family ipv4 mdt + bgp dampening 1 10 100 5 + bgp dmzlink-bw + bgp soft-reconfig-backup + exit-address-family + ! + address-family ipv4 multicast vrf blue + bgp aggregate-timer 10 + bgp slow-peer detection threshold 150 + bgp dampening 1 1 1 1 + network 198.51.110.10 mask 255.255.255.255 backdoor + aggregate-address 192.0.2.1 255.255.255.255 as-confed-set + neighbor 198.51.100.1 remote-as 10 + neighbor 198.51.100.1 local-as 20 + neighbor 198.51.100.1 activate + neighbor 198.51.100.1 next-hop-self all + neighbor 198.51.100.1 aigp send cost-community 100 poi igp-cost transitive + neighbor 198.51.100.1 route-server-client + neighbor 198.51.100.1 prefix-list AS65100-PREFIX-OUT out + neighbor 198.51.100.1 slow-peer detection threshold 150 + neighbor 198.51.100.1 route-map test-out out + exit-address-family + """, + ) + set_module_args( + dict( + config=dict( + as_number="65000", + address_family=[ + dict( + afi="ipv4", + bgp=dict(redistribute_internal=True), + redistribute=[ + dict(connected=dict(set=True)), + dict( + ospf=dict( + process_id=200, + metric=100, + match=dict( + internal=True, + externals=dict( + type_1=True, + type_2=True, + ), + ), + ), + ), + ], + neighbor=[ + dict( + tag="TEST-PEER-GROUP", + nexthop_self=dict(all=True), + send_community=dict(set=True), + ), + dict(ipv6_address="2001:db8::1", activate=True), + ], + ), + dict( + afi="ipv4", + safi="multicast", + vrf="blue", + aggregate_address=[ + dict( + address="192.0.2.1", + netmask="255.255.255.255", + as_confed_set=True, + ), + ], + bgp=dict( + aggregate_timer=10, + dampening=dict( + penalty_half_time=1, + reuse_route_val=1, + suppress_route_val=1, + max_suppress=1, + ), + slow_peer=[dict(detection=dict(threshold=150))], + ), + neighbor=[ + dict( + activate=True, + address="198.51.100.1", + aigp=dict( + send=dict( + cost_community=dict( + id=100, + poi=dict(igp_cost=True, transitive=True), + ), + ), + ), + nexthop_self=dict(all=True), + prefix_lists=[dict(name="AS65100-PREFIX-OUT", out="true")], + slow_peer=[dict(detection=dict(threshold=150))], + remote_as=10, + local_as=dict(number=20), + route_maps=[dict(name="test-out", out=True)], + route_server_client=True, + ), + ], + network=[ + dict( + address="198.51.110.10", + mask="255.255.255.255", + backdoor=True, + ), + ], + ), + dict( + afi="ipv4", + safi="mdt", + bgp=dict( + dmzlink_bw=True, + dampening=dict( + penalty_half_time=1, + reuse_route_val=10, + suppress_route_val=100, + max_suppress=5, + ), + soft_reconfig_backup=True, + ), + ), + dict( + afi="ipv4", + safi="multicast", + aggregate_address=[ + dict( + address="192.0.3.1", + netmask="255.255.255.255", + as_confed_set=True, + ), + ], + default_metric=12, + distance=dict(external=10, internal=10, local=100), + network=[ + dict( + address="198.51.111.11", + mask="255.255.255.255", + route_map="test", + ), + ], + table_map=dict(name="test_tableMap", filter=True), + ), + ], + ), + state="overridden", + ), + ) + + result = self.execute_module(changed=False, commands=[]) + + def test_ios_bgp_address_family_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp log-neighbor-changes + bgp nopeerup-delay cold-boot 20 + neighbor TEST-PEER-GROUP peer-group + neighbor 2001:db8::1 peer-group TEST-PEER-GROUP + neighbor 2001:db8::1 description TEST-PEER-GROUP-DESCRIPTION + ! + address-family ipv4 + bgp redistribute-internal + redistribute connected + redistribute ospf 200 metric 100 match internal external 1 external 2 + neighbor TEST-PEER-GROUP send-community + neighbor TEST-PEER-GROUP next-hop-self all + neighbor 2001:db8::1 activate + ! + address-family ipv4 multicast + table-map test_tableMap filter + network 198.51.111.11 mask 255.255.255.255 route-map test + aggregate-address 192.0.3.1 255.255.255.255 as-confed-set + default-metric 12 + distance bgp 10 10 100 + exit-address-family + ! + address-family ipv4 mdt + bgp dampening 1 10 100 5 + bgp dmzlink-bw + bgp soft-reconfig-backup + exit-address-family + ! + address-family ipv4 multicast vrf blue + bgp aggregate-timer 10 + bgp slow-peer detection threshold 150 + bgp dampening 1 1 1 1 + network 198.51.110.10 mask 255.255.255.255 backdoor + aggregate-address 192.0.2.1 255.255.255.255 as-confed-set + neighbor 198.51.100.1 remote-as 10 + neighbor 198.51.100.1 local-as 20 + neighbor 198.51.100.1 activate + neighbor 198.51.100.1 next-hop-self all + neighbor 198.51.100.1 aigp send cost-community 100 poi igp-cost transitive + neighbor 198.51.100.1 route-server-client + neighbor 198.51.100.1 prefix-list AS65100-PREFIX-OUT out + neighbor 198.51.100.1 slow-peer detection threshold 150 + neighbor 198.51.100.1 route-map test-out out + exit-address-family + """, + ) + set_module_args(dict(state="deleted")) + commands = [ + "router bgp 65000", + "no address-family ipv4", + "no address-family ipv4 multicast vrf blue", + "no address-family ipv4 mdt", + "no address-family ipv4 multicast", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_bgp_address_family_delete_without_config(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp log-neighbor-changes + bgp nopeerup-delay cold-boot 20 + neighbor TEST-PEER-GROUP peer-group + neighbor 2001:db8::1 peer-group TEST-PEER-GROUP + neighbor 2001:db8::1 description TEST-PEER-GROUP-DESCRIPTION + ! + address-family ipv4 + bgp redistribute-internal + redistribute connected + redistribute ospf 200 metric 100 match internal external 1 external 2 + neighbor TEST-PEER-GROUP send-community + neighbor TEST-PEER-GROUP next-hop-self all + neighbor 2001:db8::1 activate + ! + address-family ipv4 multicast + table-map test_tableMap filter + network 198.51.111.11 mask 255.255.255.255 route-map test + aggregate-address 192.0.3.1 255.255.255.255 as-confed-set + default-metric 12 + distance bgp 10 10 100 + exit-address-family + ! + address-family ipv4 mdt + bgp dampening 1 10 100 5 + bgp dmzlink-bw + bgp soft-reconfig-backup + exit-address-family + ! + address-family ipv4 multicast vrf blue + bgp aggregate-timer 10 + bgp slow-peer detection threshold 150 + bgp dampening 1 1 1 1 + network 198.51.110.10 mask 255.255.255.255 backdoor + aggregate-address 192.0.2.1 255.255.255.255 as-confed-set + neighbor 198.51.100.1 remote-as 10 + neighbor 198.51.100.1 local-as 20 + neighbor 198.51.100.1 activate + neighbor 198.51.100.1 next-hop-self all + neighbor 198.51.100.1 aigp send cost-community 100 poi igp-cost transitive + neighbor 198.51.100.1 route-server-client + neighbor 198.51.100.1 prefix-list AS65100-PREFIX-OUT out + neighbor 198.51.100.1 slow-peer detection threshold 150 + neighbor 198.51.100.1 route-map test-out out + exit-address-family + """, + ) + set_module_args( + dict( + config=dict( + as_number="65000", + address_family=[ + dict(afi="ipv4", safi="multicast"), + dict(afi="ipv4", safi="mdt"), + ], + ), + state="deleted", + ), + ) + commands = [ + "router bgp 65000", + "no address-family ipv4 mdt", + "no address-family ipv4 multicast", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_bgp_address_family_rendered(self): + set_module_args( + dict( + config=dict( + as_number="65000", + address_family=[ + dict( + afi="ipv4", + safi="multicast", + vrf="blue", + aggregate_address=[ + dict( + address="192.0.2.1", + netmask="255.255.255.255", + as_confed_set=True, + ), + ], + bgp=dict( + dampening=dict( + penalty_half_time=1, + reuse_route_val=1, + suppress_route_val=1, + max_suppress=1, + ), + ), + neighbor=[ + dict( + activate=True, + address="198.51.100.1", + aigp=dict( + send=dict( + cost_community=dict( + id=100, + poi=dict(igp_cost=True, transitive=True), + ), + ), + ), + slow_peer=[dict(detection=dict(threshold=150))], + remote_as=10, + route_maps=[dict(name="test-route", out=True)], + route_server_client=True, + ), + ], + network=[ + dict( + address="198.51.110.10", + mask="255.255.255.255", + backdoor=True, + ), + ], + ), + dict( + afi="ipv4", + safi="multicast", + aggregate_address=[ + dict( + address="192.0.3.1", + netmask="255.255.255.255", + as_confed_set=True, + ), + ], + default_metric=12, + distance=dict(external=10, internal=10, local=100), + network=[ + dict( + address="198.51.111.11", + mask="255.255.255.255", + route_map="test", + ), + ], + table_map=dict(name="test_tableMap", filter=True), + snmp=dict( + context=dict( + user=dict( + name="abc", + access=dict(ipv6="ipcal"), + credential=True, + encrypted=True, + ), + name="testsnmp", + ), + ), + ), + ], + ), + state="rendered", + ), + ) + commands = [ + "router bgp 65000", + "address-family ipv4 multicast vrf blue", + "bgp dampening 1 1 1 1", + "neighbor 198.51.100.1 remote-as 10", + "neighbor 198.51.100.1 activate", + "neighbor 198.51.100.1 aigp send cost-community 100 poi igp-cost transitive", + "neighbor 198.51.100.1 route-map test-route out", + "neighbor 198.51.100.1 route-server-client", + "neighbor 198.51.100.1 slow-peer detection threshold 150", + "network 198.51.110.10 mask 255.255.255.255 backdoor", + "aggregate-address 192.0.2.1 255.255.255.255 as-confed-set", + "address-family ipv4 multicast", + "network 198.51.111.11 mask 255.255.255.255 route-map test", + "aggregate-address 192.0.3.1 255.255.255.255 as-confed-set", + "default-metric 12", + "distance bgp 10 10 100", + "table-map test_tableMap filter", + "snmp context testsnmp user abc credential encrypted access ipv6 ipcal", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) + + def test_ios_bgp_address_family_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + router bgp 65000 + bgp log-neighbor-changes + bgp nopeerup-delay cold-boot 20 + neighbor TEST-PEER-GROUP peer-group + neighbor 2001:db8::1 peer-group TEST-PEER-GROUP + neighbor 2001:db8::1 description TEST-PEER-GROUP-DESCRIPTION + ! + address-family ipv4 + bgp redistribute-internal + redistribute connected + redistribute ospf 200 metric 100 match internal external 1 external 2 + neighbor TEST-PEER-GROUP send-community + neighbor TEST-PEER-GROUP next-hop-self all + neighbor 2001:db8::1 activate + ! + address-family ipv4 multicast + table-map test_tableMap filter + network 198.51.111.11 mask 255.255.255.255 route-map test + aggregate-address 192.0.3.1 255.255.255.255 as-confed-set + default-metric 12 + distance bgp 10 10 100 + exit-address-family + ! + address-family ipv4 mdt + bgp dampening 1 10 100 5 + bgp dmzlink-bw + bgp soft-reconfig-backup + exit-address-family + ! + address-family ipv4 multicast vrf blue + bgp aggregate-timer 10 + bgp slow-peer detection threshold 150 + bgp dampening 1 1 1 1 + network 198.51.110.10 mask 255.255.255.255 backdoor + aggregate-address 192.0.2.1 255.255.255.255 as-confed-set + neighbor 198.51.100.1 remote-as 10 + neighbor 198.51.100.1 local-as 20 + neighbor 198.51.100.1 activate + neighbor 198.51.100.1 next-hop-self all + neighbor 198.51.100.1 aigp send cost-community 100 poi igp-cost transitive + neighbor 198.51.100.1 route-server-client + neighbor 198.51.100.1 prefix-list AS65100-PREFIX-OUT out + neighbor 198.51.100.1 slow-peer detection threshold 150 + neighbor 198.51.100.1 route-map test-out out + exit-address-family + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = { + "as_number": "65000", + "address_family": [ + { + "afi": "ipv4", + "bgp": {"redistribute_internal": True}, + "redistribute": [ + {"connected": {"set": True}}, + { + "ospf": { + "process_id": 200, + "metric": 100, + "match": { + "internal": True, + "externals": { + "type_1": True, + "type_2": True, + }, + }, + }, + }, + ], + "neighbors": [ + { + "send_community": {"set": True}, + "nexthop_self": {"all": True}, + "neighbor_address": "TEST-PEER-GROUP", + }, + {"neighbor_address": "2001:db8::1", "activate": True}, + ], + }, + { + "afi": "ipv4", + "safi": "multicast", + "table_map": {"name": "test_tableMap", "filter": True}, + "networks": [ + { + "address": "198.51.111.11", + "mask": "255.255.255.255", + "route_map": "test", + }, + ], + "aggregate_addresses": [ + { + "address": "192.0.3.1", + "netmask": "255.255.255.255", + "as_confed_set": True, + }, + ], + "default_metric": 12, + "distance": {"external": 10, "internal": 10, "local": 100}, + }, + { + "afi": "ipv4", + "safi": "mdt", + "bgp": { + "dampening": { + "penalty_half_time": 1, + "reuse_route_val": 10, + "suppress_route_val": 100, + "max_suppress": 5, + }, + "dmzlink_bw": True, + "soft_reconfig_backup": True, + }, + }, + { + "afi": "ipv4", + "safi": "multicast", + "vrf": "blue", + "bgp": { + "aggregate_timer": 10, + "slow_peer_options": {"detection": {"threshold": 150}}, + "dampening": { + "penalty_half_time": 1, + "reuse_route_val": 1, + "suppress_route_val": 1, + "max_suppress": 1, + }, + }, + "networks": [ + {"address": "198.51.110.10", "mask": "255.255.255.255", "backdoor": True}, + ], + "aggregate_addresses": [ + { + "address": "192.0.2.1", + "netmask": "255.255.255.255", + "as_confed_set": True, + }, + ], + "neighbors": [ + { + "remote_as": 10, + "local_as": {"set": True, "number": 20}, + "activate": True, + "neighbor_address": "198.51.100.1", + "nexthop_self": {"all": True}, + "aigp": { + "send": { + "cost_community": { + "id": 100, + "poi": {"igp_cost": True, "transitive": True}, + }, + }, + }, + "route_server_client": True, + "prefix_lists": [{"name": "AS65100-PREFIX-OUT", "out": True}], + "slow_peer_options": {"detection": {"threshold": 150}}, + "route_maps": [{"name": "test-out", "out": True}], + }, + ], + }, + ], + } + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_bgp_address_family_merged_multiple_neighbor(self): + set_module_args( + dict( + config=dict( + as_number="65000", + address_family=[ + dict( + afi="ipv4", + neighbor=[ + dict( + address="192.31.39.212", + soft_reconfiguration=True, + activate=True, + ), + dict( + address="192.31.47.206", + soft_reconfiguration=True, + activate=True, + ), + ], + network=[ + dict(address="192.0.3.1", mask="255.255.255.0"), + dict(address="192.0.2.1", mask="255.255.255.0"), + dict(address="192.0.4.1", mask="255.255.255.0"), + ], + ), + ], + ), + state="merged", + ), + ) + commands = [ + "router bgp 65000", + "address-family ipv4", + "neighbor 192.31.39.212 activate", + "neighbor 192.31.39.212 soft-reconfiguration inbound", + "neighbor 192.31.47.206 activate", + "neighbor 192.31.47.206 soft-reconfiguration inbound", + "network 192.0.3.1 mask 255.255.255.0", + "network 192.0.2.1 mask 255.255.255.0", + "network 192.0.4.1 mask 255.255.255.0", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_bgp_address_family_overridden_multiple_neighbor(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp log-neighbor-changes + bgp nopeerup-delay cold-boot 20 + neighbor TEST-PEER-GROUP peer-group + neighbor 2001:db8::1 peer-group TEST-PEER-GROUP + neighbor 2001:db8::1 description TEST-PEER-GROUP-DESCRIPTION + ! + address-family ipv4 + bgp redistribute-internal + redistribute connected + redistribute ospf 200 metric 100 match internal external 1 external 2 + neighbor TEST-PEER-GROUP send-community + neighbor TEST-PEER-GROUP next-hop-self all + neighbor 2001:db8::1 activate + ! + address-family ipv4 multicast + table-map test_tableMap filter + network 198.51.111.11 mask 255.255.255.255 route-map test + aggregate-address 192.0.3.1 255.255.255.255 as-confed-set + default-metric 12 + distance bgp 10 10 100 + exit-address-family + ! + address-family ipv4 mdt + bgp dampening 1 10 100 5 + bgp dmzlink-bw + bgp soft-reconfig-backup + exit-address-family + ! + address-family ipv4 multicast vrf blue + bgp aggregate-timer 10 + bgp slow-peer detection threshold 150 + bgp dampening 1 1 1 1 + network 198.51.110.10 mask 255.255.255.255 backdoor + aggregate-address 192.0.2.1 255.255.255.255 as-confed-set + neighbor 198.51.100.1 remote-as 10 + neighbor 198.51.100.1 local-as 20 + neighbor 198.51.100.1 activate + neighbor 198.51.100.1 next-hop-self all + neighbor 198.51.100.1 aigp send cost-community 100 poi igp-cost transitive + neighbor 198.51.100.1 route-server-client + neighbor 198.51.100.1 prefix-list AS65100-PREFIX-OUT out + neighbor 198.51.100.1 slow-peer detection threshold 150 + neighbor 198.51.100.1 route-map test-out out + exit-address-family + """, + ) + set_module_args( + dict( + config=dict( + as_number="65000", + address_family=[ + dict( + afi="ipv4", + neighbor=[ + dict( + address="192.31.39.212", + soft_reconfiguration=True, + activate=True, + ), + dict( + address="192.31.47.206", + soft_reconfiguration=True, + activate=True, + ), + ], + network=[ + dict(address="192.0.3.1", mask="255.255.255.0"), + dict(address="192.0.2.1", mask="255.255.255.0"), + dict(address="192.0.4.1", mask="255.255.255.0"), + ], + ), + ], + ), + state="overridden", + ), + ) + commands = [ + "router bgp 65000", + "address-family ipv4 multicast", + "no default-metric 12", + "no distance bgp 10 10 100", + "no redistribute ospf 200", + "no table-map test_tableMap filter", + "no network 198.51.111.11 mask 255.255.255.255 route-map test", + "no aggregate-address 192.0.3.1 255.255.255.255 as-confed-set", + "address-family ipv4 mdt", + "no bgp dmzlink-bw", + "no bgp soft-reconfig-backup", + "no bgp dampening 1 10 100 5", + "address-family ipv4 multicast vrf blue", + "no bgp aggregate-timer 10", + "no bgp dampening 1 1 1 1", + "no bgp slow-peer detection threshold 150", + "no neighbor 198.51.100.1", + "no network 198.51.110.10 mask 255.255.255.255 backdoor", + "no aggregate-address 192.0.2.1 255.255.255.255 as-confed-set", + "address-family ipv4", + "no bgp redistribute-internal", + "no redistribute connected", + "neighbor 192.31.39.212 activate", + "neighbor 192.31.39.212 soft-reconfiguration inbound", + "neighbor 192.31.47.206 activate", + "neighbor 192.31.47.206 soft-reconfiguration inbound", + "no neighbor TEST-PEER-GROUP", + "no neighbor 2001:db8::1", + "network 192.0.3.1 mask 255.255.255.0", + "network 192.0.2.1 mask 255.255.255.0", + "network 192.0.4.1 mask 255.255.255.0", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp_global.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp_global.py new file mode 100644 index 000000000..35fe1dd2d --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp_global.py @@ -0,0 +1,736 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_bgp_global +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import AnsibleFailJson, set_module_args + +from .ios_module import TestIosModule + + +class TestIosBgpGlobalModule(TestIosModule): + module = ios_bgp_global + + def setUp(self): + super(TestIosBgpGlobalModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.bgp_global.bgp_global." + "Bgp_globalFacts.get_bgp_global_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosBgpGlobalModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_bgp_global_merged(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp nopeerup-delay post-boot 10 + bgp bestpath compare-routerid + bgp advertise-best-external + timers bgp 100 200 150 + redistribute connected metric 10 + neighbor 192.0.2.1 remote-as 100 + neighbor 192.0.2.1 route-map test-route out + address-family ipv4 + neighbor 192.0.2.28 activate + neighbor 172.31.35.140 activate + """, + ) + + set_module_args( + { + "config": { + "aggregate_addresses": [ + { + "address": "192.0.2.3", + "attribute_map": "ma", + "netmask": "255.255.0.0", + "summary_only": True, + }, + {"address": "192.0.2.4", "as_set": True, "netmask": "255.255.255.0"}, + {"address": "192.0.2.5", "as_set": True, "netmask": "255.255.255.0"}, + ], + "as_number": "65000", + "auto_summary": True, + "bgp": { + "additional_paths": {"send": True}, + "aggregate_timer": 0, + "always_compare_med": True, + "asnotation": True, + "bestpath_options": { + "aigp": True, + "compare_routerid": True, + "med": {"confed": True, "missing_as_worst": True}, + }, + "confederation": {"identifier": "22"}, + "consistency_checker": {"error_message": {"interval": 10, "set": True}}, + "dampening": { + "max_suppress": 44, + "penalty_half_time": 22, + "reuse_route_val": 22, + "suppress_route_val": 33, + }, + "deterministic_med": True, + "graceful_restart": {"restart_time": 2, "stalepath_time": 22}, + "graceful_shutdown": { + "community": "22", + "local_preference": 23, + "neighbors": {"time": 31}, + }, + "inject_maps": [ + {"copy_attributes": True, "exist_map_name": "mp2", "name": "map1"}, + {"copy_attributes": True, "exist_map_name": "mp3", "name": "map2"}, + ], + "listen": { + "limit": 200, + "range": {"host_with_subnet": "192.0.2.9/24", "peer_group": "mygrp"}, + }, + "log_neighbor_changes": True, + "maxas_limit": 2, + "maxcommunity_limit": 3, + "maxextcommunity_limit": 3, + "nexthop": {"route_map": "map1", "trigger": {"delay": 2}}, + "nopeerup_delay_options": { + "cold_boot": 2, + "nsf_switchover": 10, + "post_boot": 22, + "user_initiated": 22, + }, + "recursion": True, + "redistribute_internal": True, + "refresh": {"max_eor_time": 700, "stalepath_time": 800}, + "router_id": {"vrf": True}, + "scan_time": 22, + "slow_peer": { + "detection": {"threshold": 345}, + "split_update_group": {"dynamic": True, "permanent": True}, + }, + "sso": True, + "suppress_inactive": True, + "update_delay": 2, + "update_group": True, + }, + "bmp": {"buffer_size": 22}, + "distance": { + "bgp": {"routes_external": 2, "routes_internal": 3, "routes_local": 4}, + "mbgp": {"routes_external": 2, "routes_internal": 3, "routes_local": 5}, + }, + "distributes": [ + {"out": True, "prefix": "workcheck"}, + {"gateway": "checkme", "in": True}, + ], + "maximum_paths": {"ibgp": 22}, + "maximum_secondary_paths": {"ibgp": 22, "paths": 12}, + "neighbors": [ + { + "neighbor_address": "192.0.2.3", + "remote_as": "300", + "timers": { + "holdtime": 20, + "interval": 10, + }, + }, + { + "aigp": { + "send": { + "cost_community": { + "id": 100, + "poi": {"igp_cost": True, "transitive": True}, + }, + }, + }, + "neighbor_address": "192.0.2.4", + "remote_as": "100.1", + }, + ], + "redistribute": [ + {"application": {"metric": 22, "name": "ap1"}}, + {"application": {"metric": 33, "name": "ap112", "route_map": "mp1"}}, + {"connected": {"metric": 22}}, + {"static": {"metric": 33, "route_map": "mp1"}}, + {"mobile": {"metric": 211}}, + ], + }, + "state": "merged", + }, + ) + commands = [ + "router bgp 65000", + "auto-summary", + "bmp buffer-size 22", + "distance bgp 2 3 4", + "distance mbgp 2 3 5", + "maximum-paths ibgp 22", + "maximum-secondary-paths 12", + "maximum-secondary-paths ibgp 22", + "bgp additional-paths send", + "bgp aggregate-timer 0", + "bgp always-compare-med", + "bgp asnotation dot", + "bgp bestpath aigp ignore", + "bgp bestpath med confed missing-as-worst", + "bgp confederation identifier 22", + "bgp consistency-checker error-message interval 10", + "bgp dampening 22 22 33 44", + "bgp deterministic-med", + "bgp graceful-restart restart-time 2", + "bgp graceful-restart stalepath-time 22", + "bgp graceful-shutdown all neighbors 31 local-preference 23 community 22", + "bgp listen limit 200", + "bgp listen range 192.0.2.9/24 peer-group mygrp", + "bgp log-neighbor-changes", + "bgp maxas-limit 2", + "bgp maxcommunity-limit 3", + "bgp maxextcommunity-limit 3", + "bgp nexthop route-map map1", + "bgp nexthop trigger delay 2", + "bgp nopeerup-delay cold-boot 2", + "bgp nopeerup-delay post-boot 22", + "bgp nopeerup-delay nsf-switchover 10", + "bgp nopeerup-delay user-initiated 22", + "bgp recursion host", + "bgp redistribute-internal", + "bgp refresh max-eor-time 700", + "bgp refresh stalepath-time 800", + "bgp router-id vrf auto-assign", + "bgp scan-time 22", + "bgp slow-peer detection threshold 345", + "bgp slow-peer split-update-group dynamic permanent", + "bgp sso route-refresh-enable", + "bgp suppress-inactive", + "bgp update-delay 2", + "bgp update-group split as-override", + "bgp inject-map map1 exist-map mp2 copy-attributes", + "bgp inject-map map2 exist-map mp3 copy-attributes", + "distribute-list prefix workcheck out", + "distribute-list gateway checkme in", + "aggregate-address 192.0.2.3 255.255.0.0 summary-only attribute-map ma", + "aggregate-address 192.0.2.4 255.255.255.0 as-set", + "aggregate-address 192.0.2.5 255.255.255.0 as-set", + "neighbor 192.0.2.4 remote-as 100.1", + "neighbor 192.0.2.4 aigp send cost-community 100 poi igp-cost transitive", + "neighbor 192.0.2.3 remote-as 300", + "neighbor 192.0.2.3 timers 10 20", + "redistribute connected metric 22", + "redistribute mobile metric 211", + "redistribute application ap1 metric 22", + "redistribute static metric 33 route-map mp1", + "redistribute application ap112 metric 33 route-map mp1", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_bgp_global_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp nopeerup-delay post-boot 10 + bgp bestpath compare-routerid + bgp advertise-best-external + timers bgp 100 200 150 + redistribute connected metric 10 + neighbor 192.0.2.1 remote-as 100 + neighbor 192.0.2.1 route-map test-route out + address-family ipv4 + neighbor 192.0.2.28 activate + neighbor 172.31.35.140 activate + """, + ) + set_module_args( + dict( + config=dict( + as_number="65000", + bgp=dict( + advertise_best_external=True, + bestpath_options=dict(compare_routerid=True), + nopeerup_delay_options=dict(post_boot=10), + ), + redistribute=[dict(connected=dict(set=True, metric=10))], + neighbors=[ + dict( + address="192.0.2.1", + remote_as=100, + route_map=dict(name="test-route", out=True), + ), + ], + timers=dict(keepalive=100, holdtime=200, min_holdtime=150), + ), + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_bgp_global_merged_fail_msg(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp nopeerup-delay post-boot 10 + bgp bestpath compare-routerid + bgp advertise-best-external + timers bgp 100 200 150 + redistribute connected metric 10 + neighbor 192.0.2.1 remote-as 100 + neighbor 192.0.2.1 route-map test-route out + address-family ipv4 + neighbor 192.0.2.28 activate + neighbor 172.31.35.140 activate + """, + ) + set_module_args( + dict( + config=dict( + as_number="6500", + bgp=dict( + advertise_best_external=True, + bestpath_options=dict(compare_routerid=True), + nopeerup_delay_options=dict(post_boot=10), + ), + redistribute=[dict(connected=dict(set=True, metric=10))], + neighbors=[ + dict( + address="192.0.2.1", + remote_as=100, + route_map=dict(name="test-route", out=True), + ), + ], + timers=dict(keepalive=100, holdtime=200, min_holdtime=150), + ), + state="merged", + ), + ) + # self.assertEqual(sorted(result["commands"]), sorted(commands)) + with self.assertRaises(AnsibleFailJson) as error: + self.execute_module(changed=False, commands=[]) + self.assertIn( + "BGP is already configured with ASN 65000. Please remove it with state: purged before configuring new ASN", + str(error.exception), + ) + + def test_ios_bgp_global_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp nopeerup-delay post-boot 10 + bgp bestpath compare-routerid + bgp advertise-best-external + aggregate-address 192.168.0.11 255.255.0.0 attribute-map map1 + timers bgp 100 200 150 + redistribute connected metric 10 + neighbor 192.0.2.2 remote-as 100 + neighbor 192.0.2.2 route-map test-route out + address-family ipv4 + neighbor 192.0.2.28 activate + neighbor 172.31.35.140 activate + """, + ) + set_module_args( + dict( + config=dict( + as_number="65000", + aggregate_address=dict( + dict(address="192.168.0.11", attribute_map="map1", netmask="255.255.0.0"), + ), + aggregate_addresses=[ + dict(address="192.168.0.1", attribute_map="map", netmask="255.255.0.0"), + dict(address="192.168.0.2", attribute_map="map2", netmask="255.255.0.0"), + ], + bgp=dict( + advertise_best_external=True, + bestpath_options=dict(compare_routerid=True), + log_neighbor_changes=True, + nopeerup_delay_options=dict(cold_boot=20, post_boot=10), + ), + redistribute=[dict(connected=dict(set=True, metric=10))], + neighbors=[ + dict(address="192.0.2.1", remote_as=200, description="replace neighbor"), + ], + ), + state="replaced", + ), + ) + commands = [ + "router bgp 65000", + "no timers bgp 100 200 150", + "bgp log-neighbor-changes", + "bgp nopeerup-delay cold-boot 20", + "aggregate-address 192.168.0.1 255.255.0.0 attribute-map map", + "aggregate-address 192.168.0.2 255.255.0.0 attribute-map map2", + "neighbor 192.0.2.1 remote-as 200", + "neighbor 192.0.2.1 description replace neighbor", + "no neighbor 192.0.2.2", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_bgp_global_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp nopeerup-delay post-boot 10 + bgp bestpath compare-routerid + bgp advertise-best-external + timers bgp 100 200 150 + redistribute connected metric 10 + neighbor 192.0.2.1 remote-as 100 + neighbor 192.0.2.1 route-map test-route out + address-family ipv4 + neighbor 192.0.2.28 activate + neighbor 172.31.35.140 activate + """, + ) + set_module_args( + dict( + config=dict( + as_number="65000", + bgp=dict( + advertise_best_external=True, + bestpath_options=dict(compare_routerid=True), + nopeerup_delay_options=dict(post_boot=10), + ), + redistribute=[dict(connected=dict(set=True, metric=10))], + neighbors=[ + dict( + address="192.0.2.1", + remote_as=100, + route_map=dict(name="test-route", out=True), + ), + ], + timers=dict(keepalive=100, holdtime=200, min_holdtime=150), + ), + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_bgp_global_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp nopeerup-delay post-boot 10 + bgp bestpath compare-routerid + bgp advertise-best-external + timers bgp 100 200 150 + redistribute connected metric 10 + neighbor 192.0.2.1 remote-as 100 + neighbor 192.0.2.1 route-map test-route out + address-family ipv4 + neighbor 192.0.2.28 activate + neighbor 172.31.35.140 activate + """, + ) + set_module_args(dict(config=dict(as_number=65000), state="deleted")) + commands = [ + "router bgp 65000", + "no timers bgp 100 200 150", + "no bgp advertise-best-external", + "no bgp bestpath compare-routerid", + "no bgp nopeerup-delay post-boot 10", + "no neighbor 192.0.2.1", + "no redistribute connected", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_bgp_global_deleted_empty(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + set_module_args(dict(config=dict(as_number=65000), state="deleted")) + result = self.execute_module(changed=False) + self.assertEqual(result["commands"], []) + + def test_ios_bgp_global_purged(self): + self.execute_show_command.return_value = dedent( + """\ + router bgp 65000 + bgp nopeerup-delay post-boot 10 + bgp bestpath compare-routerid + bgp advertise-best-external + timers bgp 100 200 150 + redistribute connected metric 10 + neighbor 192.0.2.1 remote-as 100 + neighbor 192.0.2.1 route-map test-route out + address-family ipv4 + neighbor 192.0.2.28 activate + neighbor 172.31.35.140 activate + """, + ) + set_module_args(dict(config=dict(as_number=65000), state="purged")) + commands = ["no router bgp 65000"] + self.execute_module(changed=True, commands=commands) + + def test_deprecated_attributes_rendered(self): + set_module_args( + { + "config": { + "aggregate_address": { + "address": "192.0.2.3", + "attribute_map": "ma", + "netmask": "255.255.0.0", + "summary_only": True, + }, + "as_number": "65000", + "auto_summary": True, + "bgp": { + "additional_paths": {"install": True, "receive": True}, + "bestpath": [ + {"aigp": True}, + {"compare_routerid": True}, + {"med": {"confed": True, "missing_as_worst": True}}, + ], + "inject_map": { + "copy_attributes": True, + "exist_map_name": "mp2", + "name": "map1", + }, + "listen": { + "limit": 200, + "range": {"ipv4_with_subnet": "192.0.2.9/24", "peer_group": "mygrp"}, + }, + "log_neighbor_changes": True, + "nopeerup_delay": [ + { + "cold_boot": 2, + "nsf_switchover": 10, + "post_boot": 22, + "user_initiated": 22, + }, + ], + }, + "bmp": {"buffer_size": 22, "server": 2}, + "distance": { + "bgp": {"routes_external": 2, "routes_internal": 3, "routes_local": 4}, + "mbgp": {"routes_external": 2, "routes_internal": 3, "routes_local": 5}, + }, + "distribute_list": {"out": True, "acl": "5000", "interface": "Loopback0"}, + "maximum_paths": {"ibgp": 2, "paths": 2}, + "maximum_secondary_paths": {"ibgp": 22, "paths": 22}, + "neighbors": [ + { + "advertise": {"diverse_path": {"backup": True}}, + "neighbor_address": "192.1.1.1", + "route_reflector_client": True, + }, + { + "neighbor_address": "192.5.5.5", + "remote_as": 64500, + "update_source": "Loopback0", + "route_map": {"name": "rmp1", "in": True}, + }, + { + "neighbor_address": "192.6.6.6", + "remote_as": 64500, + "update_source": "Loop", + }, + { + "neighbor_address": "192.1.1.2", + "local_as": { + "no_prepend": {"replace_as": True, "set": True}, + "number": 56, + "set": True, + }, + }, + { + "activate": True, + "address": "192.0.1.2", + "remote_as": 45000, + "send_community": {"extended": True}, + "password": "new password", + }, + {"activate": True, "neighbor_address": "172.21.1.2", "remote_as": 45000}, + {"neighbor_address": "192.0.2.3", "remote_as": 300}, + { + "neighbor_address": "192.0.2.4", + "remote_as": 6553601, + "shutdown": {"set": True, "graceful": 10, "community": 20}, + }, + { + "activate": True, + "advertise": {"additional_paths": {"group_best": True}}, + "ipv6_adddress": "2001:DB8::1037", + }, + { + "tag": "tagged", + "peer_group": "5", + "soft_reconfiguration": True, + "version": 4, + }, + ], + "networks": [ + { + "address": "192.0.2.1", + "backdoor": True, + "netmask": "55.255.0.0", + "route_map": "mp1", + }, + { + "address": "192.0.2.3", + "backdoor": True, + "netmask": "255.255.0.0", + "route_map": "mp2", + }, + { + "address": "192.0.2.0", + "backdoor": True, + "netmask": "255.255.0.0", + "route_map": "mp2", + }, + ], + "redistribute": [ + {"static": {"metric": 33, "route_map": "mp1"}}, + {"application": {"metric": 22, "name": "ap1"}}, + {"application": {"metric": 33, "name": "ap112", "route_map": "mp1"}}, + {"connected": {"metric": 22}}, + {"mobile": {"metric": 211}}, + ], + "route_server_context": {"description": "good smn server BMP"}, + }, + "state": "rendered", + }, + ) + commands = [ + "router bgp 65000", + "auto-summary", + "bmp buffer-size 22", + "bmp server 2", + "distance bgp 2 3 4", + "distance mbgp 2 3 5", + "maximum-paths 2", + "maximum-paths ibgp 2", + "maximum-secondary-paths 22", + "maximum-secondary-paths ibgp 22", + "description good smn server BMP", + "bgp additional-paths install receive", + "bgp bestpath aigp ignore", + "bgp bestpath compare-routerid", + "bgp bestpath med confed missing-as-worst", + "bgp listen limit 200", + "bgp listen range 192.0.2.9/24 peer-group mygrp", + "bgp log-neighbor-changes", + "bgp nopeerup-delay cold-boot 2", + "bgp nopeerup-delay post-boot 22", + "bgp nopeerup-delay nsf-switchover 10", + "bgp nopeerup-delay user-initiated 22", + "bgp inject-map map1 exist-map mp2 copy-attributes", + "distribute-list 5000 out Loopback0", + "aggregate-address 192.0.2.3 255.255.0.0 summary-only attribute-map ma", + "network 192.0.2.1 mask 55.255.0.0 route-map mp1 backdoor", + "network 192.0.2.3 mask 255.255.0.0 route-map mp2 backdoor", + "network 192.0.2.0 mask 255.255.0.0 route-map mp2 backdoor", + "neighbor 192.1.1.1 advertise diverse-path backup", + "neighbor 192.1.1.1 route-reflector-client", + "neighbor 192.5.5.5 remote-as 64500", + "neighbor 192.5.5.5 update-source Loopback0", + "neighbor 192.5.5.5 route-map rmp1 in", + "neighbor 192.6.6.6 remote-as 64500", + "neighbor 192.6.6.6 update-source Loop", + "neighbor 192.1.1.2 local-as 56 no-prepend replace-as", + "neighbor 192.0.1.2 remote-as 45000", + "neighbor 192.0.1.2 password new password", + "neighbor 192.0.1.2 activate", + "neighbor 192.0.1.2 send-community extended", + "neighbor 172.21.1.2 remote-as 45000", + "neighbor 172.21.1.2 activate", + "neighbor 192.0.2.3 remote-as 300", + "neighbor 192.0.2.4 remote-as 6553601", + "neighbor 192.0.2.4 shutdown graceful 10 community 20", + "neighbor 2001:DB8::1037 activate", + "neighbor 2001:DB8::1037 advertise additional-paths group-best", + "neighbor tagged peer-group 5", + "neighbor tagged soft-reconfiguration inbound", + "neighbor tagged version 4", + "redistribute static metric 33 route-map mp1", + "redistribute application ap1 metric 22", + "redistribute application ap112 metric 33 route-map mp1", + "redistribute connected metric 22", + "redistribute mobile metric 211", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) + + def test_ios_bgp_global_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + router bgp 65000 + bgp nopeerup-delay post-boot 10 + bgp bestpath compare-routerid + bgp advertise-best-external + timers bgp 100 200 150 + redistribute connected metric 10 + neighbor 192.0.2.1 remote-as 100 + neighbor 192.0.2.1 route-map test-route out + address-family ipv4 + neighbor 192.0.2.28 activate + neighbor 172.31.35.140 activate + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = { + "as_number": "65000", + "bgp": { + "nopeerup_delay_options": {"post_boot": 10}, + "bestpath_options": {"compare_routerid": True}, + "advertise_best_external": True, + }, + "timers": {"keepalive": 100, "holdtime": 200, "min_holdtime": 150}, + "redistribute": [{"connected": {"set": True, "metric": 10}}], + "neighbors": [ + { + "remote_as": "100", + "neighbor_address": "192.0.2.1", + "route_maps": [{"name": "test-route", "out": True}], + }, + ], + } + self.assertEqual(parsed_list, result["parsed"]) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_command.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_command.py new file mode 100644 index 000000000..42fee4028 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_command.py @@ -0,0 +1,130 @@ +# (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 <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import json + +from ansible_collections.cisco.ios.plugins.modules import ios_command +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosCommandModule(TestIosModule): + module = ios_command + + def setUp(self): + super(TestIosCommandModule, self).setUp() + + self.mock_run_commands = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_command.run_commands", + ) + self.run_commands = self.mock_run_commands.start() + + def tearDown(self): + super(TestIosCommandModule, self).tearDown() + self.mock_run_commands.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + module, commands = args + output = list() + + for item in commands: + try: + obj = json.loads(item["command"]) + command = obj["command"] + except ValueError: + command = item["command"] + filename = str(command).replace(" ", "_") + output.append(load_fixture(filename)) + return output + + self.run_commands.side_effect = load_from_file + + def test_ios_command_simple(self): + set_module_args(dict(commands=["show version"])) + result = self.execute_module() + self.assertEqual(len(result["stdout"]), 1) + self.assertTrue(result["stdout"][0].startswith("Cisco IOS Software")) + + def test_ios_command_multiple(self): + set_module_args(dict(commands=["show version", "show version"])) + result = self.execute_module() + self.assertEqual(len(result["stdout"]), 2) + self.assertTrue(result["stdout"][0].startswith("Cisco IOS Software")) + + def test_ios_command_wait_for(self): + wait_for = 'result[0] contains "Cisco IOS"' + set_module_args(dict(commands=["show version"], wait_for=wait_for)) + self.execute_module() + + def test_ios_command_wait_for_fails(self): + wait_for = 'result[0] contains "test string"' + set_module_args(dict(commands=["show version"], wait_for=wait_for)) + self.execute_module(failed=True) + self.assertEqual(self.run_commands.call_count, 10) + + def test_ios_command_retries(self): + wait_for = 'result[0] contains "test string"' + set_module_args(dict(commands=["show version"], wait_for=wait_for, retries=2)) + self.execute_module(failed=True) + self.assertEqual(self.run_commands.call_count, 3) + + def test_ios_command_retries_0(self): + set_module_args(dict(commands=["show version"], retries=0)) + self.execute_module(failed=False) + self.assertEqual(self.run_commands.call_count, 1) + + def test_ios_command_match_any(self): + wait_for = ['result[0] contains "Cisco IOS"', 'result[0] contains "test string"'] + set_module_args(dict(commands=["show version"], wait_for=wait_for, match="any")) + self.execute_module() + + def test_ios_command_match_all(self): + wait_for = ['result[0] contains "Cisco IOS"', 'result[0] contains "IOSv Software"'] + set_module_args(dict(commands=["show version"], wait_for=wait_for, match="all")) + self.execute_module() + + def test_ios_command_match_all_failure(self): + wait_for = ['result[0] contains "Cisco IOS"', 'result[0] contains "test string"'] + commands = ["show version", "show version"] + set_module_args(dict(commands=commands, wait_for=wait_for, match="all")) + self.execute_module(failed=True) + + def test_ios_command_configure_check_warning(self): + commands = ["configure terminal"] + set_module_args({"commands": commands, "_ansible_check_mode": True}) + result = self.execute_module() + self.assertEqual( + result["warnings"], + [ + "Only show commands are supported when using check mode, not executing configure terminal", + ], + ) + + def test_ios_command_configure_not_warning(self): + commands = ["configure terminal"] + set_module_args(dict(commands=commands)) + result = self.execute_module() + self.assertEqual(result["warnings"], []) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_config.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_config.py new file mode 100644 index 000000000..fb8188b41 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_config.py @@ -0,0 +1,303 @@ +# +# (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 <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.cisco.ios.plugins.cliconf.ios import Cliconf +from ansible_collections.cisco.ios.plugins.modules import ios_config +from ansible_collections.cisco.ios.tests.unit.compat.mock import MagicMock, patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosConfigModule(TestIosModule): + module = ios_config + + def setUp(self): + super(TestIosConfigModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_get_connection = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_config.get_connection", + ) + self.get_connection = self.mock_get_connection.start() + + self.conn = self.get_connection() + self.conn.edit_config = MagicMock() + + self.mock_run_commands = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_config.run_commands", + ) + self.run_commands = self.mock_run_commands.start() + + self.cliconf_obj = Cliconf(MagicMock()) + self.running_config = load_fixture("ios_config_config.cfg") + + def tearDown(self): + super(TestIosConfigModule, self).tearDown() + self.mock_get_config.stop() + self.mock_run_commands.stop() + self.mock_get_connection.stop() + + def load_fixtures(self, commands=None): + config_file = "ios_config_config.cfg" + self.get_config.return_value = load_fixture(config_file) + self.get_connection.edit_config.return_value = None + + def test_ios_config_unchanged(self): + src = load_fixture("ios_config_config.cfg") + self.conn.get_diff = MagicMock(return_value=self.cliconf_obj.get_diff(src, src)) + set_module_args(dict(src=src)) + self.execute_module() + + def test_ios_config_src(self): + src = load_fixture("ios_config_src.cfg") + set_module_args(dict(src=src)) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff(src, self.running_config), + ) + commands = ["hostname foo", "interface GigabitEthernet0/0", "no ip address"] + self.execute_module(changed=True, commands=commands) + + def test_ios_config_backup(self): + set_module_args(dict(backup=True)) + result = self.execute_module() + self.assertIn("__backup__", result) + + def test_ios_config_save_changed_true(self): + src = load_fixture("ios_config_src.cfg") + set_module_args(dict(src=src, save_when="changed")) + commands = ["hostname foo", "interface GigabitEthernet0/0", "no ip address"] + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff(src, self.running_config), + ) + self.execute_module(changed=True, commands=commands) + self.assertEqual(self.run_commands.call_count, 1) + self.assertEqual(self.get_config.call_count, 1) + self.assertEqual(self.conn.edit_config.call_count, 1) + args = self.run_commands.call_args[0][1] + self.assertIn("copy running-config startup-config\r", args) + + def test_ios_config_save_changed_false(self): + set_module_args(dict(save_when="changed")) + self.execute_module(changed=False) + self.assertEqual(self.run_commands.call_count, 0) + self.assertEqual(self.get_config.call_count, 0) + self.assertEqual(self.conn.edit_config.call_count, 0) + + def test_ios_config_save_always(self): + self.run_commands.return_value = "hostname foo" + set_module_args(dict(save_when="always")) + self.execute_module(changed=True) + self.assertEqual(self.run_commands.call_count, 1) + self.assertEqual(self.get_config.call_count, 0) + self.assertEqual(self.conn.edit_config.call_count, 0) + args = self.run_commands.call_args[0][1] + self.assertIn("copy running-config startup-config\r", args) + + def test_ios_config_lines_wo_parents(self): + lines = ["hostname foo"] + set_module_args(dict(lines=lines)) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff("\n".join(lines), self.running_config), + ) + commands = ["hostname foo"] + self.execute_module(changed=True, commands=commands) + + def test_ios_config_lines_w_parents(self): + lines = ["shutdown"] + parents = ["interface GigabitEthernet0/0"] + set_module_args(dict(lines=lines, parents=parents)) + module = MagicMock() + module.params = {"lines": lines, "parents": parents, "src": None} + candidate_config = ios_config.get_candidate_config(module) + + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff(candidate_config, self.running_config), + ) + + commands = ["interface GigabitEthernet0/0", "shutdown"] + self.execute_module(changed=True, commands=commands) + + def test_ios_config_before(self): + lines = ["hostname foo"] + set_module_args(dict(lines=lines, before=["test1", "test2"])) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff("\n".join(lines), self.running_config), + ) + commands = ["test1", "test2", "hostname foo"] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_config_after(self): + lines = ["hostname foo"] + set_module_args(dict(lines=lines, after=["test1", "test2"])) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff("\n".join(lines), self.running_config), + ) + commands = ["hostname foo", "test1", "test2"] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_config_before_after_no_change(self): + lines = ["hostname router"] + set_module_args(dict(lines=lines, before=["test1", "test2"], after=["test3", "test4"])) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff("\n".join(lines), self.running_config), + ) + self.execute_module() + + def test_ios_config_config(self): + config = "hostname localhost" + lines = ["hostname router"] + set_module_args(dict(lines=lines, config=config)) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff("\n".join(lines), config), + ) + commands = ["hostname router"] + self.execute_module(changed=True, commands=commands) + + def test_ios_config_replace_block(self): + lines = ["description test string", "test string"] + parents = ["interface GigabitEthernet0/0"] + set_module_args(dict(lines=lines, replace="block", parents=parents)) + + module = MagicMock() + module.params = {"lines": lines, "parents": parents, "src": None} + candidate_config = ios_config.get_candidate_config(module) + + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff( + candidate_config, + self.running_config, + diff_replace="block", + path=parents, + ), + ) + + commands = parents + lines + self.execute_module(changed=True, commands=commands) + + def test_ios_config_match_none(self): + lines = ["hostname router"] + set_module_args(dict(lines=lines, match="none")) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff( + "\n".join(lines), + self.running_config, + diff_match="none", + ), + ) + self.execute_module(changed=True, commands=lines) + + def test_ios_config_match_none2(self): + lines = ["ip address 1.2.3.4 255.255.255.0", "description test string"] + parents = ["interface GigabitEthernet0/0"] + set_module_args(dict(lines=lines, parents=parents, match="none")) + + module = MagicMock() + module.params = {"lines": lines, "parents": parents, "src": None} + candidate_config = ios_config.get_candidate_config(module) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff( + candidate_config, + self.running_config, + diff_match="none", + path=parents, + ), + ) + + commands = parents + lines + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_config_match_strict(self): + lines = ["ip address 1.2.3.4 255.255.255.0", "description test string", "shutdown"] + parents = ["interface GigabitEthernet0/0"] + set_module_args(dict(lines=lines, parents=parents, match="strict")) + + module = MagicMock() + module.params = {"lines": lines, "parents": parents, "src": None} + candidate_config = ios_config.get_candidate_config(module) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff( + candidate_config, + self.running_config, + diff_match="strict", + path=parents, + ), + ) + + commands = parents + ["shutdown"] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_config_match_exact(self): + lines = ["ip address 1.2.3.4 255.255.255.0", "description test string", "shutdown"] + parents = ["interface GigabitEthernet0/0"] + set_module_args(dict(lines=lines, parents=parents, match="exact")) + + module = MagicMock() + module.params = {"lines": lines, "parents": parents, "src": None} + candidate_config = ios_config.get_candidate_config(module) + self.conn.get_diff = MagicMock( + return_value=self.cliconf_obj.get_diff( + candidate_config, + self.running_config, + diff_match="exact", + path=parents, + ), + ) + + commands = parents + lines + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_config_src_and_lines_fails(self): + args = dict(src="foo", lines="foo") + set_module_args(args) + self.execute_module(failed=True) + + def test_ios_config_src_and_parents_fails(self): + args = dict(src="foo", parents="foo") + set_module_args(args) + self.execute_module(failed=True) + + def test_ios_config_match_exact_requires_lines(self): + args = dict(match="exact") + set_module_args(args) + self.execute_module(failed=True) + + def test_ios_config_match_strict_requires_lines(self): + args = dict(match="strict") + set_module_args(args) + self.execute_module(failed=True) + + def test_ios_config_replace_block_requires_lines(self): + args = dict(replace="block") + set_module_args(args) + self.execute_module(failed=True) + + def test_ios_config_replace_config_requires_src(self): + args = dict(replace="config") + set_module_args(args) + self.execute_module(failed=True) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_facts.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_facts.py new file mode 100644 index 000000000..09dd1a899 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_facts.py @@ -0,0 +1,173 @@ +# +# 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 <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible.module_utils.six import assertCountEqual + +from ansible_collections.cisco.ios.plugins.modules import ios_facts +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosFactsModule(TestIosModule): + module = ios_facts + + def setUp(self): + super(TestIosFactsModule, self).setUp() + self.mock_run_commands = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.legacy.base.run_commands", + ) + self.run_commands = self.mock_run_commands.start() + + self.mock_get_resource_connection = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection", + ) + self.get_resource_connection = self.mock_get_resource_connection.start() + + self.mock_get_capabilities = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.legacy.base.get_capabilities", + ) + self.get_capabilities = self.mock_get_capabilities.start() + self.get_capabilities.return_value = { + "device_info": { + "network_os": "ios", + "network_os_hostname": "an-ios-01", + "network_os_image": "flash0:/vios-adventerprisek9-m", + "network_os_model": "WS-C3750-24TS", + "network_os_version": "15.6(3)M2", + }, + "network_api": "cliconf", + } + + def tearDown(self): + super(TestIosFactsModule, 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(" | ", 1)[0].replace(" ", "_") + output.append(load_fixture("ios_facts_%s" % filename)) + return output + + self.run_commands.side_effect = load_from_file + + def test_ios_facts_stacked(self): + set_module_args(dict(gather_subset="default")) + result = self.execute_module() + self.assertEqual(result["ansible_facts"]["ansible_net_model"], "WS-C3750-24TS") + self.assertEqual(result["ansible_facts"]["ansible_net_serialnum"], "CAT0726R0ZU") + self.assertEqual(result["ansible_facts"]["ansible_net_operatingmode"], "autonomous") + self.assertEqual( + result["ansible_facts"]["ansible_net_stacked_models"], + ["WS-C3750-24TS-E", "WS-C3750-24TS-E", "WS-C3750G-12S-E"], + ) + self.assertEqual( + result["ansible_facts"]["ansible_net_stacked_serialnums"], + ["CAT0726R0ZU", "CAT0726R10A", "CAT0732R0M4"], + ) + + def test_ios_facts_tunnel_address_and_lineprotocol(self): + set_module_args(dict(gather_subset="interfaces")) + result = self.execute_module() + self.assertEqual( + result["ansible_facts"]["ansible_net_interfaces"]["GigabitEthernet0/0"]["macaddress"], + "5e00.0003.0000", + ) + self.assertEqual( + result["ansible_facts"]["ansible_net_interfaces"]["GigabitEthernet1"]["macaddress"], + "5e00.0006.0000", + ) + self.assertIsNone( + result["ansible_facts"]["ansible_net_interfaces"]["Tunnel1110"]["macaddress"], + ) + self.assertEqual( + result["ansible_facts"]["ansible_net_interfaces"]["GigabitEthernet1"]["lineprotocol"], + "up", + ) + self.assertEqual( + result["ansible_facts"]["ansible_net_interfaces"]["TenGigabitEthernet2/5/5"][ + "lineprotocol" + ], + "down", + ) + + def test_ios_facts_filesystems_info(self): + set_module_args(dict(gather_subset="hardware")) + result = self.execute_module() + self.assertEqual( + result["ansible_facts"]["ansible_net_filesystems_info"]["bootflash:"]["spacetotal_kb"], + 7712692.0, + ) + self.assertEqual( + result["ansible_facts"]["ansible_net_filesystems_info"]["bootflash:"]["spacefree_kb"], + 6453180.0, + ) + + def test_ios_facts_memory_info(self): + set_module_args(dict(gather_subset="hardware")) + result = self.execute_module() + self.assertEqual( + result["ansible_facts"]["ansible_net_memfree_mb"], + 479095636 / (1024 * 1024), # 456.9012031555176, + ) + self.assertEqual( + result["ansible_facts"]["ansible_net_memtotal_mb"], + 732743660 / (1024 * 1024), # 698.7988090515137, + ) + + def test_ios_facts_neighbors(self): + set_module_args(dict(gather_subset="interfaces")) + result = self.execute_module() + assertCountEqual( + self, + result["ansible_facts"]["ansible_net_neighbors"].keys(), + ["GigabitEthernet1", "GigabitEthernet3"], + ) + assertCountEqual( + self, + result["ansible_facts"]["ansible_net_neighbors"]["GigabitEthernet1"], + [ + { + "platform": "cisco CSR1000V", + "host": "R2", + "port": "GigabitEthernet0/1", + "ip": "10.0.0.3", + }, + { + "platform": "cisco CSR1000V", + "host": "R3", + "port": "GigabitEthernet3", + "ip": "10.0.0.4", + }, + ], + ) + assertCountEqual( + self, + result["ansible_facts"]["ansible_net_neighbors"]["GigabitEthernet3"], + [{"host": "Rtest", "port": "Gi1", "ip": "10.3.0.3"}], + ) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_hostname.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_hostname.py new file mode 100644 index 000000000..858ad343d --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_hostname.py @@ -0,0 +1,121 @@ +# +# (c) 2022, 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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_hostname +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosHostnameModule(TestIosModule): + module = ios_hostname + + def setUp(self): + super(TestIosHostnameModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.hostname.hostname." + "HostnameFacts.get_hostname_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosHostnameModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_hostname_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + hostname testname + """, + ) + set_module_args(dict(config=dict(hostname="testname"), state="merged")) + commands = [] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_hostname_merged(self): + self.execute_show_command.return_value = dedent( + """\ + hostname testname + """, + ) + set_module_args(dict(config=dict(hostname="testnameNew"), state="merged")) + commands = ["hostname testnameNew"] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_hostname_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + hostname testname + """, + ) + set_module_args(dict(config=dict(), state="deleted")) + commands = ["no hostname testname"] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_hostname_deleted_blank(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + set_module_args(dict(config=dict(), state="deleted")) + commands = [] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_hostname_replaced_overridden(self): + """both the replaced and overridden states are supported to have same behaviour""" + self.execute_show_command.return_value = dedent( + """\ + hostname testname + """, + ) + set_module_args(dict(config=dict(hostname="testnameNew"), state="replaced")) + commands = ["hostname testnameNew"] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_interfaces.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_interfaces.py new file mode 100644 index 000000000..368067b4b --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_interfaces.py @@ -0,0 +1,785 @@ +# +# (c) 2022, 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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_interfaces +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosInterfacesModule(TestIosModule): + module = ios_interfaces + + def setUp(self): + super(TestIosInterfacesModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.interfaces.interfaces." + "InterfacesFacts.get_interfaces_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosInterfacesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_interfaces_merged(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet1 + description Ansible UT interface 1 + no shutdown + ip address dhcp + negotiation auto + interface GigabitEthernet0/1 + description Ansible UT interface 2 + ip address dhcp + speed 1000 + mtu 1500 + no negotiation auto + source template ANSIBLE + interface GigabitEthernet3 + description Ansible UT interface 3 + no ip address + shutdown + duplex auto + negotiation auto + channel-group 10 mode active + interface GigabitEthernet4 + description Ansible UT interface 4 + no ip address + shutdown + negotiation auto + source template ANSIBLE + interface GigabitEthernet5 + description Ansible UT interface 5 + no ip address + duplex full + negotiation auto + ipv6 dhcp server + interface GigabitEthernet6 + description Ansible UT interface 6 + source template NOCHANGE + """, + ) + set_module_args( + { + "config": [ + { + "description": "This interface should be disabled", + "enabled": True, + "name": "GigabitEthernet1", + "template": "ANSIBLE", + }, + { + "description": "This interface should be enabled", + "enabled": False, + "name": "GigabitEthernet0/1", + "template": "DISABLED", + }, + {"mode": "layer3", "name": "GigabitEthernet6"}, + ], + "state": "merged", + }, + ) + commands = [ + "interface GigabitEthernet0/1", + "description This interface should be enabled", + "source template DISABLED", + "shutdown", + "interface GigabitEthernet1", + "description This interface should be disabled", + "source template ANSIBLE", + "interface GigabitEthernet6", + "no switchport", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_interfaces_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet1 + description Ansible UT interface 1 + no shutdown + ip address dhcp + negotiation auto + interface GigabitEthernet0/1 + description Ansible UT interface 2 + ip address dhcp + speed 1000 + mtu 1500 + no negotiation auto + interface GigabitEthernet3 + description Ansible UT interface 3 + no ip address + shutdown + duplex auto + negotiation auto + channel-group 10 mode active + interface GigabitEthernet4 + description Ansible UT interface 4 + no ip address + shutdown + negotiation auto + interface GigabitEthernet5 + description Ansible UT interface 5 + no ip address + duplex full + negotiation auto + ipv6 dhcp server + """, + ) + set_module_args( + { + "config": [ + {"description": "Ansible UT interface 1", "name": "GigabitEthernet1"}, + { + "description": "Ansible UT interface 2", + "name": "GigabitEthernet0/1", + "speed": 1000, + "mtu": 1500, + }, + ], + "state": "merged", + }, + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_interfaces_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet1 + description Ansible UT interface 1 + no shutdown + ip address dhcp + negotiation auto + source template ANSIBLE + interface GigabitEthernet0/1 + description Ansible UT interface 2 + ip address dhcp + speed 1000 + mtu 1500 + no negotiation auto + interface GigabitEthernet3 + description Ansible UT interface 3 + no ip address + shutdown + duplex auto + negotiation auto + channel-group 10 mode active + interface GigabitEthernet4 + description Ansible UT interface 4 + no ip address + shutdown + negotiation auto + interface GigabitEthernet5 + description Ansible UT interface 5 + no ip address + duplex full + negotiation auto + ipv6 dhcp server + interface GigabitEthernet6 + description Ansible UT interface 6 + no switchport + """, + ) + set_module_args( + { + "config": [ + {"description": "Ansible UT interface 1", "name": "GigabitEthernet1"}, + {"name": "GigabitEthernet0/1", "speed": 1200, "mtu": 1800}, + { + "name": "GigabitEthernet6", + "description": "Ansible UT interface 6", + "mode": "layer2", + "template": "ANSIBLE", + }, + ], + "state": "replaced", + }, + ) + commands = [ + "interface GigabitEthernet1", + "no source template ANSIBLE", + "interface GigabitEthernet0/1", + "no description Ansible UT interface 2", + "speed 1200", + "mtu 1800", + "interface GigabitEthernet6", + "source template ANSIBLE", + "switchport", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_interfaces_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet1 + description Ansible UT interface 1 + no shutdown + ip address dhcp + negotiation auto + interface GigabitEthernet0/1 + description Ansible UT interface 2 + ip address dhcp + speed 1000 + mtu 1500 + no negotiation auto + interface GigabitEthernet3 + description Ansible UT interface 3 + no ip address + shutdown + duplex auto + negotiation auto + channel-group 10 mode active + interface GigabitEthernet4 + description Ansible UT interface 4 + no ip address + shutdown + negotiation auto + interface GigabitEthernet5 + description Ansible UT interface 5 + no ip address + duplex full + negotiation auto + ipv6 dhcp server + """, + ) + set_module_args( + { + "config": [ + {"description": "Ansible UT interface 1", "name": "GigabitEthernet1"}, + { + "description": "Ansible UT interface 2", + "name": "GigabitEthernet0/1", + "speed": 1000, + "mtu": 1500, + }, + ], + "state": "replaced", + }, + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_interfaces_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet1 + description Ansible UT interface 1 + no shutdown + ip address dhcp + negotiation auto + interface GigabitEthernet0/1 + description Ansible UT interface 2 + ip address dhcp + speed 1000 + mtu 1500 + no negotiation auto + interface GigabitEthernet3 + description Ansible UT interface 3 + no ip address + shutdown + duplex auto + negotiation auto + channel-group 10 mode active + interface GigabitEthernet4 + description Ansible UT interface 4 + no ip address + shutdown + negotiation auto + interface GigabitEthernet5 + description Ansible UT interface 5 + no ip address + duplex full + negotiation auto + ipv6 dhcp server + interface GigabitEthernet6 + description Ansible UT interface 6 + no switchport + source template ANSIBLE_INTERFACE_6 + """, + ) + set_module_args( + { + "config": [ + { + "description": "Ansible UT interface try 1", + "speed": 1000, + "name": "GigabitEthernet1", + }, + { + "description": "Ansible UT interface 2", + "name": "GigabitEthernet3", + "speed": 1000, + "mtu": 1500, + }, + ], + "state": "overridden", + }, + ) + + commands = [ + "interface GigabitEthernet0/1", + "no description Ansible UT interface 2", + "no speed 1000", + "no mtu 1500", + "shutdown", + "interface GigabitEthernet4", + "no description Ansible UT interface 4", + "interface GigabitEthernet5", + "no description Ansible UT interface 5", + "no duplex full", + "shutdown", + "interface GigabitEthernet6", + "no description Ansible UT interface 6", + "no source template ANSIBLE_INTERFACE_6", + "shutdown", + "switchport", + "interface GigabitEthernet1", + "description Ansible UT interface try 1", + "speed 1000", + "interface GigabitEthernet3", + "description Ansible UT interface 2", + "speed 1000", + "mtu 1500", + "no duplex auto", + "no shutdown", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_interfaces_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet1 + description Ansible UT interface 1 + no shutdown + ip address dhcp + negotiation auto + source template ANSIBLE + interface GigabitEthernet0/1 + description Ansible UT interface 2 + ip address dhcp + speed 1000 + mtu 1500 + no negotiation auto + interface GigabitEthernet3 + description Ansible UT interface 3 + no ip address + shutdown + duplex auto + negotiation auto + channel-group 10 mode active + interface GigabitEthernet4 + description Ansible UT interface 4 + no ip address + shutdown + negotiation auto + interface GigabitEthernet5 + description Ansible UT interface 5 + no ip address + duplex full + negotiation auto + ipv6 dhcp server + """, + ) + set_module_args(dict(config=[dict(name="GigabitEthernet1")], state="deleted")) + commands = [ + "interface GigabitEthernet1", + "no description Ansible UT interface 1", + "no source template ANSIBLE", + "shutdown", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_interfaces_purged(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet1 + description Ansible UT interface 1 + no shutdown + ip address dhcp + negotiation auto + interface GigabitEthernet0/1 + description Ansible UT interface 2 + ip address dhcp + speed 1000 + mtu 1500 + no negotiation auto + interface GigabitEthernet3 + description Ansible UT interface 3 + no ip address + shutdown + duplex auto + negotiation auto + channel-group 10 mode active + interface GigabitEthernet4 + description Ansible UT interface 4 + no ip address + shutdown + negotiation auto + interface GigabitEthernet5 + description Ansible UT interface 5 + no ip address + duplex full + negotiation auto + ipv6 dhcp server + """, + ) + set_module_args( + dict( + config=[ + { + "name": "GigabitEthernet0/1", + "description": "Ansible UT interface 2", + "speed": "1000", + "mtu": 1500, + "enabled": True, + }, + { + "name": "GigabitEthernet3", + "description": "Ansible UT interface 3", + "enabled": True, + "duplex": "auto", + }, + ], + state="purged", + ), + ) + commands = ["no interface GigabitEthernet0/1", "no interface GigabitEthernet3"] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_interfaces_purged_extreme(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet1 + description Ansible UT interface 1 + no shutdown + ip address dhcp + negotiation auto + interface GigabitEthernet0/1 + description Ansible UT interface 2 + ip address dhcp + speed 1000 + mtu 1500 + no negotiation auto + interface GigabitEthernet3 + description Ansible UT interface 3 + no ip address + shutdown + duplex auto + negotiation auto + channel-group 10 mode active + interface GigabitEthernet4 + description Ansible UT interface 4 + no ip address + shutdown + negotiation auto + interface GigabitEthernet5 + description Ansible UT interface 5 + no ip address + duplex full + negotiation auto + ipv6 dhcp server + """, + ) + set_module_args(dict(config=[], state="purged")) + commands = [ + "no interface GigabitEthernet0/1", + "no interface GigabitEthernet1", + "no interface GigabitEthernet3", + "no interface GigabitEthernet4", + "no interface GigabitEthernet5", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_interfaces_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + interface GigabitEthernet1 + description Ansible UT interface 1 + no shutdown + ip address dhcp + negotiation auto + interface GigabitEthernet0/1 + description Ansible UT interface 2 + ip address dhcp + speed 1000 + mtu 1500 + no negotiation auto + interface GigabitEthernet3 + description Ansible UT interface 3 + no ip address + shutdown + duplex auto + negotiation auto + channel-group 10 mode active + interface GigabitEthernet4 + description Ansible UT interface 4 + no ip address + shutdown + negotiation auto + interface GigabitEthernet5 + description Ansible UT interface 5 + no ip address + duplex full + negotiation auto + ipv6 dhcp server + interface GigabitEthernet6 + description Ansible UT interface 6 + switchport + interface GigabitEthernet7 + description Ansible UT interface 7 + no switchport + source template ANSIBLE + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [ + { + "name": "GigabitEthernet0/1", + "description": "Ansible UT interface 2", + "speed": "1000", + "mtu": 1500, + "enabled": True, + }, + {"name": "GigabitEthernet1", "description": "Ansible UT interface 1", "enabled": True}, + { + "name": "GigabitEthernet3", + "description": "Ansible UT interface 3", + "enabled": False, + "duplex": "auto", + }, + {"name": "GigabitEthernet4", "description": "Ansible UT interface 4", "enabled": False}, + { + "name": "GigabitEthernet5", + "description": "Ansible UT interface 5", + "duplex": "full", + "enabled": True, + }, + { + "name": "GigabitEthernet6", + "mode": "layer2", + "description": "Ansible UT interface 6", + "enabled": True, + }, + { + "name": "GigabitEthernet7", + "mode": "layer3", + "description": "Ansible UT interface 7", + "enabled": True, + "template": "ANSIBLE", + }, + ] + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_interfaces_rendered(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + set_module_args( + dict( + config=[ + { + "name": "GigabitEthernet1", + "description": "Ansible UT interface 1", + "enabled": False, + }, + { + "name": "GigabitEthernet0/1", + "description": "Ansible UT interface 2", + "speed": "1000", + "mtu": 1500, + "enabled": True, + }, + { + "name": "gigabitEthernet3", + "description": "Ansible UT interface 3", + "enabled": True, + "duplex": "auto", + }, + { + "name": "GigabitEthernet4", + "description": "Ansible UT interface 4", + "enabled": True, + }, + { + "name": "GigabitEthernet5", + "description": "Ansible UT interface 5", + "duplex": "full", + "enabled": True, + "template": "ANSIBLE", + }, + { + "name": "twentyFiveGigE1", + "description": "Ansible UT TwentyFiveGigE", + }, + { + "name": "twoGigabitEthernet2", + "description": "Ansible UT TwoGigabitEthernet", + }, + { + "name": "tenGigabitEthernet1", + "description": "Ansible UT TenGigabitEthernet", + }, + { + "name": "fastEthernet1", + "description": "Ansible UT FastEthernet", + }, + { + "name": "fortyGigabitEthernet1", + "description": "Ansible UT FortyGigabitEthernet", + }, + { + "name": "fiveGigabitEthernet", + "description": "Ansible UT FiveGigabitEthernet", + }, + { + "name": "ethernet1", + "description": "Ansible UT Ethernet", + }, + { + "name": "vlan10", + "description": "Ansible UT Vlan", + }, + { + "name": "loopback999", + "description": "Ansible UT loopback", + }, + { + "name": "port-channel01", + "description": "Ansible UT port-channel", + }, + { + "name": "nve1", + "description": "Ansible UT nve", + }, + { + "name": "hundredGigE1", + "description": "Ansible UT HundredGigE", + }, + { + "name": "serial0/1", + "description": "Ansible UT Serial", + }, + ], + state="rendered", + ), + ) + + commands = [ + "interface GigabitEthernet1", + "description Ansible UT interface 1", + "shutdown", + "interface GigabitEthernet0/1", + "description Ansible UT interface 2", + "speed 1000", + "mtu 1500", + "no shutdown", + "interface GigabitEthernet3", + "description Ansible UT interface 3", + "duplex auto", + "no shutdown", + "interface GigabitEthernet4", + "description Ansible UT interface 4", + "no shutdown", + "interface GigabitEthernet5", + "description Ansible UT interface 5", + "duplex full", + "source template ANSIBLE", + "no shutdown", + "interface TwentyFiveGigE1", + "description Ansible UT TwentyFiveGigE", + "no shutdown", + "interface TwoGigabitEthernet2", + "description Ansible UT TwoGigabitEthernet", + "no shutdown", + "interface TenGigabitEthernet1", + "description Ansible UT TenGigabitEthernet", + "no shutdown", + "interface FastEthernet1", + "description Ansible UT FastEthernet", + "no shutdown", + "interface FortyGigabitEthernet1", + "description Ansible UT FortyGigabitEthernet", + "no shutdown", + "interface FiveGigabitEthernet", + "description Ansible UT FiveGigabitEthernet", + "no shutdown", + "interface Ethernet1", + "description Ansible UT Ethernet", + "no shutdown", + "interface Vlan10", + "description Ansible UT Vlan", + "no shutdown", + "interface loopback999", + "description Ansible UT loopback", + "no shutdown", + "interface Port-channel01", + "description Ansible UT port-channel", + "no shutdown", + "interface nve1", + "description Ansible UT nve", + "no shutdown", + "interface HundredGigE1", + "description Ansible UT HundredGigE", + "no shutdown", + "interface Serial0/1", + "description Ansible UT Serial", + "no shutdown", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_l2_interfaces.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_l2_interfaces.py new file mode 100644 index 000000000..47bcced6e --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_l2_interfaces.py @@ -0,0 +1,787 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_l2_interfaces +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosL2InterfacesModule(TestIosModule): + module = ios_l2_interfaces + + def setUp(self): + super(TestIosL2InterfacesModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.l2_interfaces.l2_interfaces." + "L2_interfacesFacts.get_l2_interfaces_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosL2InterfacesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_l2_interfaces_merged(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict( + access=dict(vlan=20), + mode="access", + name="GigabitEthernet0/1", + voice=dict(vlan=40), + ), + dict( + mode="trunk", + name="GigabitEthernet0/2", + trunk=dict( + allowed_vlans=["60"], + encapsulation="isl", + native_vlan=20, + pruning_vlans=["9-15", "20"], + ), + ), + dict( + access=dict(vlan=20), + mode="access", + name="TwoGigabitEthernet1/0/1", + trunk=dict(pruning_vlans=["9-19", "20"]), + ), + ], + state="merged", + ), + ) + commands = [ + "interface GigabitEthernet0/1", + "switchport access vlan 20", + "switchport voice vlan 40", + "interface GigabitEthernet0/2", + "switchport trunk encapsulation isl", + "switchport trunk native vlan 20", + "switchport trunk allowed vlan add 60", + "switchport trunk pruning vlan add 9,11-15", + "interface TwoGigabitEthernet1/0/1", + "switchport trunk pruning vlan 9-20", + ] + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(result["commands"], commands) + + def test_ios_l2_interfaces_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict(access=dict(vlan=10), mode="access", name="GigabitEthernet0/1"), + dict( + mode="trunk", + name="GigabitEthernet0/2", + trunk=dict( + allowed_vlans=["10-20", "40"], + encapsulation="dot1q", + native_vlan=10, + pruning_vlans=["10", "20"], + ), + ), + dict( + mode="trunk", + name="GigabitEthernet0/3", + trunk=dict( + allowed_vlans=[ + "11", + "59", + "67", + "75", + "77", + "81", + "100", + "400-408", + "411-413", + "415", + "418", + "461", + "674", + "675", + "696", + "931", + "935", + "951", + "952", + "973", + "974", + "979", + "982", + "986", + "988", + "993", + ], + pruning_vlans=["10", "11", "12", "13", "14", "15"], + encapsulation="dot1q", + ), + ), + ], + state="merged", + ), + ) + self.maxDiff = None + self.execute_module(changed=False, commands=[]) + + def test_ios_l2_interfaces_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/2", + trunk=dict( + allowed_vlans=["20-25", "40"], + encapsulation="isl", + native_vlan=20, + pruning_vlans=["10"], + ), + ), + ], + state="replaced", + ), + ) + commands = [ + "interface GigabitEthernet0/2", + "no switchport mode", + "switchport trunk encapsulation isl", + "switchport trunk native vlan 20", + "switchport trunk allowed vlan remove 10-19", + "switchport trunk allowed vlan add 21-25", + "switchport trunk pruning vlan remove 20", + ] + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(result["commands"], commands) + + def test_ios_l2_interfaces_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict(access=dict(vlan=10), mode="access", name="GigabitEthernet0/1"), + dict( + mode="trunk", + name="GigabitEthernet0/2", + trunk=dict( + allowed_vlans=["10-20", "40"], + encapsulation="dot1q", + native_vlan=10, + pruning_vlans=["10", "20"], + ), + ), + dict(access=dict(vlan=20), mode="access", name="TwoGigabitEthernet1/0/1"), + dict( + mode="trunk", + name="GigabitEthernet0/3", + trunk=dict( + allowed_vlans=[ + "11", + "59", + "67", + "75", + "77", + "81", + "100", + "400-408", + "411-413", + "415", + "418", + "461", + "674", + "675", + "696", + "931", + "935", + "951", + "952", + "973", + "974", + "979", + "982", + "986", + "988", + "993", + ], + pruning_vlans=["10", "11", "12", "13", "14", "15"], + encapsulation="dot1q", + ), + ), + ], + state="replaced", + ), + ) + result = self.execute_module(changed=False) + commands = [] + self.maxDiff = None + self.assertEqual(result["commands"], commands) + + def test_ios_l2_interfaces_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict( + access=dict(vlan=10), + voice=dict(vlan=20), + mode="access", + name="GigabitEthernet0/2", + ), + ], + state="overridden", + ), + ) + commands = [ + "interface GigabitEthernet0/1", + "no switchport access vlan", + "no switchport mode", + "interface TwoGigabitEthernet1/0/1", + "no switchport access vlan", + "no switchport mode", + "interface GigabitEthernet0/3", + "no switchport mode", + "no switchport trunk encapsulation", + "no switchport trunk allowed vlan", + "no switchport trunk pruning vlan", + "interface GigabitEthernet0/2", + "switchport access vlan 10", + "switchport voice vlan 20", + "switchport mode access", + "no switchport trunk encapsulation", + "no switchport trunk native vlan", + "no switchport trunk allowed vlan", + "no switchport trunk pruning vlan", + ] + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(result["commands"], commands) + + def test_ios_l2_interfaces_overridden_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict(access=dict(vlan=10), mode="access", name="GigabitEthernet0/1"), + dict( + mode="trunk", + name="GigabitEthernet0/2", + trunk=dict( + allowed_vlans=["10-20", "40"], + encapsulation="dot1q", + native_vlan=10, + pruning_vlans=["10", "20"], + ), + ), + dict(access=dict(vlan=20), mode="access", name="TwoGigabitEthernet1/0/1"), + dict( + mode="trunk", + name="GigabitEthernet0/3", + trunk=dict( + allowed_vlans=[ + "11", + "59", + "67", + "75", + "77", + "81", + "100", + "400-408", + "411-413", + "415", + "418", + "461", + "674", + "675", + "696", + "931", + "935", + "951", + "952", + "973", + "974", + "979", + "982", + "986", + "988", + "993", + ], + pruning_vlans=["10", "11", "12", "13", "14", "15"], + encapsulation="dot1q", + ), + ), + ], + state="overridden", + ), + ) + self.maxDiff = None + self.execute_module(changed=False, commands=[]) + + def test_ios_l2_interfaces_deleted_interface(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk + """, + ) + set_module_args(dict(config=[dict(name="GigabitEthernet0/1")], state="deleted")) + commands = [ + "interface GigabitEthernet0/1", + "no switchport mode", + "no switchport access vlan", + ] + self.maxDiff = None + self.execute_module(changed=True, commands=commands) + + def test_ios_l2_interfaces_deleted_all(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk + """, + ) + set_module_args(dict(config=[], state="deleted")) + commands = [ + "interface GigabitEthernet0/1", + "no switchport access vlan", + "no switchport mode", + "interface GigabitEthernet0/2", + "no switchport mode", + "no switchport trunk encapsulation", + "no switchport trunk native vlan", + "no switchport trunk allowed vlan", + "no switchport trunk pruning vlan", + "interface TwoGigabitEthernet1/0/1", + "no switchport access vlan", + "no switchport mode", + "interface GigabitEthernet0/3", + "no switchport mode", + "no switchport trunk encapsulation", + "no switchport trunk allowed vlan", + "no switchport trunk pruning vlan", + ] + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(result["commands"], commands) + + def test_ios_l2_interfaces_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 1,2 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode private-vlan trunk secondary + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [ + {"name": "GigabitEthernet0/1", "mode": "access", "access": {"vlan": 10}}, + { + "name": "GigabitEthernet0/2", + "trunk": { + "allowed_vlans": ["10-20", "40"], + "encapsulation": "dot1q", + "native_vlan": 10, + "pruning_vlans": ["10", "20"], + }, + "mode": "trunk", + }, + {"name": "TwoGigabitEthernet1/0/1", "mode": "access", "access": {"vlan": 20}}, + { + "name": "GigabitEthernet0/3", + "trunk": { + "allowed_vlans": ["1", "2"], + "encapsulation": "dot1q", + "pruning_vlans": ["10-15"], + }, + "mode": "private_vlan_trunk", + }, + ] + self.maxDiff = None + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_l2_interfaces_rendered(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict( + access=dict(vlan=20), + mode="access", + name="GigabitEthernet0/1", + voice=dict(vlan=40), + ), + dict( + mode="trunk", + name="GigabitEthernet0/2", + trunk=dict( + allowed_vlans=["10-20", "40"], + encapsulation="isl", + native_vlan=20, + pruning_vlans=["12-15", "20"], + ), + ), + ], + state="rendered", + ), + ) + commands = [ + "interface GigabitEthernet0/1", + "switchport access vlan 20", + "switchport voice vlan 40", + "switchport mode access", + "interface GigabitEthernet0/2", + "switchport mode trunk", + "switchport trunk encapsulation isl", + "switchport trunk native vlan 20", + "switchport trunk allowed vlan 10-20,40", + "switchport trunk pruning vlan 12-15,20", + ] + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(result["rendered"], commands) + + def test_ios_l2_interfaces_merged_mode_change(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[dict(access=dict(vlan=20), mode="trunk", name="TwoGigabitEthernet1/0/1")], + state="merged", + ), + ) + commands = ["interface TwoGigabitEthernet1/0/1", "switchport mode trunk"] + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(result["commands"], commands) + + def test_ios_l2_interfaces_fiveGibBit(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + switchport mode access + switchport access vlan 10 + interface GigabitEthernet0/2 + switchport trunk allowed vlan 10-20,40 + switchport trunk encapsulation dot1q + switchport trunk native vlan 10 + switchport trunk pruning vlan 10,20 + switchport mode trunk + interface TwoGigabitEthernet1/0/1 + switchport mode access + switchport access vlan 20 + interface GigabitEthernet0/3 + switchport trunk allowed vlan 11,59,67,75,77,81,100,400-408,411-413,415,418 + switchport trunk allowed vlan add 461,674,675,696,931,935,951,952,973,974,979 + switchport trunk allowed vlan add 982,986,988,993 + switchport trunk encapsulation dot1q + switchport trunk pruning vlan 10-15 + switchport mode trunk + """, + ) + set_module_args( + dict( + config=[ + dict(access=dict(vlan=20), mode="trunk", name="FiveGigabitEthernet1/0/1"), + dict( + access=dict(vlan_name="vlan12"), + mode="trunk", + name="FiveGigabitEthernet1/0/2", + ), + dict( + voice=dict(vlan_tag="dot1p"), + mode="trunk", + name="FiveGigabitEthernet1/0/3", + ), + ], + state="merged", + ), + ) + commands = [ + "interface FiveGigabitEthernet1/0/3", + "switchport voice vlan dot1p", + "switchport mode trunk", + "interface FiveGigabitEthernet1/0/1", + "switchport access vlan 20", + "switchport mode trunk", + "interface FiveGigabitEthernet1/0/2", + "switchport access vlan name vlan12", + "switchport mode trunk", + ] + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_l3_interfaces.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_l3_interfaces.py new file mode 100644 index 000000000..e020f4fdd --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_l3_interfaces.py @@ -0,0 +1,473 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_l3_interfaces +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosL3InterfacesModule(TestIosModule): + module = ios_l3_interfaces + + def setUp(self): + super(TestIosL3InterfacesModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.l3_interfaces.l3_interfaces." + "L3_InterfacesFacts.get_l3_interfaces_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosL3InterfacesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_l3_interfaces_merged_common_ip(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/3.100 + encapsulation dot1Q 20 + ip address 192.168.0.3 255.255.255.0 + interface Serial3/0 + ipv6 address fd5d:12c9:2201:1::1/64 + interface Serial7/0 + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/3.100", + ipv4=[dict(address="192.168.0.3/24", secondary=True)], + ), + dict(name="Serial3/0", ipv6=[dict(address="FD5D:12C9:2201:1::1/64", cga=True)]), + ], + state="merged", + ), + ) + result = self.execute_module(changed=False) + self.assertEqual(result["commands"], []) + + def test_ios_l3_interfaces_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + description Configured by Ansible + duplex auto + speed auto + interface GigabitEthernet0/2 + description This is test + duplex auto + speed 1000 + interface GigabitEthernet0/3 + description Configured by Ansible Network + ipv6 address FD5D:12C9:2201:1::1/64 + interface GigabitEthernet0/3.100 + encapsulation dot1Q 20 + ip address 192.168.0.3 255.255.255.0 + interface Serial1/0 + description Configured by PAUL + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/1", + ipv4=[dict(address="192.168.0.1/24", secondary=True)], + ), + dict(name="GigabitEthernet0/2", ipv4=[dict(address="192.168.0.2/24")]), + dict(name="Serial1/0", ipv4=[dict(address="192.168.0.3/24")]), + ], + state="overridden", + ), + ) + commands = [ + "interface GigabitEthernet0/3", + "no ipv6 address fd5d:12c9:2201:1::1/64", + "interface GigabitEthernet0/3.100", + "no ip address 192.168.0.3 255.255.255.0", + "interface GigabitEthernet0/1", + "ip address 192.168.0.1 255.255.255.0 secondary", + "interface GigabitEthernet0/2", + "ip address 192.168.0.2 255.255.255.0", + "interface Serial1/0", + "ip address 192.168.0.3 255.255.255.0", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_l3_interfaces_deleted_all(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + description Configured by Ansible + duplex auto + speed auto + interface GigabitEthernet0/2 + description This is test + duplex auto + speed 1000 + interface GigabitEthernet0/3 + description Configured by Ansible Network + ipv6 address FD5D:12C9:2201:1::1/64 + interface GigabitEthernet0/3.100 + encapsulation dot1Q 20 + ip address 192.168.0.3 255.255.255.0 + interface Serial1/0 + description Configured by PAUL + """, + ) + set_module_args(dict(state="deleted")) + commands = [ + "interface GigabitEthernet0/3", + "no ipv6 address fd5d:12c9:2201:1::1/64", + "interface GigabitEthernet0/3.100", + "no ip address 192.168.0.3 255.255.255.0", + ] + + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_l3_interfaces_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + description Configured by Ansible + duplex auto + speed auto + interface GigabitEthernet0/2 + description This is test + duplex auto + speed 1000 + interface GigabitEthernet0/3 + description Configured by Ansible Network + ipv6 address FD5D:12C9:2201:1::1/64 + interface GigabitEthernet0/3.100 + encapsulation dot1Q 20 + ip address 192.168.0.3 255.255.255.0 + interface Serial1/0 + description Configured by PAUL + """, + ) + set_module_args( + dict( + config=[ + dict(name="GigabitEthernet0/3", ipv6=[dict(address="FD5D:12C9:2202:1::1/64")]), + dict( + name="GigabitEthernet0/2", + ipv4=[dict(address="192.168.0.2/24", secondary=False)], + ), + dict(name="Serial1/0", ipv4=[dict(address="192.168.0.5/24")]), + dict(name="GigabitEthernet0/3.100", ipv4=[dict(address="192.168.0.4/24")]), + ], + state="replaced", + ), + ) + commands = [ + "interface GigabitEthernet0/3", + "no ipv6 address fd5d:12c9:2201:1::1/64", + "ipv6 address fd5d:12c9:2202:1::1/64", + "interface GigabitEthernet0/2", + "ip address 192.168.0.2 255.255.255.0", + "interface Serial1/0", + "ip address 192.168.0.5 255.255.255.0", + "interface GigabitEthernet0/3.100", + "no ip address 192.168.0.3 255.255.255.0", + "ip address 192.168.0.4 255.255.255.0", + ] + + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_l3_interfaces_parsed(self): + set_module_args( + dict( + running_config="interface GigabitEthernet0/3.100\nencapsulation dot1Q 20\n ip address 192.168.0.3 255.255.255.0\n", + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [{"name": "GigabitEthernet0/3.100", "ipv4": [{"address": "192.168.0.3/24"}]}] + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_l3_interfaces_rendered(self): + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/3", + ipv4=[ + dict( + address="dhcp", + dhcp_client="GigabitEthernet0/3", + dhcp_hostname="abc.com", + ), + ], + ipv6=[dict(address="FD5D:12C9:2202:1::1/64")], + ), + dict( + name="GigabitEthernet0/2", + ipv4=[ + dict(address="192.168.0.2/24"), + dict( + dhcp=dict( + enable=False, + hostname="abc.com", + ), + ), + ], + ), + dict( + name="GigabitEthernet0/4", + ipv4=[dict(address="192.168.0.4/24", secondary=True)], + ), + dict(name="Serial1/0", ipv4=[dict(address="192.168.0.5/24")]), + ], + state="rendered", + ), + ) + commands = [ + "interface GigabitEthernet0/3", + "ip address dhcp client-id GigabitEthernet0/3 hostname abc.com", + "ipv6 address fd5d:12c9:2202:1::1/64", + "interface GigabitEthernet0/2", + "ip address 192.168.0.2 255.255.255.0", + "interface GigabitEthernet0/4", + "ip address 192.168.0.4 255.255.255.0 secondary", + "interface Serial1/0", + "ip address 192.168.0.5 255.255.255.0", + ] + + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) + + def test_ios_l3_interfaces_merged(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/1 + description Configured by Ansible + duplex auto + speed auto + interface GigabitEthernet0/2 + description This is test + duplex auto + speed 1000 + interface GigabitEthernet0/3 + description Configured by Ansible Network + ipv6 address FD5D:12C9:2201:1::1/64 + interface GigabitEthernet0/3.100 + encapsulation dot1Q 20 + ip address 192.168.0.3 255.255.255.0 + interface Serial1/0 + description Configured by PAUL + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/1", + ipv4=[dict(dhcp=dict(client_id="GigabitEthernet0/2", hostname="test.com"))], + ), + dict(name="GigabitEthernet0/2", ipv4=[dict(pool="PoolName1")]), + dict(name="Serial1/0", ipv6=[dict(autoconfig=dict(default=True))]), + dict(name="Serial2/0", ipv6=[dict(dhcp=dict(rapid_commit=True))]), + dict( + name="Serial3/0", + ipv6=[dict(address="FD5D:12C9:2201:1::1/64", anycast=True)], + ), + dict(name="Vlan51", ipv4=[dict(address="192.168.0.4/31")]), + dict(name="Serial4/0", ipv6=[dict(address="FD5D:12C9:2201:2::1/64", cga=True)]), + dict(name="Serial5/0", ipv6=[dict(address="FD5D:12C9:2201:3::1/64", eui=True)]), + dict( + name="Serial6/0", + ipv6=[dict(address="FD5D:12C9:2201:4::1/64", link_local=True)], + ), + dict( + name="Serial7/0", + ipv6=[ + dict( + address="FD5D:12C9:2201:5::1/64", + segment_routing=dict(ipv6_sr=True), + ), + ], + ), + dict( + name="Serial8/0", + ipv6=[ + dict( + address="FD5D:12C9:2201:6::1/64", + segment_routing=dict(default=True), + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "interface GigabitEthernet0/1", + "ip address dhcp client-id GigabitEthernet0/2 hostname test.com", + "interface GigabitEthernet0/2", + "ip address pool PoolName1", + "interface Serial1/0", + "ipv6 address autoconfig default", + "interface Serial2/0", + "ipv6 address dhcp rapid-commit", + "interface Serial3/0", + "ipv6 address fd5d:12c9:2201:1::1/64 anycast", + "interface Serial7/0", + "ipv6 address fd5d:12c9:2201:5::1/64 segment-routing ipv6-sr", + "interface Serial8/0", + "ipv6 address fd5d:12c9:2201:6::1/64 segment-routing default", + "interface Serial4/0", + "ipv6 address fd5d:12c9:2201:2::1/64 cga", + "interface Serial6/0", + "ipv6 address fd5d:12c9:2201:4::1/64 link-local", + "interface Serial5/0", + "ipv6 address fd5d:12c9:2201:3::1/64 eui", + "interface Vlan51", + "ip address 192.168.0.4 255.255.255.254", + ] + + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_l3_interfaces_idemp_merged(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/3.100 + encapsulation dot1Q 20 + ip address 192.168.0.3 255.255.255.0 + interface GigabitEthernet0/1 + ip address dhcp client-id GigabitEthernet0/2 hostname test.com + interface Serial2/0 + ipv6 address dhcp rapid-commit + interface Serial3/0 + ipv6 address fd5d:12c9:2201:1::1/64 anycast + interface Serial7/0 + ipv6 address fd5d:12c9:2201:5::1/64 segment-routing ipv6-sr + interface Serial8/0 + ipv6 address fd5d:12c9:2201:6::1/64 segment-routing default + interface Serial4/0 + ipv6 address fd5d:12c9:2201:2::1/64 cga + interface Serial6/0 + ipv6 address fd5d:12c9:2201:4::1/64 link-local + interface Serial5/0 + ipv6 address fd5d:12c9:2201:3::1/64 eui + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/1", + ipv4=[dict(dhcp=dict(client_id="GigabitEthernet0/2", hostname="test.com"))], + ), + dict(name="GigabitEthernet0/3.100", ipv4=[dict(address="192.168.0.3/24")]), + dict(name="Serial2/0", ipv6=[dict(dhcp=dict(rapid_commit=True))]), + dict( + name="Serial3/0", + ipv6=[dict(address="FD5D:12C9:2201:1::1/64", anycast=True)], + ), + dict(name="Serial4/0", ipv6=[dict(address="FD5D:12C9:2201:2::1/64", cga=True)]), + dict(name="Serial5/0", ipv6=[dict(address="FD5D:12C9:2201:3::1/64", eui=True)]), + dict( + name="Serial6/0", + ipv6=[dict(address="FD5D:12C9:2201:4::1/64", link_local=True)], + ), + dict( + name="Serial7/0", + ipv6=[ + dict( + address="FD5D:12C9:2201:5::1/64", + segment_routing=dict(ipv6_sr=True), + ), + ], + ), + dict( + name="Serial8/0", + ipv6=[ + dict( + address="FD5D:12C9:2201:6::1/64", + segment_routing=dict(default=True), + ), + ], + ), + ], + state="merged", + ), + ) + commands = [] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_l3_interfaces_remove_primary_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/3.100 + encapsulation dot1Q 20 + ip address 192.168.0.3 255.255.255.0 + ip address 192.168.1.3 255.255.255.0 secondary + """, + ) + set_module_args( + dict( + config=[ + dict(name="GigabitEthernet0/3.100", ipv4=[dict(address="192.168.1.3/24")]), + ], + state="replaced", + ), + ) + commands = [ + "interface GigabitEthernet0/3.100", + "ip address 192.168.1.3 255.255.255.0", + "no ip address 192.168.0.3 255.255.255.0", + "no ip address 192.168.1.3 255.255.255.0 secondary", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lacp.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lacp.py new file mode 100644 index 000000000..a5b46c916 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lacp.py @@ -0,0 +1,158 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_lacp +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosLacpModule(TestIosModule): + module = ios_lacp + + def setUp(self): + super(TestIosLacpModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.lacp.lacp." + "LacpFacts.get_lacp_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosLacpModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_lacp_merged(self): + self.execute_show_command.return_value = dedent( + """\ + 123, 5e00.0000.8000 + """, + ) + set_module_args( + dict( + config={"system": {"priority": 32768}}, + state="merged", + ), + ) + commands = ["lacp system-priority 32768"] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lacp_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + 123, 5e00.0000.8000 + """, + ) + set_module_args( + dict( + config={"system": {"priority": 123}}, + state="merged", + ), + ) + self.execute_module(changed=False) + + def test_ios_lacp_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + 123, 5e00.0000.8000 + """, + ) + set_module_args( + dict( + config={"system": {"priority": 12300}}, + state="replaced", + ), + ) + commands = ["lacp system-priority 12300"] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lacp_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + 123, 5e00.0000.8000 + """, + ) + set_module_args( + dict( + config={"system": {"priority": 1}}, + state="deleted", + ), + ) + commands = ["no lacp system-priority"] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lacp_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + 123, 5e00.0000.8000 + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = {"system": {"priority": 123}} + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_lacp_rendered(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + set_module_args( + dict( + config={"system": {"priority": 123}}, + state="rendered", + ), + ) + commands = ["lacp system-priority 123"] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lacp_interfaces.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lacp_interfaces.py new file mode 100644 index 000000000..2be548564 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lacp_interfaces.py @@ -0,0 +1,312 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_lacp_interfaces +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosLacpInterfaceModule(TestIosModule): + module = ios_lacp_interfaces + + def setUp(self): + super(TestIosLacpInterfaceModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.lacp_interfaces.lacp_interfaces." + "Lacp_InterfacesFacts.get_lacp_interface_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosLacpInterfaceModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + # lacp_interface + def test_ios_lacp_interface_merged(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel10 + lacp fast-switchover + lacp max-bundle 2 + interface Port-channel40 + lacp max-bundle 5 + interface GigabitEthernet0/0 + interface GigabitEthernet0/1 + lacp port-priority 30 + interface GigabitEthernet0/2 + lacp port-priority 20 + """, + ) + set_module_args( + dict( + config=[ + {"name": "Port-channel10", "fast_switchover": True, "max_bundle": 12}, + {"name": "Port-channel40", "max_bundle": 5}, + {"name": "GigabitEthernet0/0"}, + {"name": "GigabitEthernet0/1", "port_priority": 20}, + {"name": "GigabitEthernet0/2", "port_priority": 30}, + ], + state="merged", + ), + ) + commands = [ + "interface Port-channel10", + "lacp max-bundle 12", + "interface GigabitEthernet0/1", + "lacp port-priority 20", + "interface GigabitEthernet0/2", + "lacp port-priority 30", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lacp_interface_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel10 + lacp fast-switchover + lacp max-bundle 2 + interface Port-channel40 + lacp max-bundle 5 + interface GigabitEthernet0/0 + interface GigabitEthernet0/1 + lacp port-priority 30 + interface GigabitEthernet0/2 + lacp port-priority 20 + """, + ) + set_module_args( + dict( + config=[ + {"name": "Port-channel10", "fast_switchover": True, "max_bundle": 2}, + {"name": "Port-channel40", "max_bundle": 5}, + {"name": "GigabitEthernet0/0"}, + {"name": "GigabitEthernet0/1", "port_priority": 30}, + {"name": "GigabitEthernet0/2", "port_priority": 20}, + ], + state="merged", + ), + ) + self.execute_module(changed=False) + + def test_ios_lacp_interface_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel10 + lacp fast-switchover + lacp max-bundle 2 + interface Port-channel40 + lacp max-bundle 5 + interface GigabitEthernet0/0 + interface GigabitEthernet0/1 + lacp port-priority 30 + interface GigabitEthernet0/2 + lacp port-priority 20 + """, + ) + set_module_args( + dict( + config=[ + {"name": "Port-channel10", "fast_switchover": True, "max_bundle": 12}, + {"name": "Port-channel40", "max_bundle": 5}, + {"name": "GigabitEthernet0/0"}, + {"name": "GigabitEthernet0/1", "port_priority": 20}, + {"name": "GigabitEthernet0/2", "port_priority": 30}, + ], + state="replaced", + ), + ) + commands = [ + "interface Port-channel10", + "lacp max-bundle 12", + "interface GigabitEthernet0/1", + "lacp port-priority 20", + "interface GigabitEthernet0/2", + "lacp port-priority 30", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lacp_interface_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel10 + lacp fast-switchover + lacp max-bundle 2 + interface Port-channel40 + lacp max-bundle 5 + interface GigabitEthernet0/0 + interface GigabitEthernet0/1 + lacp port-priority 30 + interface GigabitEthernet0/2 + lacp port-priority 20 + """, + ) + set_module_args( + dict( + config=[ + {"name": "Port-channel10", "fast_switchover": True, "max_bundle": 12}, + {"name": "Port-channel40", "max_bundle": 5}, + {"name": "GigabitEthernet0/0"}, + {"name": "GigabitEthernet0/1", "port_priority": 20}, + {"name": "GigabitEthernet0/2", "port_priority": 30}, + ], + state="overridden", + ), + ) + commands = [ + "interface Port-channel10", + "lacp max-bundle 12", + "interface GigabitEthernet0/1", + "lacp port-priority 20", + "interface GigabitEthernet0/2", + "lacp port-priority 30", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lacp_interface_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel10 + lacp fast-switchover + lacp max-bundle 2 + interface Port-channel40 + lacp max-bundle 5 + interface GigabitEthernet0/0 + interface GigabitEthernet0/1 + lacp port-priority 30 + interface GigabitEthernet0/2 + lacp port-priority 20 + """, + ) + set_module_args( + dict( + config=[ + {"name": "Port-channel10", "fast_switchover": True, "max_bundle": 12}, + {"name": "Port-channel40", "max_bundle": 5}, + {"name": "GigabitEthernet0/0"}, + {"name": "GigabitEthernet0/1", "port_priority": 20}, + {"name": "GigabitEthernet0/2", "port_priority": 30}, + ], + state="deleted", + ), + ) + commands = [ + "interface Port-channel10", + "no lacp max-bundle", + "no lacp fast-switchover", + "interface Port-channel40", + "no lacp max-bundle", + "interface GigabitEthernet0/1", + "no lacp port-priority", + "interface GigabitEthernet0/2", + "no lacp port-priority", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lacp_interface_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + interface Port-channel10 + lacp fast-switchover + lacp max-bundle 2 + interface Port-channel40 + lacp max-bundle 5 + interface GigabitEthernet0/0 + interface GigabitEthernet0/1 + lacp port-priority 30 + interface GigabitEthernet0/2 + lacp port-priority 20 + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [ + {"name": "Port-channel10", "fast_switchover": True, "max_bundle": 2}, + {"name": "Port-channel40", "max_bundle": 5}, + {"name": "GigabitEthernet0/0"}, + {"name": "GigabitEthernet0/1", "port_priority": 30}, + {"name": "GigabitEthernet0/2", "port_priority": 20}, + ] + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_lacp_interface_rendered(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + set_module_args( + dict( + config=[ + {"fast_switchover": True, "max_bundle": 2, "name": "Port-channel10"}, + {"max_bundle": 5, "name": "Port-channel40"}, + {"name": "GigabitEthernet0/0"}, + {"name": "GigabitEthernet0/1", "port_priority": 30}, + {"name": "GigabitEthernet0/2", "port_priority": 20}, + ], + state="rendered", + ), + ) + commands = [ + "interface Port-channel10", + "lacp max-bundle 2", + "lacp fast-switchover", + "interface Port-channel40", + "lacp max-bundle 5", + "interface GigabitEthernet0/1", + "lacp port-priority 30", + "interface GigabitEthernet0/2", + "lacp port-priority 20", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lag_interfaces.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lag_interfaces.py new file mode 100644 index 000000000..74301348d --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lag_interfaces.py @@ -0,0 +1,509 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_lag_interfaces +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosLagInterfacesModule(TestIosModule): + module = ios_lag_interfaces + + def setUp(self): + super(TestIosLagInterfacesModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.lag_interfaces.lag_interfaces." + "Lag_interfacesFacts.get_lag_interfaces_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosLagInterfacesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_lag_interfaces_merged(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel11 + interface Port-channel22 + interface GigabitEthernet0/1 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/2 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/3 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/4 + shutdown + channel-group 22 mode active link 20 + interface GigabitEthernet0/5 + shutdown + channel-group 22 link 22 + """, + ) + set_module_args( + dict( + config=[ + { + "members": [{"member": "GigabitEthernet0/1", "mode": "active"}], + "name": "Port-channel11", + }, + { + "members": [ + {"member": "GigabitEthernet0/2", "mode": "active"}, + {"member": "GigabitEthernet0/3", "mode": "passive"}, + {"link": 20, "member": "GigabitEthernet0/4", "mode": "active"}, + {"link": 22, "member": "GigabitEthernet0/5"}, + ], + "name": "Port-channel22", + }, + ], + state="merged", + ), + ) + commands = ["interface GigabitEthernet0/3", "channel-group 22 mode passive"] + result = self.execute_module(changed=True) + # print(result["commands"]) + self.assertEqual(result["commands"], commands) + + def test_ios_lag_interfaces_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel11 + interface Port-channel22 + interface GigabitEthernet0/1 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/2 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/3 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/4 + shutdown + channel-group 22 mode active link 20 + interface GigabitEthernet0/5 + shutdown + channel-group 22 link 22 + """, + ) + set_module_args( + dict( + config=[ + { + "members": [{"member": "GigabitEthernet0/1", "mode": "active"}], + "name": "Port-channel11", + }, + { + "members": [ + {"member": "GigabitEthernet0/2", "mode": "active"}, + {"member": "GigabitEthernet0/3", "mode": "active"}, + {"link": 20, "member": "GigabitEthernet0/4", "mode": "active"}, + {"link": 22, "member": "GigabitEthernet0/5"}, + ], + "name": "Port-channel22", + }, + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_lag_interfaces_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel11 + interface Port-channel22 + interface GigabitEthernet0/1 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/2 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/3 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/4 + shutdown + channel-group 22 mode active link 20 + interface GigabitEthernet0/5 + shutdown + channel-group 22 link 22 + """, + ) + set_module_args( + dict( + config=[ + { + "members": [{"member": "GigabitEthernet0/3", "mode": "active"}], + "name": "Port-channel11", + }, + { + "members": [ + {"member": "GigabitEthernet0/1", "mode": "active"}, + {"member": "GigabitEthernet0/3", "mode": "on"}, + {"link": 20, "member": "GigabitEthernet0/4", "mode": "active"}, + {"link": 22, "member": "GigabitEthernet0/5"}, + ], + "name": "Port-channel22", + }, + ], + state="replaced", + ), + ) + commands = [ + "interface GigabitEthernet0/1", + "no channel-group 11 mode active", + "interface GigabitEthernet0/1", + "channel-group 22 mode active", + "interface GigabitEthernet0/3", + "channel-group 22 mode on", + "interface GigabitEthernet0/2", + "no channel-group 22 mode active", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lag_interfaces_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel11 + interface Port-channel22 + interface GigabitEthernet0/1 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/2 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/3 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/4 + shutdown + channel-group 22 mode active link 20 + interface GigabitEthernet0/5 + shutdown + channel-group 22 link 22 + """, + ) + set_module_args( + dict( + config=[ + { + "members": [{"member": "GigabitEthernet0/1", "mode": "active"}], + "name": "Port-channel11", + }, + { + "members": [ + {"member": "GigabitEthernet0/2", "mode": "active"}, + {"member": "GigabitEthernet0/3", "mode": "active"}, + {"link": 20, "member": "GigabitEthernet0/4", "mode": "active"}, + {"link": 22, "member": "GigabitEthernet0/5"}, + ], + "name": "Port-channel22", + }, + ], + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_lag_interfaces_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel11 + interface Port-channel22 + interface GigabitEthernet0/1 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/2 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/3 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/4 + shutdown + channel-group 22 mode active link 20 + interface GigabitEthernet0/5 + shutdown + channel-group 22 link 22 + """, + ) + set_module_args( + dict( + config=[ + { + "members": [ + {"member": "GigabitEthernet0/2", "mode": "active"}, + {"member": "GigabitEthernet0/3", "mode": "active"}, + {"link": 20, "member": "GigabitEthernet0/4", "mode": "active"}, + {"link": 22, "member": "GigabitEthernet0/5"}, + ], + "name": "Port-channel22", + }, + ], + state="overridden", + ), + ) + + commands = [ + "interface GigabitEthernet0/1", + "no channel-group 11 mode active", + "interface GigabitEthernet0/3", + "no channel-group 11 mode active", + "interface GigabitEthernet0/3", + "channel-group 22 mode active", + ] + result = self.execute_module(changed=True) + print(result["commands"]) + self.assertEqual(result["commands"], commands) + + def test_ios_lag_interfaces_overridden_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel11 + interface Port-channel22 + interface GigabitEthernet0/1 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/2 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/3 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/4 + shutdown + channel-group 22 mode active link 20 + interface GigabitEthernet0/5 + shutdown + channel-group 22 link 22 + """, + ) + set_module_args( + dict( + config=[ + { + "members": [{"member": "GigabitEthernet0/1", "mode": "active"}], + "name": "Port-channel11", + }, + { + "members": [ + {"member": "GigabitEthernet0/2", "mode": "active"}, + {"member": "GigabitEthernet0/3", "mode": "active"}, + {"link": 20, "member": "GigabitEthernet0/4", "mode": "active"}, + {"link": 22, "member": "GigabitEthernet0/5"}, + ], + "name": "Port-channel22", + }, + ], + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_lag_interfaces_deleted_interface(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel11 + interface Port-channel22 + interface GigabitEthernet0/1 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/2 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/3 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/4 + shutdown + channel-group 22 mode active link 20 + interface GigabitEthernet0/5 + shutdown + channel-group 22 link 22 + """, + ) + set_module_args(dict(config=[], state="deleted")) + commands = [ + "interface GigabitEthernet0/1", + "no channel-group 11 mode active", + "interface GigabitEthernet0/3", + "no channel-group 11 mode active", + "interface GigabitEthernet0/2", + "no channel-group 22 mode active", + "interface GigabitEthernet0/4", + "no channel-group 22 mode active link 20", + "interface GigabitEthernet0/5", + "no channel-group 22 link 22", + ] + result = self.execute_module(changed=True) + print(result["commands"]) + self.assertEqual(result["commands"], commands) + + def test_ios_lag_interfaces_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + interface Port-channel11 + interface Port-channel22 + interface GigabitEthernet0/1 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/2 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/3 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/4 + shutdown + channel-group 22 mode active link 20 + interface GigabitEthernet0/5 + shutdown + channel-group 22 link 22 + """, + ) + set_module_args(dict(config=[dict(name="Port-channel11")], state="deleted")) + commands = [ + "interface GigabitEthernet0/1", + "no channel-group 11 mode active", + "interface GigabitEthernet0/3", + "no channel-group 11 mode active", + ] + res = self.execute_module(changed=True) + self.assertEqual(res["commands"], commands) + + def test_ios_lag_interfaces_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + interface Port-channel11 + interface Port-channel22 + interface GigabitEthernet0/1 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/2 + shutdown + channel-group 22 mode active + interface GigabitEthernet0/3 + shutdown + channel-group 11 mode active + interface GigabitEthernet0/4 + shutdown + channel-group 22 mode active link 20 + interface GigabitEthernet0/5 + shutdown + channel-group 22 link 22 + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [ + { + "name": "Port-channel11", + "members": [ + {"member": "GigabitEthernet0/1", "mode": "active"}, + {"member": "GigabitEthernet0/3", "mode": "active"}, + ], + }, + { + "name": "Port-channel22", + "members": [ + {"member": "GigabitEthernet0/2", "mode": "active"}, + {"member": "GigabitEthernet0/4", "link": 20, "mode": "active"}, + {"member": "GigabitEthernet0/5", "link": 22}, + ], + }, + ] + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_lag_interfaces_rendered(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + set_module_args( + dict( + config=[ + { + "members": [{"member": "GigabitEthernet0/1", "mode": "active"}], + "name": "Port-channel11", + }, + { + "members": [ + {"member": "GigabitEthernet0/2", "mode": "active"}, + {"member": "GigabitEthernet0/3", "mode": "active"}, + {"link": 20, "member": "GigabitEthernet0/4", "mode": "active"}, + {"link": 22, "member": "GigabitEthernet0/5"}, + ], + "name": "Port-channel22", + }, + ], + state="rendered", + ), + ) + commands = [ + "interface GigabitEthernet0/1", + "channel-group 11 mode active", + "interface GigabitEthernet0/2", + "channel-group 22 mode active", + "interface GigabitEthernet0/3", + "channel-group 22 mode active", + "interface GigabitEthernet0/4", + "channel-group 22 mode active link 20", + "interface GigabitEthernet0/5", + "channel-group 22 link 22", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lldp_global.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lldp_global.py new file mode 100644 index 000000000..dd67f0608 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lldp_global.py @@ -0,0 +1,173 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_lldp_global +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosLldpGlobalModule(TestIosModule): + module = ios_lldp_global + + def setUp(self): + super(TestIosLldpGlobalModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.lldp_global.lldp_global." + "Lldp_globalFacts.get_lldp_global_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosLldpGlobalModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_lldp_global_merged(self): + self.execute_show_command.return_value = dedent( + """\ + lldp timer 10 + lldp holdtime 10 + lldp reinit 3 + lldp run + """, + ) + set_module_args( + dict( + config={"timer": 20, "holdtime": 10, "reinit": 3, "enabled": True}, + state="merged", + ), + ) + commands = ["lldp timer 20"] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lldp_global_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + lldp timer 10 + lldp holdtime 10 + lldp reinit 3 + lldp run + """, + ) + set_module_args( + dict( + config={"timer": 10, "holdtime": 10, "reinit": 3, "enabled": True}, + state="merged", + ), + ) + self.execute_module(changed=False) + + def test_ios_lldp_global_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + lldp timer 10 + lldp holdtime 10 + lldp reinit 3 + lldp run + """, + ) + set_module_args( + dict( + config={"timer": 15, "reinit": 9}, + state="replaced", + ), + ) + commands = ["no lldp holdtime", "no lldp run", "lldp timer 15", "lldp reinit 9"] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lldp_global_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + lldp timer 10 + lldp holdtime 10 + lldp reinit 3 + lldp run + """, + ) + set_module_args( + dict( + config={"timer": 15, "reinit": 9}, + state="deleted", + ), + ) + commands = ["no lldp holdtime", "no lldp run", "no lldp timer", "no lldp reinit"] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lldp_global_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + lldp timer 10 + lldp holdtime 10 + lldp reinit 3 + lldp run + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = {"timer": 10, "holdtime": 10, "reinit": 3, "enabled": True} + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_lldp_global_rendered(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + set_module_args( + dict( + config={"timer": 10, "holdtime": 10, "reinit": 3, "enabled": True}, + state="rendered", + ), + ) + commands = ["lldp holdtime 10", "lldp run", "lldp timer 10", "lldp reinit 3"] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lldp_inteface.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lldp_inteface.py new file mode 100644 index 000000000..a3dfe9706 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lldp_inteface.py @@ -0,0 +1,318 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_lldp_interfaces +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosLldpInterfacesModule(TestIosModule): + module = ios_lldp_interfaces + + def setUp(self): + super(TestIosLldpInterfacesModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.lldp_interfaces.lldp_interfaces." + "Lldp_InterfacesFacts.get_lldp_interfaces_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosLldpInterfacesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_lldp_interfaces_merged(self): + self.execute_show_command.return_value = dedent( + """\ + GigabitEthernet0/0: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + + GigabitEthernet0/1: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + + GigabitEthernet0/2: + Tx: disabled + Rx: disabled + Tx state: IDLE + Rx state: INIT + + GigabitEthernet0/3: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + """, + ) + set_module_args( + dict( + config=[ + {"name": "GigabitEthernet0/1", "receive": True, "transmit": True}, + {"name": "GigabitEthernet0/2", "receive": True}, + {"name": "GigabitEthernet0/3", "transmit": True}, + ], + state="merged", + ), + ) + commands = ["interface GigabitEthernet0/2", "lldp receive"] + result = self.execute_module(changed=True) + # print(result["commands"]) + self.assertEqual(result["commands"], commands) + + def test_ios_lldp_interfaces_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + GigabitEthernet0/0: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + + GigabitEthernet0/1: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + + GigabitEthernet0/2: + Tx: disabled + Rx: disabled + Tx state: IDLE + Rx state: INIT + + GigabitEthernet0/3: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + """, + ) + set_module_args( + dict( + config=[ + {"name": "GigabitEthernet0/2", "receive": True, "transmit": True}, + {"name": "GigabitEthernet0/3", "receive": False}, + ], + state="replaced", + ), + ) + commands = [ + "interface GigabitEthernet0/2", + "lldp receive", + "lldp transmit", + "interface GigabitEthernet0/3", + "no lldp transmit", + "no lldp receive", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_lag_interfaces_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + GigabitEthernet0/0: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + + GigabitEthernet0/1: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + + GigabitEthernet0/2: + Tx: disabled + Rx: disabled + Tx state: IDLE + Rx state: INIT + + GigabitEthernet0/3: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + """, + ) + set_module_args( + dict( + config=[ + {"name": "GigabitEthernet0/2", "receive": True, "transmit": True}, + ], + state="overridden", + ), + ) + + commands = [ + "interface GigabitEthernet0/0", + "no lldp receive", + "no lldp transmit", + "interface GigabitEthernet0/1", + "no lldp receive", + "no lldp transmit", + "interface GigabitEthernet0/2", + "lldp receive", + "lldp transmit", + "interface GigabitEthernet0/3", + "no lldp receive", + "no lldp transmit", + ] + result = self.execute_module(changed=True) + print(result["commands"]) + self.assertEqual(result["commands"], commands) + + def test_ios_lldp_interfaces_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + GigabitEthernet0/0: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + + GigabitEthernet0/1: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + + GigabitEthernet0/2: + Tx: disabled + Rx: disabled + Tx state: IDLE + Rx state: INIT + + GigabitEthernet0/3: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + """, + ) + set_module_args( + dict( + config=[dict(name="GigabitEthernet0/2"), dict(name="GigabitEthernet0/1")], + state="deleted", + ), + ) + commands = [ + "interface GigabitEthernet0/1", + "no lldp receive", + "no lldp transmit", + ] + res = self.execute_module(changed=True) + self.assertEqual(res["commands"], commands) + + def test_ios_lldp_interfaces_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + GigabitEthernet0/0: + Tx: enabled + Rx: disabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + + GigabitEthernet0/1: + Tx: enabled + Rx: enabled + Tx state: IDLE + Rx state: WAIT FOR FRAME + + GigabitEthernet0/2: + Tx: disabled + Rx: enabled + Tx state: IDLE + Rx state: INIT + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [ + {"name": "GigabitEthernet0/0", "transmit": True, "receive": False}, + {"name": "GigabitEthernet0/1", "transmit": True, "receive": True}, + {"name": "GigabitEthernet0/2", "transmit": False, "receive": True}, + ] + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_lldp_interfaces_rendered(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + set_module_args( + dict( + config=[ + {"name": "GigabitEthernet0/0", "transmit": True, "receive": False}, + {"name": "GigabitEthernet0/1", "transmit": True, "receive": True}, + {"name": "GigabitEthernet0/2", "transmit": False, "receive": True}, + ], + state="rendered", + ), + ) + commands = [ + "interface GigabitEthernet0/0", + "no lldp receive", + "lldp transmit", + "interface GigabitEthernet0/1", + "lldp receive", + "lldp transmit", + "interface GigabitEthernet0/2", + "lldp receive", + "no lldp transmit", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_logging.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_logging.py new file mode 100644 index 000000000..26278952a --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_logging.py @@ -0,0 +1,153 @@ +# +# (c) 2016 Red Hat Inc. +# (c) 2017 Paul Neumann +# +# 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 <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.cisco.ios.plugins.modules import ios_logging +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosLoggingModule(TestIosModule): + module = ios_logging + + def setUp(self): + super(TestIosLoggingModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_logging.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_logging.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_capabilities = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_logging.get_capabilities", + ) + self.get_capabilities = self.mock_get_capabilities.start() + self.get_capabilities.return_value = {"device_info": {"network_os_version": "15.6(2)T"}} + + def tearDown(self): + super(TestIosLoggingModule, self).tearDown() + + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_get_capabilities.stop() + + def load_fixtures(self, commands=None): + self.get_config.return_value = load_fixture("ios_logging_config.cfg") + self.load_config.return_value = None + + def test_ios_logging_buffer_size_changed_implicit(self): + set_module_args(dict(dest="buffered")) + commands = ["logging buffered 4096"] + self.execute_module(changed=True, commands=commands) + + def test_ios_logging_buffer_size_changed_explicit(self): + set_module_args(dict(dest="buffered", size=6000)) + commands = ["logging buffered 6000"] + self.execute_module(changed=True, commands=commands) + + def test_ios_logging_add_host(self): + set_module_args(dict(dest="host", name="192.168.1.1")) + commands = ["logging host 192.168.1.1"] + self.execute_module(changed=True, commands=commands) + + def test_ios_logging_host_idempotent(self): + set_module_args(dict(dest="host", name="2.3.4.5")) + commands = [] + self.execute_module(changed=False, commands=commands) + + def test_ios_logging_delete_non_exist_host(self): + set_module_args(dict(dest="host", name="192.168.1.1", state="absent")) + commands = [] + self.execute_module(changed=False, commands=commands) + + def test_ios_logging_delete_host(self): + set_module_args(dict(dest="host", name="2.3.4.5", state="absent")) + commands = ["no logging host 2.3.4.5"] + self.execute_module(changed=True, commands=commands) + + def test_ios_logging_configure_disabled_monitor_destination(self): + set_module_args(dict(dest="monitor", level="debugging")) + commands = ["logging monitor debugging"] + self.execute_module(changed=True, commands=commands) + + +class TestIosLoggingModuleIOS12(TestIosModule): + module = ios_logging + + def setUp(self): + super(TestIosLoggingModuleIOS12, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_logging.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_logging.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_capabilities = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_logging.get_capabilities", + ) + self.get_capabilities = self.mock_get_capabilities.start() + self.get_capabilities.return_value = {"device_info": {"network_os_version": "12.1(2)T"}} + + def tearDown(self): + super(TestIosLoggingModuleIOS12, self).tearDown() + + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_get_capabilities.stop() + + def load_fixtures(self, commands=None): + self.get_config.return_value = load_fixture("ios_logging_config_ios12.cfg") + self.load_config.return_value = None + + def test_ios_logging_add_host(self): + set_module_args(dict(dest="host", name="192.168.1.1")) + commands = ["logging 192.168.1.1"] + self.execute_module(changed=True, commands=commands) + + def test_ios_logging_host_idempotent(self): + set_module_args(dict(dest="host", name="2.3.4.5")) + commands = [] + self.execute_module(changed=False, commands=commands) + + def test_ios_logging_delete_non_exist_host(self): + set_module_args(dict(dest="host", name="192.168.1.1", state="absent")) + commands = [] + self.execute_module(changed=False, commands=commands) + + def test_ios_logging_delete_host(self): + set_module_args(dict(dest="host", name="2.3.4.5", state="absent")) + commands = ["no logging 2.3.4.5"] + self.execute_module(changed=True, commands=commands) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_logging_global.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_logging_global.py new file mode 100644 index 000000000..6520ebc7a --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_logging_global.py @@ -0,0 +1,711 @@ +# +# (c) 2021, 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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_logging_global +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosLoggingGlobalModule(TestIosModule): + module = ios_logging_global + + def setUp(self): + super(TestIosLoggingGlobalModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.logging_global.logging_global." + "Logging_globalFacts.get_logging_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosLoggingGlobalModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_logging_global_merged_idempotent(self): + """ + passing all commands as have and expecting [] commands + """ + self.execute_show_command.return_value = dedent( + """\ + logging on + logging count + logging buginf + logging userinfo + logging esm config + logging server-arp + logging trap errors + logging delimiter tcp + logging reload alerts + logging host 172.16.1.1 + logging exception 4099 + logging history alerts + logging history size 400 + logging facility local5 + logging snmp-trap errors + logging monitor warnings + logging origin-id hostname + logging host 172.16.1.11 xml + logging cns-events warnings + logging queue-limit esm 150 + logging dmvpn rate-limit 10 + logging message-counter log + logging console xml critical + logging message-counter debug + logging persistent batch 4444 + logging host 172.16.1.25 filtered + logging source-interface GBit1/0 + logging source-interface CTunnel2 + logging policy-firewall rate-limit 10 + logging buffered xml 5099 notifications + logging rate-limit all 2 except warnings + logging host 172.16.1.10 filtered stream 10 + logging host 172.16.1.13 transport tcp port 514 + logging discriminator msglog01 severity includes 5 + logging filter tftp://172.16.2.18/ESM/elate.tcl args TESTInst2 + logging filter tftp://172.16.2.14/ESM/escalate.tcl args TESTInst + """, + ) + playbook = dict( + config=dict( + logging_on="enable", + buffered=dict(size=5099, severity="notifications", xml=True), + buginf=True, + cns_events="warnings", + console=dict(severity="critical", xml=True), + count=True, + delimiter=dict(tcp=True), + discriminator=["msglog01 severity includes 5"], + dmvpn=dict(rate_limit=10), + esm=dict(config=True), + exception=4099, + facility="local5", + filter=[ + dict(url="tftp://172.16.2.18/ESM/elate.tcl", args="TESTInst2"), + dict(url="tftp://172.16.2.14/ESM/escalate.tcl", args="TESTInst"), + ], + history=dict(severity="alerts", size=400), + hosts=[ + dict(hostname="172.16.1.1"), + dict(hostname="172.16.1.11", xml=True), + dict(hostname="172.16.1.25", filtered=True), + dict(hostname="172.16.1.10", stream=10, filtered=True), + dict(hostname="172.16.1.13", transport=dict(tcp=dict(port=514))), + ], + message_counter=["log", "debug"], + monitor=dict(severity="warnings"), + origin_id=dict(tag="hostname"), + persistent=dict(batch=4444), + policy_firewall=dict(rate_limit=10), + queue_limit=dict(esm=150), + rate_limit=dict(all=True, size=2, except_severity="warnings"), + reload=dict(severity="alerts"), + server_arp=True, + snmp_trap=["errors"], + source_interface=[dict(interface="GBit1/0"), dict(interface="CTunnel2")], + trap="errors", + userinfo=True, + ), + ) + merged = [] + playbook["state"] = "merged" + set_module_args(playbook) + result = self.execute_module() + + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(merged)) + + def test_ios_logging_global_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + logging on + logging count + logging buginf + logging buffered xml 5099 notifications + logging console xml critical + logging delimiter tcp + logging dmvpn rate-limit 10 + logging esm config + logging exception 4099 + logging facility local5 + logging history alerts + logging history size 400 + logging monitor warnings + logging origin-id hostname + logging persistent batch 4444 + logging policy-firewall rate-limit 10 + logging queue-limit esm 150 + logging rate-limit all 2 except warnings + logging server-arp + logging reload alerts + logging userinfo + logging trap errors + """, + ) + playbook = dict( + config=dict( + logging_on="enable", + count=True, + buffered=dict(size=5099, severity="notifications", xml=True), + buginf=True, + console=dict(severity="critical", xml=True), + delimiter=dict(tcp=True), + dmvpn=dict(rate_limit=10), + esm=dict(config=True), + exception=4099, + facility="local5", + history=dict(severity="alerts", size=400), + monitor=dict(severity="warnings"), + origin_id=dict(tag="hostname"), + persistent=dict(batch=4444), + policy_firewall=dict(rate_limit=10), + queue_limit=dict(esm=150), + rate_limit=dict(all=True, size=2, except_severity="warnings"), + reload=dict(severity="alerts"), + server_arp=True, + trap="errors", + userinfo=True, + ), + ) + deleted = [ + "no logging on", + "no logging count", + "no logging buginf", + "no logging buffered xml 5099 notifications", + "no logging console xml critical", + "no logging delimiter tcp", + "no logging dmvpn rate-limit 10", + "no logging esm config", + "no logging exception 4099", + "no logging facility local5", + "no logging history alerts", + "no logging history size 400", + "no logging monitor warnings", + "no logging origin-id hostname", + "no logging persistent batch 4444", + "no logging policy-firewall rate-limit 10", + "no logging queue-limit esm 150", + "no logging rate-limit all 2 except warnings", + "no logging server-arp", + "no logging reload alerts", + "no logging userinfo", + "no logging trap errors", + ] + playbook["state"] = "deleted" + set_module_args(playbook) + result = self.execute_module(changed=True) + + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(deleted)) + + def test_ios_logging_global_deleted_list(self): + self.execute_show_command.return_value = dedent( + """\ + logging discriminator msglog01 severity includes 5 + logging filter tftp://172.16.2.18/ESM/elate.tcl args TESTInst2 + logging filter tftp://172.16.2.14/ESM/escalate.tcl args TESTInst + logging host 172.16.1.1 + logging host 172.16.1.11 xml + logging host 172.16.1.25 filtered + logging host 172.16.1.10 filtered stream 10 + logging host 172.16.1.13 transport tcp port 514 + logging message-counter log + logging message-counter debug + logging snmp-trap errors + logging source-interface GBit1/0 + logging source-interface CTunnel2 + """, + ) + playbook = dict( + config=dict( + discriminator=["msglog01 severity includes 5"], + filter=[ + dict(url="tftp://172.16.2.18/ESM/elate.tcl", args="TESTInst2"), + dict(url="tftp://172.16.2.14/ESM/escalate.tcl", args="TESTInst"), + ], + hosts=[ + dict(host="172.16.1.1"), + dict(hostname="172.16.1.11", xml=True), + dict(host="172.16.1.25", filtered=True), + dict(hostname="172.16.1.10", stream=10, filtered=True), + dict(hostname="172.16.1.13", transport=dict(tcp=dict(port=514))), + ], + message_counter=["log", "debug"], + snmp_trap=["errors"], + source_interface=[dict(interface="GBit1/0"), dict(interface="CTunnel2")], + ), + ) + deleted = [ + "no logging discriminator msglog01 severity includes 5", + "no logging filter tftp://172.16.2.18/ESM/elate.tcl args TESTInst2", + "no logging filter tftp://172.16.2.14/ESM/escalate.tcl args TESTInst", + "no logging host 172.16.1.1", + "no logging host 172.16.1.11", + "no logging host 172.16.1.25", + "no logging host 172.16.1.10", + "no logging host 172.16.1.13", + "no logging message-counter log", + "no logging message-counter debug", + "no logging snmp-trap errors", + "no logging source-interface GBit1/0", + "no logging source-interface CTunnel2", + ] + playbook["state"] = "deleted" + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(deleted)) + + def test_ios_logging_global_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + logging on + logging count + logging buginf + logging buffered xml 5099 notifications + logging console xml critical + logging delimiter tcp + logging dmvpn rate-limit 10 + logging esm config + logging exception 4099 + logging facility local5 + logging history alerts + logging history size 400 + logging monitor warnings + logging origin-id hostname + logging persistent batch 4444 + logging policy-firewall rate-limit 10 + logging queue-limit esm 150 + logging rate-limit all 2 except warnings + logging server-arp + logging reload alerts + logging userinfo + logging trap errors + """, + ) + playbook = dict( + config=dict( + discriminator=["msglog01 severity includes 5"], + filter=[ + dict(url="tftp://172.16.2.18/ESM/elate.tcl", args="TESTInst2"), + dict(url="tftp://172.16.2.14/ESM/escalate.tcl", args="TESTInst"), + ], + hosts=[ + dict(hostname="172.16.1.1"), + dict(hostname="172.16.1.11", xml=True), + dict(hostname="172.16.1.25", filtered=True), + dict(hostname="172.16.1.10", stream=10, filtered=True), + dict(hostname="172.16.1.13", transport=dict(tcp=dict(port=514))), + ], + message_counter=["log", "debug"], + snmp_trap=["errors"], + source_interface=[dict(interface="GBit1/0"), dict(interface="CTunnel2")], + ), + ) + overridden = [ + "no logging on", + "no logging count", + "no logging buginf", + "no logging buffered xml 5099 notifications", + "no logging console xml critical", + "no logging delimiter tcp", + "no logging dmvpn rate-limit 10", + "no logging esm config", + "no logging exception 4099", + "no logging facility local5", + "no logging history alerts", + "no logging history size 400", + "no logging monitor warnings", + "no logging origin-id hostname", + "no logging persistent batch 4444", + "no logging policy-firewall rate-limit 10", + "no logging queue-limit esm 150", + "no logging rate-limit all 2 except warnings", + "no logging server-arp", + "no logging reload alerts", + "no logging userinfo", + "no logging trap errors", + "logging discriminator msglog01 severity includes 5", + "logging filter tftp://172.16.2.18/ESM/elate.tcl args TESTInst2", + "logging filter tftp://172.16.2.14/ESM/escalate.tcl args TESTInst", + "logging host 172.16.1.1", + "logging host 172.16.1.11 xml", + "logging host 172.16.1.25 filtered", + "logging host 172.16.1.10 filtered stream 10", + "logging host 172.16.1.13 transport tcp port 514", + "logging message-counter log", + "logging message-counter debug", + "logging snmp-trap errors", + "logging source-interface GBit1/0", + "logging source-interface CTunnel2", + ] + playbook["state"] = "overridden" + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(overridden)) + + def test_ios_logging_global_overridden_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + logging host 172.16.1.11 xml + logging monitor critical + logging buffered xml 5099 warnings + logging facility local6 + """, + ) + playbook = dict( + config=dict( + buffered=dict(size=5099, severity="warnings", xml=True), + facility="local6", + hosts=[dict(host="172.16.1.11", xml=True)], + monitor=dict(severity="critical"), + ), + ) + overridden = [] + playbook["state"] = "overridden" + set_module_args(playbook) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(overridden)) + + def test_ios_logging_global_merged(self): + self.execute_show_command.return_value = dedent( + """\ + logging host 172.16.1.1 + """, + ) + playbook = dict( + config=dict( + monitor=dict(discriminator="TEST"), + hosts=[ + dict(hostname="172.16.2.15", session_id=dict(text="Test")), + dict( + ipv6="2001:0db8:85a3:0000:0000:8a2e:0370:7304", + discriminator="msglog01 severity includes 5", + ), + dict(ipv6="2001:0db8:85a3:0000:0000:8a2e:0370:7314", sequence_num_session=True), + dict(ipv6="2001:0db8:85a3:0000:0000:8a2e:0370:7324", vrf="vpn1"), + dict(ipv6="2001:0db8:85a3:0000:0000:8a2e:0370:7334", stream=10, filtered=True), + dict( + ipv6="2001:0db8:85a3:0000:0000:8a2e:0370:7344", + session_id=dict(tag="ipv4"), + ), + dict( + ipv6="2001:0db8:85a3:0000:0000:8a2e:0370:7354", + transport=dict(tcp=dict(port=514, xml=True)), + ), + dict( + ipv6="2001:0db8:85a3:0000:0000:8a2e:0370:7374", + vrf="Apn2", + transport=dict(udp=dict(discriminator="msglog01 severity includes 5")), + ), + dict( + ipv6="2001:0db8:85a3:0000:0000:8a2e:0370:7384", + transport=dict(udp=dict(sequence_num_session=True)), + ), + dict( + ipv6="2001:0db8:85a3:0000:0000:8a2e:0370:7364", + transport=dict( + tcp=dict( + audit=True, + filtered=True, + stream=10, + session_id=dict(text="Test"), + ), + ), + ), + ], + ), + ) + merged = [ + "logging monitor discriminator TEST", + "logging host 172.16.2.15 session-id string Test", + "logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7304 discriminator msglog01 severity includes 5", + "logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7314 sequence-num-session", + "logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7324 vrf vpn1", + "logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 filtered stream 10", + "logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7344 session-id ipv4", + "logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7354 transport tcp port 514 xml", + "logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7364 transport tcp audit filtered stream 10 session-id string Test", + "logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7374 vrf Apn2 transport udp discriminator msglog01 severity includes 5", + "logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7384 transport udp sequence-num-session", + ] + playbook["state"] = "merged" + set_module_args(playbook) + result = self.execute_module(changed=True) + + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(merged)) + + def test_ios_logging_global_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + logging on + logging buffered xml 5099 notifications + logging buginf + logging cns-events warnings + logging console xml critical + logging count + logging delimiter tcp + logging host 172.16.2.15 session-id string Test + """, + ), + state="parsed", + ), + ) + parsed = dict( + logging_on="enable", + buffered=dict(size=5099, severity="notifications", xml=True), + buginf=True, + cns_events="warnings", + console=dict(severity="critical", xml=True), + count=True, + delimiter=dict(tcp=True), + hosts=[dict(host="172.16.2.15", session_id=dict(text="Test"))], + ) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["parsed"]), sorted(parsed)) + + def test_ios_logging_global_gathered(self): + self.execute_show_command.return_value = dedent( + """\ + logging persistent notify + """, + ) + set_module_args(dict(state="gathered")) + gathered = dict(persistent=dict(notify=True)) + result = self.execute_module(changed=False) + + self.maxDiff = None + self.assertEqual(sorted(result["gathered"]), sorted(gathered)) + + def test_ios_logging_global_gathered_host(self): + self.execute_show_command.return_value = dedent( + """\ + logging host 172.16.1.1 vrf vpn-1 transport tcp audit + """, + ) + set_module_args(dict(state="gathered")) + gathered = dict( + hosts=[dict(hostname="172.16.1.1", vrf="vpn-1", transport=dict(tcp=dict(audit=True)))], + ) + result = self.execute_module(changed=False) + + self.maxDiff = None + self.assertEqual(sorted(result["gathered"]), sorted(gathered)) + + def test_ios_logging_global_rendered(self): + set_module_args( + dict( + config=dict( + rate_limit=dict(console=True, size=2, except_severity="warnings"), + reload=dict(message_limit=10, severity="alerts"), + persistent=dict( + url="flash0:172.16.0.1", + threshold=2, + immediate=True, + protected=True, + notify=True, + ), + queue_limit=dict(trap=1000), + buffered=dict(discriminator="notifications", filtered=True), + hosts=[ + dict( + ipv6="2001:0db8:85a3:0000:0000:8a2e:0370:7364", + transport=dict(tcp=dict(session_id=dict(tag="hostname"))), + ), + ], + ), + state="rendered", + ), + ) + rendered = [ + "logging reload message-limit 10 alerts", + "logging rate-limit console 2 except warnings", + "logging buffered discriminator notifications filtered", + "logging persistent url flash0:172.16.0.1 threshold 2 immediate protected notify", + "logging queue-limit trap 1000", + "logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7364 transport tcp session-id hostname", + ] + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["rendered"]), sorted(rendered)) + + def test_ios_logging_global_deleted_empty(self): + self.execute_show_command.return_value = dedent( + """\ + logging rate-limit all 2 except warnings + logging server-arp + logging origin-id string Test + logging reload alerts + logging userinfo + logging trap errors + logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7324 + logging persistent size 1000 filesize 1000 + logging source-interface Gbit0/1 vrf vpn1 + logging filter flash:172.16.1.1 1 args Test + """, + ) + playbook = dict(config=dict()) + deleted = [ + "no logging rate-limit all 2 except warnings", + "no logging server-arp", + "no logging origin-id string Test", + "no logging reload alerts", + "no logging userinfo", + "no logging trap errors", + "no logging host ipv6 2001:0db8:85a3:0000:0000:8a2e:0370:7324", + "no logging persistent size 1000 filesize 1000", + "no logging source-interface Gbit0/1 vrf vpn1", + "no logging filter flash:172.16.1.1 1 args Test", + ] + playbook["state"] = "deleted" + set_module_args(playbook) + result = self.execute_module(changed=True) + + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(deleted)) + + def test_ios_logging_global_deleted_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + no logging exception + no logging buffered + no logging reload + no logging rate-limit + no logging console + no logging monitor + no logging cns-events + no logging trap + """, + ) + playbook = dict(config=dict()) + deleted = [] + playbook["state"] = "deleted" + set_module_args(playbook) + result = self.execute_module(changed=False) + + self.maxDiff = None + self.assertEqual(result["commands"], deleted) + + def test_ios_logging_global_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + logging host 172.16.1.1 + """, + ) + playbook = dict( + config=dict(hosts=[dict(hostname="172.16.2.15", session_id=dict(text="Test"))]), + ) + replaced = ["no logging host 172.16.1.1", "logging host 172.16.2.15 session-id string Test"] + playbook["state"] = "replaced" + set_module_args(playbook) + result = self.execute_module(changed=True) + + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(replaced)) + + def test_ios_logging_global_replaced_ordering_host(self): + self.execute_show_command.return_value = dedent( + """\ + logging buffered 64000 + no logging console + no logging monitor + logging history notifications + logging history size 400 + logging origin-id hostname + logging source-interface GigabitEthernet0 vrf Mgmt-intf + logging host 172.16.0.1 transport udp port 10000 + logging host 172.16.0.2 + """, + ) + playbook = { + "config": { + "buffered": {"size": 64000}, + "history": {"severity": "notifications", "size": 400}, + "hosts": [ + {"hostname": "172.16.0.1", "vrf": "Mgmt-intf"}, + {"hostname": "172.16.0.3", "vrf": "Mgmt-intf"}, + {"hostname": "172.16.0.4", "vrf": "Mgmt-intf"}, + ], + "origin_id": {"tag": "hostname"}, + "rate_limit": {"console": True, "except_severity": "errors", "size": 10}, + "source_interface": [{"interface": "GigabitEthernet0", "vrf": "Mgmt-intf"}], + "trap": "informational", + }, + } + replaced = [ + "logging rate-limit console 10 except errors", + "logging trap informational", + "logging host 172.16.0.1 vrf Mgmt-intf", + "logging host 172.16.0.3 vrf Mgmt-intf", + "logging host 172.16.0.4 vrf Mgmt-intf", + "no logging host 172.16.0.2", + ] + playbook["state"] = "replaced" + set_module_args(playbook) + result = self.execute_module(changed=True) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(replaced)) + + def test_ios_logging_global_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + logging host 172.16.2.15 + """, + ) + playbook = dict(config=dict(hosts=[dict(hostname="172.16.2.15")])) + replaced = [] + playbook["state"] = "replaced" + set_module_args(playbook) + result = self.execute_module(changed=False) + + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(replaced)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ntp.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ntp.py new file mode 100644 index 000000000..3e73fdfd4 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ntp.py @@ -0,0 +1,110 @@ +# 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 <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.cisco.ios.plugins.modules import ios_ntp +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosNtpModule(TestIosModule): + module = ios_ntp + + def setUp(self): + super(TestIosNtpModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_ntp.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_ntp.load_config", + ) + self.load_config = self.mock_load_config.start() + + def tearDown(self): + super(TestIosNtpModule, self).tearDown() + self.mock_get_config.stop() + self.mock_load_config.stop() + + def load_fixtures(self, commands=None): + self.get_config.return_value = load_fixture("ios_ntp_config.cfg").strip() + self.load_config.return_value = dict(diff=None, session="session") + + def test_ios_ntp_idempotent(self): + set_module_args( + dict( + server="10.75.32.5", + source_int="Loopback0", + acl="NTP_ACL", + logging=True, + auth=True, + auth_key="15435A030726242723273C21181319000A", + key_id="10", + vrf="my_mgmt_vrf", + state="present", + ), + ) + commands = [] + self.execute_module(changed=False, commands=commands) + + def test_ios_ntp_config(self): + set_module_args( + dict( + server="10.75.33.5", + source_int="Vlan2", + acl="NTP_ACL", + logging=True, + auth=True, + auth_key="15435A030726242723273C21181319000A", + key_id="10", + state="present", + ), + ) + commands = ["ntp server 10.75.33.5", "ntp source Vlan2"] + self.execute_module(changed=True, commands=commands) + + def test_ios_ntp_remove(self): + set_module_args( + dict( + server="10.75.32.5", + source_int="Loopback0", + acl="NTP_ACL", + logging=True, + auth=True, + auth_key="15435A030726242723273C21181319000A", + key_id="10", + vrf="my_mgmt_vrf", + state="absent", + ), + ) + commands = [ + "no ntp server vrf my_mgmt_vrf 10.75.32.5", + "no ntp source Loopback0", + "no ntp access-group peer NTP_ACL", + "no ntp logging", + "no ntp authenticate", + "no ntp trusted-key 10", + "no ntp authentication-key 10 md5 15435A030726242723273C21181319000A 7", + ] + self.execute_module(changed=True, commands=commands) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ntp_global.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ntp_global.py new file mode 100644 index 000000000..d80b568f4 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ntp_global.py @@ -0,0 +1,470 @@ +# +# (c) 2021, 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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_ntp_global +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosNtpGlobalModule(TestIosModule): + module = ios_ntp_global + + def setUp(self): + super(TestIosNtpGlobalModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.ntp_global.ntp_global." + "Ntp_globalFacts.get_ntp_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosNtpGlobalModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_ntp_global_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + ntp allow mode control 4 + ntp allow mode private + ntp authenticate + ntp broadcastdelay 22 + ntp clock-period 5 + ntp logging + ntp master 4 + ntp max-associations 34 + ntp maxdistance 3 + ntp mindistance 10 + ntp orphan 4 + ntp panic update + ntp source GigabitEthernet0/1 + ntp update-calendar + ntp access-group ipv4 peer DHCP-Server kod + ntp access-group ipv6 peer preauth_ipv6_acl kod + ntp access-group peer 2 kod + ntp access-group query-only 10 + ntp authentication-key 2 md5 SomeSecurePassword 7 + ntp peer 172.16.1.10 version 2 + ntp peer 172.16.1.11 key 2 minpoll 5 prefer version 2 + ntp peer ip checkPeerDomainIpv4.com prefer + ntp peer ipv6 checkPeerDomainIpv6.com + ntp peer ipv6 testPeerDomainIpv6.com prefer + ntp server 172.16.1.12 version 2 + ntp server ipv6 checkServerDomainIpv6.com + ntp server 172.16.1.13 source GigabitEthernet0/1 + ntp trusted-key 3 - 13 + ntp trusted-key 21 + """, + ) + set_module_args( + dict( + config=dict( + access_group=dict( + peer=[ + dict(access_list="2", kod=True), + dict(access_list="preauth_ipv6_acl", ipv6=True, kod=True), + ], + ), + allow=dict(control=dict(rate_limit=4)), + authenticate=True, + authentication_keys=[ + dict(algorithm="md5", encryption=7, id=2, key="SomeSecurePassword"), + ], + broadcast_delay=22, + logging=True, + master=dict(stratum=4), + max_associations=34, + max_distance=3, + min_distance=10, + orphan=4, + panic_update=True, + peers=[ + dict(peer="172.16.1.10", version=2), + dict(key_id=2, minpoll=5, peer="172.16.1.11", prefer=True, version=2), + dict(peer="checkPeerDomainIpv4.com", prefer=True, use_ipv4=True), + dict(peer="checkPeerDomainIpv6.com", use_ipv6=True), + dict(peer="testPeerDomainIpv6.com", prefer=True, use_ipv6=True), + ], + servers=[ + dict(server="172.16.1.12", version=2), + dict(server="172.16.1.13", source="GigabitEthernet0/1"), + dict(server="checkServerDomainIpv6.com", use_ipv6=True), + ], + source="GigabitEthernet0/1", + trusted_keys=[dict(range_start=21), dict(range_end=13, range_start=3)], + update_calendar=True, + ), + state="merged", + ), + ) + commands = [] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ntp_global_merged(self): + self.execute_show_command.return_value = dedent( + """\ + ntp allow mode control 4 + ntp allow mode private + """, + ) + set_module_args( + dict( + config=dict( + access_group=dict( + peer=[ + dict(access_list="2", kod=True), + dict(access_list="preauth_ipv6_acl", ipv6=True, kod=True), + ], + ), + allow=dict(control=dict(rate_limit=4)), + authenticate=True, + authentication_keys=[ + dict(algorithm="md5", encryption=7, id=2, key="SomeSecurePassword"), + ], + broadcast_delay=22, + logging=True, + master=dict(stratum=4), + max_associations=34, + max_distance=3, + min_distance=10, + orphan=4, + panic_update=True, + peers=[ + dict(peer="172.16.1.10", version=2), + dict(key=2, minpoll=5, peer="172.16.1.11", prefer=True, version=2), + dict(peer="checkPeerDomainIpv4.com", prefer=True, use_ipv4=True), + dict(peer="checkPeerDomainIpv6.com", use_ipv6=True), + dict(peer="testPeerDomainIpv6.com", prefer=True, use_ipv6=True), + ], + servers=[ + dict(server="172.16.1.12", version=2), + dict(server="172.16.1.110", key_id=2), + dict(server="172.16.1.13", source="GigabitEthernet0/1"), + dict(server="checkServerDomainIpv6.com", use_ipv6=True), + ], + source="GigabitEthernet0/1", + trusted_keys=[dict(range_start=21), dict(range_end=13, range_start=3)], + update_calendar=True, + ), + state="merged", + ), + ) + commands = [ + "ntp authenticate", + "ntp broadcastdelay 22", + "ntp logging", + "ntp master 4", + "ntp max-associations 34", + "ntp maxdistance 3", + "ntp mindistance 10", + "ntp orphan 4", + "ntp panic update", + "ntp source GigabitEthernet0/1", + "ntp update-calendar", + "ntp access-group peer 2 kod", + "ntp access-group ipv6 peer preauth_ipv6_acl kod", + "ntp authentication-key 2 md5 SomeSecurePassword 7", + "ntp peer 172.16.1.10 version 2", + "ntp peer 172.16.1.11 key 2 minpoll 5 prefer version 2", + "ntp peer ip checkPeerDomainIpv4.com prefer", + "ntp peer ipv6 checkPeerDomainIpv6.com", + "ntp peer ipv6 testPeerDomainIpv6.com prefer", + "ntp server 172.16.1.12 version 2", + "ntp server 172.16.1.110 key 2", + "ntp server 172.16.1.13 source GigabitEthernet0/1", + "ntp server ipv6 checkServerDomainIpv6.com", + "ntp trusted-key 21", + "ntp trusted-key 3 - 13", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ntp_global_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + ntp allow mode control 4 + ntp allow mode private + ntp authenticate + ntp broadcastdelay 22 + ntp clock-period 5 + ntp logging + ntp master 4 + ntp max-associations 34 + ntp maxdistance 3 + ntp mindistance 10 + ntp orphan 4 + ntp panic update + ntp source GigabitEthernet0/1 + ntp update-calendar + ntp access-group ipv4 peer DHCP-Server kod + ntp access-group ipv6 peer preauth_ipv6_acl kod + ntp access-group peer 2 kod + ntp access-group query-only 10 + ntp authentication-key 2 md5 SomeSecurePassword 7 + ntp peer 172.16.1.10 version 2 + ntp peer 172.16.1.11 key 2 minpoll 5 prefer version 2 + ntp peer ip checkPeerDomainIpv4.com prefer + ntp peer ipv6 checkPeerDomainIpv6.com + ntp peer ipv6 testPeerDomainIpv6.com prefer + ntp server 172.16.1.12 version 2 + ntp server ipv6 checkServerDomainIpv6.com + ntp server 172.16.1.13 source GigabitEthernet0/1 + ntp trusted-key 3 - 13 + ntp trusted-key 21 + """, + ) + set_module_args(dict(config=dict(), state="deleted")) + commands = [ + "no ntp allow mode control 4", + "no ntp allow mode private", + "no ntp authenticate", + "no ntp broadcastdelay 22", + "no ntp clock-period 5", + "no ntp logging", + "no ntp master 4", + "no ntp max-associations 34", + "no ntp maxdistance 3", + "no ntp mindistance 10", + "no ntp orphan 4", + "no ntp panic update", + "no ntp source GigabitEthernet0/1", + "no ntp update-calendar", + "no ntp access-group peer 2 kod", + "no ntp access-group ipv4 peer DHCP-Server kod", + "no ntp access-group ipv6 peer preauth_ipv6_acl kod", + "no ntp access-group query-only 10", + "no ntp authentication-key 2 md5 SomeSecurePassword 7", + "no ntp peer 172.16.1.10 version 2", + "no ntp peer 172.16.1.11 key 2 minpoll 5 prefer version 2", + "no ntp peer ip checkPeerDomainIpv4.com prefer", + "no ntp peer ipv6 checkPeerDomainIpv6.com", + "no ntp peer ipv6 testPeerDomainIpv6.com prefer", + "no ntp server 172.16.1.12 version 2", + "no ntp server 172.16.1.13 source GigabitEthernet0/1", + "no ntp server ipv6 checkServerDomainIpv6.com", + "no ntp trusted-key 21", + "no ntp trusted-key 3 - 13", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ntp_global_deleted_blank(self): + self.execute_show_command.return_value = dedent( + """\ + """, + ) + set_module_args(dict(config=dict(), state="deleted")) + commands = [] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ntp_global_replaced_overridden(self): + """both the replaced and overridden states are supported to have same behaviour""" + self.execute_show_command.return_value = dedent( + """\ + ntp allow mode control 4 + ntp allow mode private + ntp authenticate + ntp broadcastdelay 22 + ntp clock-period 15 + ntp logging + ntp master 14 + ntp max-associations 134 + ntp maxdistance 3 + ntp mindistance 100 + ntp orphan 4 + ntp panic update + ntp source Loopback888 + ntp update-calendar + ntp access-group ipv4 peer DHCPAC kod + ntp access-group ipv6 peer preauth_ipv6_acl kod + ntp access-group peer 2 kod + ntp access-group query-only 10 + ntp authentication-key 2 md5 SomeSecurePassword 7 + ntp peer 172.16.1.9 version 2 + ntp peer 172.16.1.1 key 2 minpoll 5 prefer version 2 + ntp peer ip checkPeerDomainIpv4.com prefer + ntp peer ipv6 checkPeerDomainIpv6.com + ntp peer ipv6 testPeerDomainIpv6.com prefer + ntp server 172.16.1.19 version 2 + ntp server ipv6 checkServerDomainIpv6.com + ntp server 172.16.1.111 source GigabitEthernet0/1 + ntp trusted-key 3 - 130 + ntp trusted-key 21 + """, + ) + set_module_args( + dict( + config=dict( + access_group=dict( + peer=[ + dict(access_list="2", kod=True), + dict(access_list="preauth_ipv6_acl", ipv6=True, kod=True), + ], + ), + allow=dict(control=dict(rate_limit=4)), + authenticate=True, + authentication_keys=[ + dict(algorithm="md5", encryption=7, id=2, key="SomeSecurePassword"), + ], + broadcast_delay=22, + logging=True, + master=dict(stratum=4), + max_associations=34, + max_distance=3, + min_distance=10, + orphan=4, + panic_update=True, + peers=[ + dict(peer="172.16.1.10", version=2), + dict(key=2, minpoll=5, peer="172.16.1.11", prefer=True, version=2), + dict(peer="checkPeerDomainIpv4.com", prefer=True, use_ipv4=True), + dict(peer="checkPeerDomainIpv6.com", use_ipv6=True), + dict(peer="testPeerDomainIpv6.com", prefer=True, use_ipv6=True), + ], + servers=[ + dict(server="172.16.1.12", version=2), + dict(server="172.16.1.13", source="GigabitEthernet0/1"), + dict(server="checkServerDomainIpv6.com", use_ipv6=True), + ], + source="GigabitEthernet0/1", + trusted_keys=[dict(range_start=21), dict(range_end=13, range_start=3)], + update_calendar=True, + ), + state="replaced", + ), + ) + commands = [ + "no ntp allow mode private", + "no ntp clock-period 15", + "ntp master 4", + "ntp max-associations 34", + "ntp mindistance 10", + "ntp source GigabitEthernet0/1", + "no ntp access-group ipv4 peer DHCPAC kod", + "no ntp access-group query-only 10", + "ntp peer 172.16.1.10 version 2", + "ntp peer 172.16.1.11 key 2 minpoll 5 prefer version 2", + "no ntp peer 172.16.1.1 key 2 minpoll 5 prefer version 2", + "no ntp peer 172.16.1.9 version 2", + "ntp server 172.16.1.12 version 2", + "ntp server 172.16.1.13 source GigabitEthernet0/1", + "no ntp server 172.16.1.111 source GigabitEthernet0/1", + "no ntp server 172.16.1.19 version 2", + "no ntp trusted-key 3 - 130", + "ntp trusted-key 3 - 13", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ntp_global_replaced_overridden_idempotent(self): + """both the replaced and overridden states are supported to have same behaviour""" + self.execute_show_command.return_value = dedent( + """\ + ntp allow mode control 4 + ntp authenticate + ntp broadcastdelay 22 + ntp logging + ntp master 4 + ntp max-associations 34 + ntp maxdistance 3 + ntp mindistance 10 + ntp orphan 4 + ntp panic update + ntp source Loopback888 + ntp update-calendar + ntp access-group ipv6 peer preauth_ipv6_acl kod + ntp access-group peer 2 kod + ntp authentication-key 2 md5 SomeSecurePassword 7 + ntp peer 172.16.1.11 key 2 minpoll 5 prefer version 2 + ntp peer ip checkPeerDomainIpv4.com prefer + ntp server ipv6 checkServerDomainIpv6.com + ntp server 172.16.1.13 source GigabitEthernet0/1 + ntp trusted-key 3 - 13 + ntp trusted-key 21 + """, + ) + set_module_args( + dict( + config=dict( + access_group=dict( + peer=[ + dict(access_list="2", kod=True), + dict(access_list="preauth_ipv6_acl", ipv6=True, kod=True), + ], + ), + allow=dict(control=dict(rate_limit=4)), + authenticate=True, + authentication_keys=[ + dict(algorithm="md5", encryption=7, id=2, key="SomeSecurePassword"), + ], + broadcast_delay=22, + logging=True, + master=dict(stratum=4), + max_associations=34, + max_distance=3, + min_distance=10, + orphan=4, + panic_update=True, + peers=[ + dict(key_id=2, minpoll=5, peer="172.16.1.11", prefer=True, version=2), + dict(peer="checkPeerDomainIpv4.com", prefer=True, use_ipv4=True), + ], + servers=[ + dict(server="172.16.1.13", source="GigabitEthernet0/1"), + dict(server="checkServerDomainIpv6.com", use_ipv6=True), + ], + source="Loopback888", + trusted_keys=[dict(range_start=21), dict(range_end=13, range_start=3)], + update_calendar=True, + ), + state="overridden", + ), + ) + commands = [] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospf_interfaces.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospf_interfaces.py new file mode 100644 index 000000000..fe16e7a18 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospf_interfaces.py @@ -0,0 +1,666 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_ospf_interfaces +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosOspfInterfacesModule(TestIosModule): + module = ios_ospf_interfaces + + def setUp(self): + super(TestIosOspfInterfacesModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.ospf_interfaces.ospf_interfaces." + "Ospf_interfacesFacts.get_ospf_interfaces_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosOspfInterfacesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_ospf_interfaces_merged(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + ip ospf priority 40 + ip ospf adjacency stagger disable + ip ospf ttl-security hops 50 + ip ospf 10 area 20 + ip ospf cost 30 + interface GigabitEthernet0/3 + ipv6 ospf 55 area 105 + ipv6 ospf priority 20 + ipv6 ospf transmit-delay 30 + ipv6 ospf adjacency stagger disable + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/2", + address_family=[ + dict( + afi="ipv4", + bfd=True, + cost=dict(interface_cost=30), + network=dict(broadcast=True), + priority=60, + resync_timeout=90, + ttl_security=dict(hops=120), + authentication=dict(key_chain="test_key"), + ), + dict( + afi="ipv6", + bfd=True, + dead_interval=dict(time=100), + network=dict(manet=True), + priority=50, + ), + ], + ), + dict( + name="GigabitEthernet0/3", + address_family=[ + dict( + afi="ipv4", + bfd=True, + cost=dict(interface_cost=50), + priority=50, + ttl_security=dict(hops=150), + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "interface GigabitEthernet0/2", + "ip ospf authentication key-chain test_key", + "ip ospf bfd", + "ip ospf network broadcast", + "ip ospf priority 60", + "ip ospf resync-timeout 90", + "ip ospf ttl-security hops 120", + "ipv6 ospf bfd", + "ipv6 ospf dead-interval 100", + "ipv6 ospf network manet", + "ipv6 ospf priority 50", + "interface GigabitEthernet0/3", + "ip ospf bfd", + "ip ospf cost 50", + "ip ospf priority 50", + "ip ospf ttl-security hops 150", + ] + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], commands) + + def test_ios_ospf_interfaces_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + ip ospf priority 40 + ip ospf adjacency stagger disable + ip ospf ttl-security hops 50 + ip ospf 10 area 20 + ip ospf cost 30 + interface GigabitEthernet0/3 + ipv6 ospf 55 area 105 + ipv6 ospf priority 20 + ipv6 ospf transmit-delay 30 + ipv6 ospf adjacency stagger disable + """, + ) + set_module_args( + dict( + config=[ + dict( + address_family=[ + dict( + afi="ipv4", + adjacency=True, + cost=dict(interface_cost=30), + priority=40, + process=dict(id=10, area_id="20"), + ttl_security=dict(hops=50), + ), + ], + name="GigabitEthernet0/2", + ), + dict( + address_family=[ + dict( + afi="ipv6", + adjacency=True, + priority=20, + process=dict(id=55, area_id="105"), + transmit_delay=30, + ), + ], + name="GigabitEthernet0/3", + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_ospf_interfaces_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + ip ospf priority 40 + ip ospf adjacency stagger disable + ip ospf ttl-security hops 50 + ip ospf 10 area 20 + ip ospf cost 30 + interface GigabitEthernet0/3 + ipv6 ospf 55 area 105 + ipv6 ospf priority 20 + ipv6 ospf transmit-delay 30 + ipv6 ospf adjacency stagger disable + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/3", + address_family=[ + dict( + afi="ipv4", + bfd=True, + cost=dict(interface_cost=50), + priority=50, + ttl_security=dict(hops=150), + ), + ], + ), + ], + state="replaced", + ), + ) + commands = [ + "interface GigabitEthernet0/3", + "ip ospf bfd", + "ip ospf cost 50", + "ip ospf priority 50", + "ip ospf ttl-security hops 150", + "no ipv6 ospf 55 area 105", + "no ipv6 ospf adjacency stagger disable", + "no ipv6 ospf priority 20", + "no ipv6 ospf transmit-delay 30", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ospf_interfaces_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + ip ospf priority 40 + ip ospf adjacency stagger disable + ip ospf ttl-security hops 50 + ip ospf 10 area 20 + ip ospf cost 30 + interface GigabitEthernet0/3 + ipv6 ospf 55 area 105 + ipv6 ospf priority 20 + ipv6 ospf transmit-delay 30 + ipv6 ospf adjacency stagger disable + """, + ) + set_module_args( + dict( + config=[ + dict( + address_family=[ + dict( + afi="ipv4", + adjacency=True, + cost=dict(interface_cost=30), + priority=40, + process=dict(id=10, area_id="20"), + ttl_security=dict(set=True, hops=50), + ), + ], + name="GigabitEthernet0/2", + ), + dict( + address_family=[ + dict( + afi="ipv6", + adjacency=True, + priority=20, + process=dict(id=55, area_id="105"), + transmit_delay=30, + ), + ], + name="GigabitEthernet0/3", + ), + ], + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_ospf_interfaces_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + ip ospf priority 40 + ip ospf adjacency stagger disable + ip ospf ttl-security hops 50 + ip ospf 10 area 20 + ip ospf cost 30 + interface GigabitEthernet0/3 + ipv6 ospf 55 area 105 + ipv6 ospf priority 20 + ipv6 ospf transmit-delay 30 + ipv6 ospf adjacency stagger disable + """, + ) + set_module_args( + dict( + config=[ + dict( + address_family=[ + dict( + afi="ipv6", + manet=dict(cost=dict(percent=10)), + priority=40, + process=dict(id=10, area_id="20"), + transmit_delay=50, + ), + ], + name="GigabitEthernet0/3", + ), + ], + state="overridden", + ), + ) + commands = [ + "interface GigabitEthernet0/2", + "no ip ospf 10 area 20", + "no ip ospf adjacency stagger disable", + "no ip ospf cost 30", + "no ip ospf priority 40", + "no ip ospf ttl-security hops 50", + "interface GigabitEthernet0/3", + "ipv6 ospf 10 area 20", + "no ipv6 ospf adjacency stagger disable", + "ipv6 ospf manet peering cost percent 10", + "ipv6 ospf priority 40", + "ipv6 ospf transmit-delay 50" "", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ospf_interfaces_overridden_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + ip ospf priority 40 + ip ospf adjacency stagger disable + ip ospf ttl-security hops 50 + ip ospf 10 area 20 + ip ospf cost 30 + interface GigabitEthernet0/3 + ipv6 ospf 55 area 105 + ipv6 ospf priority 20 + ipv6 ospf transmit-delay 30 + ipv6 ospf adjacency stagger disable + """, + ) + set_module_args( + dict( + config=[ + dict( + address_family=[ + dict( + afi="ipv4", + adjacency=True, + cost=dict(interface_cost=30), + priority=40, + process=dict(id=10, area_id="20"), + ttl_security=dict(set=True, hops=50), + ), + ], + name="GigabitEthernet0/2", + ), + dict( + address_family=[ + dict( + afi="ipv6", + adjacency=True, + priority=20, + process=dict(id=55, area_id="105"), + transmit_delay=30, + ), + ], + name="GigabitEthernet0/3", + ), + ], + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_ospf_interfaces_deleted_interface(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + ip ospf priority 40 + ip ospf adjacency stagger disable + ip ospf ttl-security hops 50 + ip ospf 10 area 20 + ip ospf cost 30 + interface GigabitEthernet0/3 + ipv6 ospf 55 area 105 + ipv6 ospf priority 20 + ipv6 ospf transmit-delay 30 + ipv6 ospf adjacency stagger disable + """, + ) + set_module_args(dict(config=[dict(name="GigabitEthernet0/2")], state="deleted")) + commands = [ + "interface GigabitEthernet0/2", + "no ip ospf priority 40", + "no ip ospf adjacency stagger disable", + "no ip ospf ttl-security hops 50", + "no ip ospf 10 area 20", + "no ip ospf cost 30", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ospf_interfaces_deleted_all(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + ip ospf priority 40 + ip ospf adjacency stagger disable + ip ospf ttl-security hops 50 + ip ospf 10 area 20 + ip ospf cost 30 + interface GigabitEthernet0/3 + ipv6 ospf 55 area 105 + ipv6 ospf priority 20 + ipv6 ospf transmit-delay 30 + ipv6 ospf adjacency stagger disable + """, + ) + set_module_args(dict(config=[], state="deleted")) + commands = [ + "interface GigabitEthernet0/3", + "no ipv6 ospf 55 area 105", + "no ipv6 ospf adjacency stagger disable", + "no ipv6 ospf priority 20", + "no ipv6 ospf transmit-delay 30", + "interface GigabitEthernet0/2", + "no ip ospf 10 area 20", + "no ip ospf adjacency stagger disable", + "no ip ospf cost 30", + "no ip ospf priority 40", + "no ip ospf ttl-security hops 50", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ospf_interfaces_rendered(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + ip ospf priority 40 + ip ospf adjacency stagger disable + ip ospf ttl-security hops 50 + ip ospf 10 area 20 + ip ospf cost 30 + interface GigabitEthernet0/3 + ipv6 ospf 55 area 105 + ipv6 ospf priority 20 + ipv6 ospf transmit-delay 30 + ipv6 ospf adjacency stagger disable + """, + ) + set_module_args( + dict( + config=[ + dict( + name="GigabitEthernet0/2", + address_family=[ + dict( + afi="ipv4", + bfd=True, + cost=dict(interface_cost=30), + network=dict(broadcast=True), + dead_interval=dict(time=20, minimal=100), + demand_circuit=dict(enable=True, ignore=True), + multi_area=dict(id=15, cost=20), + priority=60, + resync_timeout=90, + authentication=dict( + key_chain="thekeychain", + message_digest=True, + null=True, + ), + ttl_security=dict(hops=120), + ), + dict( + afi="ipv6", + bfd=True, + dead_interval=dict(time=100), + demand_circuit=dict(enable=True, ignore=True), + network=dict(manet=True), + neighbor=dict( + address="10.0.2.15", + cost=14, + database_filter=True, + # poll_interval=13, + # priority=56, + ), + priority=50, + authentication=dict( + key_chain="thekeychain", + message_digest=True, + null=True, + ), + cost=dict( + interface_cost=10, + dynamic_cost=dict( + default=10, + hysteresis=dict( + percent=15, + threshold=25, + ), + weight=dict( + l2_factor=12, + latency=14, + oc=True, + resources=13, + throughput=56, + ), + ), + ), + ), + ], + ), + dict( + name="GigabitEthernet0/3", + address_family=[ + dict( + afi="ipv4", + bfd=True, + cost=dict(interface_cost=50), + priority=50, + ttl_security=dict(hops=150), + ), + ], + ), + ], + state="rendered", + ), + ) + commands = [ + "interface GigabitEthernet0/2", + "ip ospf bfd", + "ip ospf cost 30", + "ip ospf dead-interval 20 minimal hello-multiplier 100", + "ip ospf demand-circuit ignore", + "ip ospf multi-area 15 cost 20", + "ip ospf network broadcast", + "ip ospf priority 60", + "ip ospf resync-timeout 90", + "ip ospf ttl-security hops 120", + "ipv6 ospf bfd", + "ipv6 ospf cost 10", + "ipv6 ospf dead-interval 100", + "ipv6 ospf demand-circuit ignore", + "ipv6 ospf neighbor 10.0.2.15 cost 14 database-filter all out", + "ipv6 ospf network manet", + "ipv6 ospf priority 50", + "interface GigabitEthernet0/3", + "ip ospf bfd", + "ip ospf cost 50", + "ip ospf priority 50", + "ip ospf ttl-security hops 150", + ] + + result = self.execute_module(changed=False) + self.assertEqual(result["rendered"], commands) + + def test_ios_ospf_interfaces_parsed(self): + self.execute_show_command.return_value = dedent( + """\ + interface GigabitEthernet0/2 + ip ospf priority 40 + ip ospf adjacency stagger disable + ip ospf ttl-security hops 50 + ip ospf 10 area 20 + ip ospf cost 30 + interface GigabitEthernet0/3 + ipv6 ospf 55 area 105 + ipv6 ospf priority 20 + ipv6 ospf transmit-delay 30 + ipv6 ospf adjacency stagger disable + """, + ) + set_module_args( + dict( + running_config=dedent( + """\ + interface GigabitEthernet0/0 + ip ospf dead-interval 10 + interface GigabitEthernet0/1 + ip ospf network broadcast + ip ospf resync-timeout 10 + ip ospf dead-interval 5 + ip ospf priority 25 + ip ospf demand-circuit ignore + ip ospf bfd + ip ospf adjacency stagger disable + ip ospf ttl-security hops 50 + ip ospf shutdown + ip ospf 10 area 30 + ip ospf cost 5 + ipv6 ospf 35 area 45 + ipv6 ospf priority 55 + ipv6 ospf transmit-delay 45 + ipv6 ospf database-filter all out + ipv6 ospf adjacency stagger disable + ipv6 ospf manet peering link-metrics 10 + interface GigabitEthernet0/2 + ip ospf dead-interval minimal hello-multiplier 10 + """, + ), + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = [ + { + "name": "GigabitEthernet0/0", + "address_family": [{"afi": "ipv4", "dead_interval": {"time": 10}}], + }, + { + "name": "GigabitEthernet0/1", + "address_family": [ + { + "afi": "ipv4", + "network": {"broadcast": True}, + "resync_timeout": 10, + "dead_interval": {"time": 5}, + "priority": 25, + "demand_circuit": {"enable": True, "ignore": True}, + "bfd": True, + "adjacency": True, + "ttl_security": {"set": True, "hops": 50}, + "shutdown": True, + "process": {"id": 10, "area_id": "30"}, + "cost": {"interface_cost": 5}, + }, + { + "afi": "ipv6", + "process": {"id": 35, "area_id": "45"}, + "priority": 55, + "transmit_delay": 45, + "database_filter": True, + "adjacency": True, + "manet": {"link_metrics": {"cost_threshold": 10}}, + }, + ], + }, + { + "name": "GigabitEthernet0/2", + "address_family": [{"afi": "ipv4", "dead_interval": {"minimal": 10}}], + }, + ] + self.assertEqual(parsed_list, result["parsed"]) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospfv2.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospfv2.py new file mode 100644 index 000000000..ba6283f4c --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospfv2.py @@ -0,0 +1,943 @@ +# +# (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 ansible_collections.cisco.ios.plugins.modules import ios_ospfv2 +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosOspfV2Module(TestIosModule): + module = ios_ospfv2 + + def setUp(self): + super(TestIosOspfV2Module, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.ospfv2.ospfv2." + "Ospfv2Facts.get_ospfv2_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosOspfV2Module, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + return load_fixture("ios_ospfv2.cfg") + + self.execute_show_command.side_effect = load_from_file + + def test_ios_ospfv2_merged(self): + set_module_args( + dict( + config=dict( + processes=[ + dict( + process_id="100", + auto_cost=dict(reference_bandwidth="4"), + distribute_list=dict( + acls=[ + dict(direction="out", name="10"), + dict(direction="in", name="123"), + ], + prefix=dict( + direction="in", + name="123", + gateway_name="gateway", + interface="GigabitEthernet0/1", + protocol="icmp", + ), + ), + network=[ + dict(address="198.51.100.0", wildcard_bits="0.0.0.255", area=5), + dict(address="192.0.2.0", wildcard_bits="0.0.0.255", area=5), + ], + domain_id=dict(ip_address=dict(address="192.0.3.1")), + max_metric=dict(on_startup=dict(time=100), router_lsa=True), + passive_interfaces=dict( + interface=dict(set_interface=False, name=["GigabitEthernet0/2"]), + ), + vrf="blue", + ), + ], + ), + state="merged", + ), + ) + commands = [ + "router ospf 100 vrf blue", + "auto-cost reference-bandwidth 4", + "distribute-list 123 in", + "distribute-list 10 out", + "distribute-list prefix 123 gateway gateway in GigabitEthernet0/1 icmp", + "network 198.51.100.0 0.0.0.255 area 5", + "network 192.0.2.0 0.0.0.255 area 5", + "domain-id 192.0.3.1", + "no passive-interface GigabitEthernet0/2", + "max-metric router-lsa on-startup 100", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ospfv2_merged_specific_param(self): + set_module_args( + dict( + config={ + "processes": [ + { + "process_id": 1, + "router_id": "0.0.0.1", + "vrf": "vrf", + "areas": [{"area_id": 0}], + "capability": {"vrf_lite": True}, + }, + ], + }, + state="merged", + ), + ) + commands = ["router ospf 1 vrf vrf", "capability vrf-lite", "router-id 0.0.0.1"] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ospfv2_merged_idempotent(self): + set_module_args( + dict( + config=dict( + processes=[ + dict( + process_id="200", + auto_cost=dict(reference_bandwidth="4"), + distribute_list=dict( + acls=[ + dict(direction="out", name="10"), + dict(direction="in", name="123"), + ], + ), + domain_id=dict(ip_address=dict(address="192.0.3.1")), + max_metric=dict(on_startup=dict(time=100), router_lsa=True), + areas=[dict(area_id="10", capability=True)], + passive_interfaces=dict( + default=True, + interface=dict( + set_interface=False, + name=["GigabitEthernet0/1", "GigabitEthernet0/2"], + ), + ), + vrf="blue", + ), + ], + ), + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_ospfv2_replaced(self): + set_module_args( + dict( + config=dict( + processes=[ + dict( + process_id="200", + auto_cost=dict(reference_bandwidth="4"), + domain_id=dict(ip_address=dict(address="192.0.1.1")), + max_metric=dict(on_startup=dict(time=200), router_lsa=True), + areas=[dict(area_id="10", capability=True)], + vrf="blue", + ), + ], + ), + state="replaced", + ), + ) + commands = [ + "router ospf 200 vrf blue", + "no distribute-list 123 in", + "no distribute-list 10 out", + "domain-id 192.0.1.1", + "max-metric router-lsa on-startup 200", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ospfv2_replaced_idempotent(self): + set_module_args( + dict( + config=dict( + processes=[ + dict( + process_id="200", + auto_cost=dict(reference_bandwidth="4"), + distribute_list=dict( + acls=[ + dict(direction="out", name="10"), + dict(direction="in", name="123"), + ], + ), + domain_id=dict(ip_address=dict(address="192.0.3.1")), + max_metric=dict(on_startup=dict(time=100), router_lsa=True), + areas=[dict(area_id="10", capability=True)], + passive_interfaces=dict( + default=True, + interface=dict( + set_interface=False, + name=["GigabitEthernet0/1", "GigabitEthernet0/2"], + ), + ), + vrf="blue", + ), + ], + ), + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_ospfv2_overridden_idempotent(self): + set_module_args( + dict( + config=dict( + processes=[ + dict( + process_id="200", + auto_cost=dict(reference_bandwidth="4"), + distribute_list=dict( + acls=[ + dict(direction="out", name="10"), + dict(direction="in", name="123"), + ], + ), + domain_id=dict(ip_address=dict(address="192.0.3.1")), + max_metric=dict(on_startup=dict(time=100), router_lsa=True), + areas=[dict(area_id="10", capability=True)], + passive_interfaces=dict( + default=True, + interface=dict( + set_interface=False, + name=["GigabitEthernet0/1", "GigabitEthernet0/2"], + ), + ), + vrf="blue", + ), + ], + ), + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_ospfv2_overridden(self): + set_module_args( + dict( + config={ + "processes": [ + { + "address_family": { + "default": True, + "snmp_context": "snmp_test", + "topology": { + "base": True, + "name": "test_topology", + "tid": True, + }, + }, + "adjacency": { + "max_adjacency": 10, + "min_adjacency": 2, + "none": True, + }, + "areas": [ + { + "area_id": "5", + "authentication": {"enable": True, "message_digest": True}, + }, + { + "area_id": "10", + "authentication": {"enable": True, "message_digest": True}, + "capability": True, + "default_cost": 10, + "filter_list": [ + {"direction": "in", "name": "test_prefix_in"}, + {"direction": "out", "name": "test_prefix_out"}, + ], + "nssa": { + "default_information_originate": { + "metric": 10, + "metric_type": 1, + "nssa_only": True, + }, + "no_ext_capability": True, + "no_redistribution": True, + "no_summary": True, + "set": True, + "translate": "suppress-fa", + }, + "ranges": [ + { + "address": "172.16.1.0", + "advertise": True, + "cost": 20, + "netmask": "0.0.0.255", + "not_advertise": True, + }, + ], + "sham_link": { + "cost": 10, + "destination": "checkDestination", + "source": "checkSource", + "ttl_security": 20, + }, + "stub": { + "no_ext_capability": True, + "no_summary": True, + "set": True, + }, + }, + ], + "auto_cost": { + "reference_bandwidth": 50, + "set": True, + }, + "bfd": True, + "capability": { + "lls": True, + "opaque": True, + "transit": True, + "vrf_lite": True, + }, + "compatible": { + "rfc1583": True, + "rfc1587": True, + "rfc5243": True, + }, + "default_information": { + "always": True, + "metric": 25, + "metric_type": 26, + "originate": True, + "route_map": "rmap1", + }, + "default_metric": "50", + "discard_route": { + "external": 5, + "internal": 2, + "set": True, + }, + "distance": { + "admin_distance": { + "acl": "acl1", + "address": "192.168.1.0", + "distance": 2, + "wildcard_bits": "0.0.0.255", + }, + "ospf": { + "external": 1, + "inter_area": 2, + "intra_area": 3, + }, + }, + "domain_id": { + "ip_address": { + "address": "192.168.1.0", + "secondary": True, + }, + "null": True, + }, + "domain_tag": 54, + "event_log": { + "enable": True, + "one_shot": True, + "pause": True, + "size": 10, + }, + "help": True, + "ignore": True, + "interface_id": True, + "ispf": True, + "limit": { + "dc": { + "disable": True, + "number": 20, + }, + "non_dc": { + "disable": True, + "number": 10, + }, + }, + "local_rib_criteria": { + "enable": True, + "forwarding_address": True, + "inter_area_summary": True, + "nssa_translation": True, + }, + "log_adjacency_changes": {"detail": True}, + "max_lsa": { + "ignore_count": 10, + "ignore_time": 10, + "number": 10, + "reset_time": 10, + "threshold_value": 10, + "warning_only": True, + }, + "max_metric": { + "external_lsa": 10, + "include_stub": True, + "on_startup": {"time": 110, "wait_for_bgp": True}, + "router_lsa": True, + "summary_lsa": 20, + }, + "maximum_paths": 15, + "mpls": { + "ldp": { + "autoconfig": { + "area": "area1", + "set": True, + }, + "sync": True, + }, + "traffic_eng": { + "area": "area12", + "autoroute_exclude": 2, + "interface": { + "area": 13, + "interface_type": "0/1", + }, + "mesh_group": { + "area": "area14", + "id": 12, + "interface": "GigabitEthernet0/1", + }, + "multicast_intact": True, + "router_id_interface": "0/1", + }, + }, + "neighbor": { + "address": "172.16.1.0", + "cost": 2, + "database_filter": True, + "poll_interval": 20, + "priority": 10, + }, + "network": [ + { + "address": "198.51.100.0", + "area": "5", + "wildcard_bits": "0.0.0.255", + }, + ], + "nsf": { + "cisco": { + "disable": True, + "helper": True, + }, + "ietf": { + "disable": True, + "helper": True, + "strict_lsa_checking": True, + }, + }, + # "passive_interface": "GigabitEthernet0/1", + "passive_interfaces": { + "default": True, + "interface": { + "name": ["GigabitEthernet0/1", "GigabitEthernet0/2"], + "set_interface": False, + }, + }, + "prefix_suppression": True, + "priority": 10, + "process_id": 1, + "queue_depth": { + "hello": { + "max_packets": 10, + "unlimited": True, + }, + "update": { + "max_packets": 30, + "unlimited": True, + }, + }, + "router_id": "router1", + "shutdown": True, + "summary_address": { + "address": "172.16.1.0", + "mask": "0.0.0.255", + "not_advertise": True, + "nssa_only": True, + "tag": 12, + }, + "timers": { + "lsa": 12, + "pacing": { + "flood": 25, + "lsa_group": 15, + "retransmission": 30, + }, + "throttle": { + "lsa": { + "first_delay": 10, + "max_delay": 20, + "min_delay": 30, + }, + "spf": { + "between_delay": 10, + "max_delay": 20, + "receive_delay": 5, + }, + }, + }, + "traffic_share": True, + "ttl_security": {"hops": 12, "set": True}, + "vrf": "vrf1", + }, + ], + }, + state="overridden", + ), + ) + + commands = [ + "no router ospf 200 vrf blue", + "router ospf 1 vrf vrf1", + "adjacency stagger none 2", + "address-family ipv4 multicast", + "topology base", + "exit-address-family", + "auto-cost reference-bandwidth 50", + "bfd all-interfaces", + "capability lls", + "compatible rfc1583", + "default-information originate always metric 25 metric-type 26 route-map rmap1", + "default-metric 50", + "discard-route external 5 internal 2", + "domain-id 192.168.1.0 True", + "domain-tag 54", + "event-log one-shot pause size 10", + "help", + "ignore lsa mospf", + "interface-id snmp-if-index", + "ispf", + "limit retransmissions dc 20 dc disable non-dc 10 non-dc disable", + "local-rib-criteria forwarding-address inter-area-summary nssa-translation", + "log-adjacency-changes detail", + "max-lsa 10 10 ignore-count 10 ignore-time 10 reset-time 10 warning-only", + "max-metric router-lsa external-lsa 10 include-stub on-startup 110 summary-lsa 20", + "maximum-paths 15", + "neighbor 172.16.1.0 cost 2 database-filter all out poll-interval 20 priority 10", + "network 198.51.100.0 0.0.0.255 area 5", + "prefix-suppression", + "priority 10", + "router-id router1", + "shutdown", + "summary-address 172.16.1.0 0.0.0.255 not-advertise", + "traffic-share min across-interfaces", + "ttl-security all-interfaces hops 12", + "area 5 authentication message-digest", + "area 10 authentication message-digest", + "area 10 capability default-exclusion", + "area 10 default-cost 10", + "area 10 nssa translate type7 suppress-fa", + "area 10 nssa default-information-originate metric 10 metric-type 1 nssa-only no-ext-capability no-redistribution no-summary", + "area 10 range 172.16.1.0 0.0.0.255 advertise cost 20", + "area 10 sham-link checkSource checkDestination cost 10 ttl-security hops 20", + "area 10 stub no-ext-capability no-summary", + "area 10 filter-list prefix test_prefix_in in", + "area 10 filter-list prefix test_prefix_out out", + "passive-interface default", + "no passive-interface GigabitEthernet0/1", + "no passive-interface GigabitEthernet0/2", + ] + result = self.execute_module(changed=True) + self.assertEqual(commands, result["commands"]) + + def test_ios_ospfv2_deleted(self): + set_module_args( + dict(config=dict(processes=[dict(process_id="200", vrf="blue")]), state="deleted"), + ) + commands = ["no router ospf 200 vrf blue"] + self.execute_module(changed=True, commands=commands) + + def test_ios_ospfv2_parsed(self): + set_module_args( + dict( + running_config="router ospf 1\n area 5 authentication\n area 5 capability default-exclusion", + state="parsed", + ), + ) + result = self.execute_module(changed=False) + parsed_list = { + "processes": [ + { + "areas": [ + {"area_id": "5", "authentication": {"enable": True}, "capability": True}, + ], + "process_id": 1, + }, + ], + } + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_ospfv2_rendered(self): + set_module_args( + dict( + config={ + "processes": [ + { + "address_family": { + "default": True, + "snmp_context": "snmp_test", + "topology": { + "base": True, + "name": "test_topology", + "tid": True, + }, + }, + "adjacency": { + "max_adjacency": 10, + "min_adjacency": 2, + "none": True, + }, + "areas": [ + { + "area_id": "5", + "authentication": {"enable": True, "message_digest": True}, + }, + { + "area_id": "10", + "authentication": {"enable": True, "message_digest": True}, + "capability": True, + "default_cost": 10, + "filter_list": [ + {"direction": "in", "name": "test_prefix_in"}, + {"direction": "out", "name": "test_prefix_out"}, + ], + "nssa": { + "default_information_originate": { + "metric": 10, + "metric_type": 1, + "nssa_only": True, + }, + "no_ext_capability": True, + "no_redistribution": True, + "no_summary": True, + "set": True, + "translate": "suppress-fa", + }, + "ranges": [ + { + "address": "172.16.1.0", + "advertise": True, + "cost": 20, + "netmask": "0.0.0.255", + "not_advertise": True, + }, + ], + "sham_link": { + "cost": 10, + "destination": "checkDestination", + "source": "checkSource", + "ttl_security": 20, + }, + "stub": { + "no_ext_capability": True, + "no_summary": True, + "set": True, + }, + }, + ], + "auto_cost": { + "reference_bandwidth": 50, + "set": True, + }, + "bfd": True, + "capability": { + "lls": True, + "opaque": True, + "transit": True, + "vrf_lite": True, + }, + "compatible": { + "rfc1583": True, + "rfc1587": True, + "rfc5243": True, + }, + "default_information": { + "always": True, + "metric": 25, + "metric_type": 26, + "originate": True, + "route_map": "rmap1", + }, + "default_metric": "50", + "discard_route": { + "external": 5, + "internal": 2, + "set": True, + }, + "distance": { + "admin_distance": { + "acl": "acl1", + "address": "192.168.1.0", + "distance": 2, + "wildcard_bits": "0.0.0.255", + }, + "ospf": { + "external": 1, + "inter_area": 2, + "intra_area": 3, + }, + }, + "domain_id": { + "ip_address": { + "address": "192.168.1.0", + "secondary": True, + }, + "null": True, + }, + "domain_tag": 54, + "event_log": { + "enable": True, + "one_shot": True, + "pause": True, + "size": 10, + }, + "help": True, + "ignore": True, + "interface_id": True, + "ispf": True, + "limit": { + "dc": { + "disable": True, + "number": 20, + }, + "non_dc": { + "disable": True, + "number": 10, + }, + }, + "local_rib_criteria": { + "enable": True, + "forwarding_address": True, + "inter_area_summary": True, + "nssa_translation": True, + }, + "log_adjacency_changes": {"detail": True}, + "max_lsa": { + "ignore_count": 10, + "ignore_time": 10, + "number": 10, + "reset_time": 10, + "threshold_value": 10, + "warning_only": True, + }, + "max_metric": { + "external_lsa": 10, + "include_stub": True, + "on_startup": {"time": 110, "wait_for_bgp": True}, + "router_lsa": True, + "summary_lsa": 20, + }, + "maximum_paths": 15, + "mpls": { + "ldp": { + "autoconfig": { + "area": "area1", + "set": True, + }, + "sync": True, + }, + "traffic_eng": { + "area": "area12", + "autoroute_exclude": 2, + "interface": { + "area": 13, + "interface_type": "0/1", + }, + "mesh_group": { + "area": "area14", + "id": 12, + "interface": "GigabitEthernet0/1", + }, + "multicast_intact": True, + "router_id_interface": "0/1", + }, + }, + "neighbor": { + "address": "172.16.1.0", + "cost": 2, + "database_filter": True, + "poll_interval": 20, + "priority": 10, + }, + "network": [ + { + "address": "198.51.100.0", + "area": "5", + "wildcard_bits": "0.0.0.255", + }, + ], + "nsf": { + "cisco": { + "disable": True, + "helper": True, + }, + "ietf": { + "disable": True, + "helper": True, + "strict_lsa_checking": True, + }, + }, + # "passive_interface": "GigabitEthernet0/1", + "passive_interfaces": { + "default": True, + "interface": { + "name": ["GigabitEthernet0/1", "GigabitEthernet0/2"], + "set_interface": False, + }, + }, + "prefix_suppression": True, + "priority": 10, + "process_id": 1, + "queue_depth": { + "hello": { + "max_packets": 10, + "unlimited": True, + }, + "update": { + "max_packets": 30, + "unlimited": True, + }, + }, + "router_id": "router1", + "shutdown": True, + "summary_address": { + "address": "172.16.1.0", + "mask": "0.0.0.255", + "not_advertise": True, + "nssa_only": True, + "tag": 12, + }, + "timers": { + "lsa": 12, + "pacing": { + "flood": 25, + "lsa_group": 15, + "retransmission": 30, + }, + "throttle": { + "lsa": { + "first_delay": 10, + "max_delay": 20, + "min_delay": 30, + }, + "spf": { + "between_delay": 10, + "max_delay": 20, + "receive_delay": 5, + }, + }, + }, + "traffic_share": True, + "ttl_security": {"hops": 12, "set": True}, + "vrf": "vrf1", + }, + ], + }, + state="rendered", + ), + ) + commands = [ + "address-family ipv4 multicast", + "adjacency stagger none 2", + "area 10 authentication message-digest", + "area 10 capability default-exclusion", + "area 10 default-cost 10", + "area 10 filter-list prefix test_prefix_in in", + "area 10 filter-list prefix test_prefix_out out", + "area 10 nssa default-information-originate metric 10 metric-type 1 nssa-only no-ext-capability no-redistribution no-summary", + "area 10 nssa translate type7 suppress-fa", + "area 10 range 172.16.1.0 0.0.0.255 advertise cost 20", + "area 10 sham-link checkSource checkDestination cost 10 ttl-security hops 20", + "area 10 stub no-ext-capability no-summary", + "area 5 authentication message-digest", + "auto-cost reference-bandwidth 50", + "bfd all-interfaces", + "capability lls", + "compatible rfc1583", + "default-information originate always metric 25 metric-type 26 route-map rmap1", + "default-metric 50", + "discard-route external 5 internal 2", + "domain-id 192.168.1.0 True", + "domain-tag 54", + "event-log one-shot pause size 10", + "exit-address-family", + "help", + "ignore lsa mospf", + "interface-id snmp-if-index", + "ispf", + "limit retransmissions dc 20 dc disable non-dc 10 non-dc disable", + "local-rib-criteria forwarding-address inter-area-summary nssa-translation", + "log-adjacency-changes detail", + "max-lsa 10 10 ignore-count 10 ignore-time 10 reset-time 10 warning-only", + "max-metric router-lsa external-lsa 10 include-stub on-startup 110 summary-lsa 20", + "maximum-paths 15", + "neighbor 172.16.1.0 cost 2 database-filter all out poll-interval 20 priority 10", + "network 198.51.100.0 0.0.0.255 area 5", + "no passive-interface GigabitEthernet0/1", + "no passive-interface GigabitEthernet0/2", + "passive-interface default", + "prefix-suppression", + "priority 10", + "router ospf 1 vrf vrf1", + "router-id router1", + "shutdown", + "summary-address 172.16.1.0 0.0.0.255 not-advertise", + "topology base", + "traffic-share min across-interfaces", + "ttl-security all-interfaces hops 12", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), commands) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospfv3.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospfv3.py new file mode 100644 index 000000000..5403ef25d --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospfv3.py @@ -0,0 +1,785 @@ +# +# (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 ansible_collections.cisco.ios.plugins.modules import ios_ospfv3 +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosOspfV3Module(TestIosModule): + module = ios_ospfv3 + + def setUp(self): + super(TestIosOspfV3Module, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.ospfv3.ospfv3." + "Ospfv3Facts.get_ospfv3_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosOspfV3Module, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + return load_fixture("ios_ospfv3.cfg") + + self.execute_show_command.side_effect = load_from_file + + def test_ios_ospfv3_merged(self): + set_module_args( + dict( + config=dict( + processes=[ + dict( + process_id="1", + auto_cost=dict(reference_bandwidth="4"), + areas=[dict(area_id=10, default_cost=10)], + address_family=[ + dict( + afi="ipv4", + unicast=True, + vrf="blue", + adjacency=dict(min_adjacency=100, max_adjacency=100), + ), + ], + ), + ], + ), + state="merged", + ), + ) + commands = [ + "router ospfv3 1", + "auto-cost reference-bandwidth 4", + "area 10 default-cost 10", + "address-family ipv4 unicast vrf blue", + "adjacency stagger 100 100", + "exit-address-family", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_ospfv3_merged_idempotent(self): + set_module_args( + dict( + config=dict( + processes=[ + dict( + process_id="1", + max_metric=dict(router_lsa=True, on_startup=dict(time=110)), + areas=[ + dict( + area_id=10, + nssa=dict(default_information_originate=dict(metric=10)), + ), + ], + address_family=[ + dict( + afi="ipv4", + unicast=True, + vrf="blue", + adjacency=dict(min_adjacency=50, max_adjacency=50), + areas=[ + dict( + area_id=25, + nssa=dict( + default_information_originate=dict( + metric=25, + nssa_only=True, + ), + ), + ), + ], + ), + ], + ), + ], + ), + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_ospfv3_replaced(self): + set_module_args( + dict( + config=dict( + processes=[ + dict( + process_id="1", + max_metric=dict(router_lsa=True, on_startup=dict(time=100)), + address_family=[ + dict( + afi="ipv4", + unicast=True, + vrf="blue", + adjacency=dict(min_adjacency=100, max_adjacency=100), + ), + ], + ), + ], + ), + state="replaced", + ), + ) + commands = [ + "router ospfv3 1", + "max-metric router-lsa on-startup 100", + "no area 10 nssa default-information-originate metric 10", + "address-family ipv4 unicast vrf blue", + "adjacency stagger 100 100", + "exit-address-family", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + # + def test_ios_ospfv3_replaced_idempotent(self): + set_module_args( + dict( + config=dict( + processes=[ + dict( + process_id="1", + max_metric=dict(router_lsa=True, on_startup=dict(time=110)), + areas=[ + dict( + area_id=10, + nssa=dict(default_information_originate=dict(metric=10)), + ), + ], + address_family=[ + dict( + afi="ipv4", + unicast=True, + vrf="blue", + adjacency=dict(min_adjacency=50, max_adjacency=50), + areas=[ + dict( + area_id=25, + nssa=dict( + default_information_originate=dict( + metric=25, + nssa_only=True, + ), + ), + ), + ], + ), + ], + ), + ], + ), + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_ospfv3_overridden(self): + set_module_args( + dict( + config=dict( + processes=[ + dict( + process_id="200", + max_metric=dict(router_lsa=True, on_startup=dict(time=200)), + areas=[ + dict( + area_id=10, + nssa=dict(default_information_originate=dict(metric=10)), + ), + ], + address_family=[ + dict( + afi="ipv4", + unicast=True, + adjacency=dict(min_adjacency=50, max_adjacency=50), + areas=[ + dict( + area_id=200, + nssa=dict( + default_information_originate=dict( + metric=200, + nssa_only=True, + ), + ), + ), + ], + ), + ], + ), + ], + ), + state="overridden", + ), + ) + + commands = [ + "no router ospfv3 1", + "router ospfv3 200", + "max-metric router-lsa on-startup 200", + "area 10 nssa default-information-originate metric 10", + "address-family ipv4 unicast", + "adjacency stagger 50 50", + "area 200 nssa default-information-originate metric 200 nssa-only", + "exit-address-family", + ] + self.execute_module(changed=True, commands=commands) + + def test_ios_ospfv3_overridden_idempotent(self): + set_module_args( + dict( + config=dict( + processes=[ + dict( + process_id="1", + max_metric=dict(router_lsa=True, on_startup=dict(time=110)), + areas=[ + dict( + area_id=10, + nssa=dict(default_information_originate=dict(metric=10)), + ), + ], + address_family=[ + dict( + afi="ipv4", + unicast=True, + vrf="blue", + adjacency=dict(min_adjacency=50, max_adjacency=50), + areas=[ + dict( + area_id=25, + nssa=dict( + default_information_originate=dict( + metric=25, + nssa_only=True, + ), + ), + ), + ], + ), + ], + ), + ], + ), + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_ospfv3_deleted(self): + set_module_args(dict(config=dict(processes=[dict(process_id="1")]), state="deleted")) + commands = ["no router ospfv3 1"] + self.execute_module(changed=True, commands=commands) + + def test_ios_ospfv3_parsed(self): + set_module_args(dict(running_config="router ospfv3 1\n area 5", state="parsed")) + result = self.execute_module(changed=False) + parsed_list = {"processes": [{"areas": [{"area_id": "5"}], "process_id": 1}]} + self.assertEqual(parsed_list, result["parsed"]) + + def test_ios_ospfv3_rendered(self): + set_module_args( + dict( + config=dict( + { + "processes": [ + { + "address_family": [ + { + "adjacency": { + "disable": True, + "max_adjacency": 25, + "min_adjacency": 25, + "none": True, + }, + "afi": "ipv4", + "areas": [ + { + "area_id": "25", + "authentication": { + "null": True, + }, + "default_cost": 10, + # "filter_list": [ + # { + # "name": "flist1", + # "direction": "in", + # } + # ], + "normal": True, + "nssa": { + "default_information_originate": { + "metric": 25, + "metric_type": 1, + "nssa_only": True, + }, + "no_redistribution": True, + "no_summary": True, + "set": True, + "translate": "always", + }, + # "ranges": [ + # { + # "address": "172.16.1.0", + # "netmask": "0.0.0.255", + # "advertise": True, + # "cost": 150, + # "not_advertise": True, + # } + # ], + "sham_link": { + "source": "demoSource", + "destination": "demoDest", + "authentication": { + "key_chain": "mykey", + "null": True, + }, + "cost": 10, + "ttl_security": 20, + }, + "stub": { + "set": True, + "no_summary": True, + }, + }, + ], + "authentication": { + "deployment": True, + "normal": True, + }, + "auto_cost": { + "set": True, + "reference_bandwidth": 10, + }, + "bfd": { + "all_interfaces": True, + "disable": True, + }, + # "capability": True, + "compatible": { + "rfc1583": True, + "rfc1587": True, + "rfc5243": True, + }, + "default_information": { + "originate": True, + "always": True, + "metric": 10, + "metric_type": 100, + "route_map": "rmap1", + }, + "default_metric": 10, + "discard_route": { + "sham_link": True, + "external": True, + "internal": True, + }, + "distance": 10, + "distribute_list": { + # "acls": [ + # { + # "name": "testAcl", + # "direction": "in", + # "interface": "GigabitEthernet0/1", + # "protocol": "tcp", + # } + # ], + "prefix": { + "name": "Prefixlist1", + "gateway_name": "gateway1", + "direction": "in", + "interface": "GigabitEthernet0/1", + "protocol": "tcp", + }, + "route_map": {"name": "ramp1"}, + }, + "event_log": { + "enable": True, + "one_shot": True, + "pause": True, + "size": 200, + }, + "graceful_restart": { + "enable": True, + "disable": True, + "strict_lsa_checking": True, + }, + "interface_id": { + "ios_if_index": True, + "snmp_if_index": True, + }, + # "limit": { + # "dc": { + # "number": {"type": 20}, + # "disable": {"type": True}, + # }, + # "non_dc": { + # "number": {"type": 20}, + # "disable": {"type": True}, + # }, + # }, + "local_rib_criteria": { + "enable": True, + "forwarding_address": True, + "inter_area_summary": True, + "nssa_translation": True, + }, + "log_adjacency_changes": { + "set": True, + "detail": True, + }, + "manet": { + "cache": { + "acknowledgement": 10, + "update": 20, + }, + "hello": { + "multicast": True, + "unicast": True, + }, + "peering": { + "set": True, + "disable": True, + "per_interface": True, + "redundancy": 10, + }, + "willingness": 10, + }, + "max_lsa": { + "number": 10, + "threshold_value": 20, + "ignore_count": 30, + "ignore_time": 5, + "reset_time": 5, + "warning_only": True, + }, + "max_metric": { + "disable": True, + "external_lsa": 20, + "inter_area_lsas": 30, + "on_startup": { + "time": 15, + "wait_for_bgp": True, + }, + "stub_prefix_lsa": True, + }, + "maximum_paths": 35, + "passive_interface": "gigEth0/1", + "prefix_suppression": { + "enable": True, + }, + "queue_depth": { + "hello": { + "max_packets": 10, + "unlimited": True, + }, + "update": { + "max_packets": 20, + "unlimited": True, + }, + }, + "router_id": "router2", + "shutdown": { + "enable": True, + }, + "summary_prefix": { + "address": "172.16.1.0", + "mask": "0.0.0.255", + "not_advertise": True, + "nssa_only": True, + "tag": 15, + }, + "timers": { + "lsa": 10, + "manet": { + "cache": { + "acknowledgement": 20, + "redundancy": 30, + }, + "hello": True, + "peering": { + "set": True, + "per_interface": True, + "redundancy": 40, + }, + "willingness": 30, + }, + "pacing": { + "flood": 10, + "lsa_group": 20, + "retransmission": 30, + }, + "throttle": { + "lsa": { + "first_delay": 10, + "min_delay": 20, + "max_delay": 30, + }, + "spf": { + "receive_delay": 20, + "between_delay": 25, + "max_delay": 35, + }, + }, + }, + "unicast": True, + "vrf": "ospf_vrf", + }, + ], + "adjacency": { + "min_adjacency": 10, + "max_adjacency": 20, + "none": True, + }, + "areas": [ + { + "area_id": "10", + "authentication": { + "key_chain": "topsecretpass", + "ipsec": { + "spi": 10, + "md5": 10, + "sha1": 20, + "hex_string": "736563726574506173736f777264", + }, + }, + "default_cost": 50, + "nssa": { + "default_information_originate": { + "metric": 10, + "metric_type": 2, + "nssa_only": True, + }, + "no_redistribution": True, + "no_summary": True, + "set": True, + "translate": "always", + }, + "stub": { + "set": True, + "no_summary": True, + }, + }, + ], + "authentication": True, + "auto_cost": { + "set": True, + "reference_bandwidth": 10, + }, + "bfd": True, + "compatible": { + "rfc1583": True, + "rfc1587": True, + "rfc5243": True, + }, + "event_log": { + "enable": True, + "one_shot": True, + "pause": True, + "size": True, + }, + "graceful_restart": { + "disable": True, + "strict_lsa_checking": True, + }, + "help": True, + "interface_id": True, + # "limit": { + # "dc": { + # "number": 10, + # "disable": True, + # }, + # "non_dc": { + # "number": 10, + # "disable": True, + # }, + # }, + "local_rib_criteria": { + "enable": True, + "forwarding_address": True, + "inter_area_summary": True, + "nssa_translation": True, + }, + "log_adjacency_changes": {"set": True, "detail": True}, + "manet": { + "cache": { + "acknowledgement": 10, + "redundancy": 20, + }, + "hello": True, + "peering": { + "set": True, + "per_interface": True, + "redundancy": 20, + }, + # "willingness": "test", + }, + "max_lsa": { + "number": 10, + "threshold_value": 10, + "ignore_count": 10, + "ignore_time": 10, + "reset_time": 10, + "warning_only": True, + }, + "max_metric": { + "external_lsa": 10, + "include_stub": True, + "on_startup": {"time": 110, "wait_for_bgp": True}, + "router_lsa": True, + "summary_lsa": 10, + }, + "passive_interface": "GigabitEthernet0/1", + "prefix_suppression": True, + "process_id": 1, + "queue_depth": { + "hello": { + "max_packets": 20, + "unlimited": True, + }, + }, + "router_id": "router1", + "shutdown": True, + "timers": { + "lsa": 10, + "manet": { + "cache": { + "acknowledgement": 10, + "redundancy": 20, + }, + "hello": True, + "peering": { + "set": True, + "per_interface": True, + "redundancy": 10, + }, + "willingness": 20, + }, + "pacing": { + "flood": 20, + "lsa_group": 20, + "retransmission": 20, + }, + "throttle": { + "lsa": { + "first_delay": 30, + "min_delay": 10, + "max_delay": 50, + }, + "spf": { + "receive_delay": 10, + "between_delay": 5, + "max_delay": 20, + }, + }, + }, + }, + ], + }, + ), + state="rendered", + ), + ) + commands = [ + "router ospfv3 1", + "adjacency stagger none 10", + "auto-cost reference-bandwidth 10", + "bfd all-interfaces", + "compatible rfc1583", + "event-log one-shot pause size True", + "help", + "interface-id snmp-if-index", + "local-rib-criteria forwarding-address inter-area-summary nssa-translation", + "log-adjacency-changes detail", + "manet cache acknowledgement 10", + "manet hello", + "manet peering selective per-interface redundancy 20", + "max-lsa 10 10 ignore-count 10 ignore-time 10 reset-time 10 warning-only", + "max-metric router-lsa external-lsa 10 include-stub on-startup 110 summary-lsa 10", + "passive-interface GigabitEthernet0/1", + "prefix-suppression", + "router-id router1", + "shutdown", + "area 10 authentication", + "area 10 default-cost 50", + "area 10 nssa default-information-originate metric 10 metric-type 2 nssa-only no-redistribution no-summary", + "area 10 nssa translate type7 always", + "area 10 stub no-summary", + "address-family ipv4 unicast vrf ospf_vrf", + "adjacency stagger none 25", + "auto-cost reference-bandwidth 10", + "bfd all-interfaces", + "compatible rfc1583", + "default-information originate always metric 10 metric-type 100 route-map rmap1", + "default-metric 10", + "distribute-list prefix Prefixlist1 gateway gateway1 in GigabitEthernet0/1 tcp", + "distribute-list route-map ramp1 in", + "event-log one-shot pause size 200", + "graceful_restart True disable", + "interface-id snmp-if-index", + "local-rib-criteria forwarding-address inter-area-summary nssa-translation", + "log-adjacency-changes detail", + "max-lsa 10 20 ignore-count 30 ignore-time 5 reset-time 5 warning-only", + "max-lsa 10 20 ignore-count 30 ignore-time 5 reset-time 5 warning-only", + "max-metric external-lsa 20 on-startup 15", + "maximum-paths 35", + "passive-interface gigEth0/1", + "prefix-suppression", + "router-id router2", + "shutdown", + "summary-prefix 172.16.1.0 0.0.0.255 not-advertise", + "area 25 authentication", + "area 25 default-cost 10", + "area 25 nssa default-information-originate metric 25 metric-type 1 nssa-only no-redistribution no-summary", + "area 25 nssa translate type7 always", + "area 25 sham-link demoSource demoDest cost 10 ttl-security hops 20", + "area 25 stub no-summary", + "exit-address-family", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ping.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ping.py new file mode 100644 index 000000000..7e7035eae --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ping.py @@ -0,0 +1,149 @@ +# +# (c) 2022, 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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_ping +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosPingModule(TestIosModule): + module = ios_ping + + def setUp(self): + super(TestIosPingModule, self).setUp() + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.config.ping.ping.Ping.run_command", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosPingModule, self).tearDown() + self.mock_execute_show_command.stop() + + def test_ios_ping_count(self): + self.execute_show_command.return_value = dedent( + """\ + Type escape sequence to abort. + ending 2, 100-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds: + ! + Success rate is 100 percent (2/2), round-trip min/avg/max = 25/25/25 ms + """, + ) + set_module_args(dict(count=2, dest="8.8.8.8")) + result = self.execute_module() + mock_res = { + "commands": "ping ip 8.8.8.8 repeat 2", + "packet_loss": "0%", + "packets_rx": 2, + "packets_tx": 2, + "rtt": {"min": 25, "avg": 25, "max": 25}, + "changed": False, + } + self.assertEqual(result, mock_res) + + def test_ios_ping_v6(self): + self.execute_show_command.return_value = dedent( + """\ + Type escape sequence to abort. + ending 2, 100-byte ICMP Echos to 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff, timeout is 2 seconds: + ! + Success rate is 100 percent (2/2), round-trip min/avg/max = 25/25/25 ms + """, + ) + set_module_args(dict(count=2, dest="2001:db8:ffff:ffff:ffff:ffff:ffff:ffff", afi="ipv6")) + result = self.execute_module() + mock_res = { + "commands": "ping ipv6 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff repeat 2", + "packet_loss": "0%", + "packets_rx": 2, + "packets_tx": 2, + "rtt": {"min": 25, "avg": 25, "max": 25}, + "changed": False, + } + self.assertEqual(result, mock_res) + + def test_ios_ping_options_all(self): + self.execute_show_command.return_value = dedent( + """\ + Type escape sequence to abort. + ending 2, 100-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds: + ! + Success rate is 100 percent (2/2), round-trip min/avg/max = 25/25/25 ms + """, + ) + set_module_args( + { + "afi": "ip", + "count": 4, + "dest": "8.8.8.8", + "df_bit": True, + "source": "Loopback88", + "state": "present", + "vrf": "DummyVrf", + }, + ) + result = self.execute_module() + mock_res = { + "commands": "ping vrf DummyVrf ip 8.8.8.8 repeat 4 df-bit source Loopback88", + "packet_loss": "0%", + "packets_rx": 2, + "packets_tx": 2, + "rtt": {"min": 25, "avg": 25, "max": 25}, + "changed": False, + } + self.assertEqual(result, mock_res) + + def test_ios_ping_state_absent_pass(self): + self.execute_show_command.return_value = dedent( + """\ + Type escape sequence to abort. + ending 2, 100-byte ICMP Echos to 8.8.8.8, timeout is 2 seconds: + ! + Success rate is 90 percent (2/2), round-trip min/avg/max = 25/25/25 ms + """, + ) + set_module_args(dict(count=2, dest="8.8.8.8", state="absent")) + result = self.execute_module(failed=True) + mock_res = { + "msg": "Ping succeeded unexpectedly", + "commands": "ping ip 8.8.8.8 repeat 2", + "packet_loss": "10%", + "packets_rx": 2, + "packets_tx": 2, + "rtt": {"min": 25, "avg": 25, "max": 25}, + "failed": True, + } + self.assertEqual(result, mock_res) + + def test_ios_ping_state_absent_present_fail(self): + self.execute_show_command.return_value = dedent( + """\ + Type escape sequence to abort. + ending 2, 100-byte ICMP Echos to 8.8.8.8, timeout is 12 seconds: + ! + Success rate is 0 percent (0/2), round-trip min/avg/max = 25/25/25 ms + """, + ) + set_module_args(dict(count=2, dest="8.8.8.8", state="present")) + result = self.execute_module(failed=True) + mock_res = { + "msg": "Ping failed unexpectedly", + "commands": "ping ip 8.8.8.8 repeat 2", + "packet_loss": "100%", + "packets_rx": 0, + "packets_tx": 2, + "rtt": {"min": 25, "avg": 25, "max": 25}, + "failed": True, + } + self.assertEqual(result, mock_res) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_prefix_lists.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_prefix_lists.py new file mode 100644 index 000000000..29a4f4c24 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_prefix_lists.py @@ -0,0 +1,557 @@ +# +# (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 ansible_collections.cisco.ios.plugins.modules import ios_prefix_lists +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosPrefixListsModule(TestIosModule): + module = ios_prefix_lists + + def setUp(self): + super(TestIosPrefixListsModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.prefix_lists.prefix_lists." + "Prefix_listsFacts.get_prefix_list_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosPrefixListsModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + return load_fixture("ios_prefix_lists.cfg") + + self.execute_show_command.side_effect = load_from_file + + def test_ios_prefix_lists_merged(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + prefix_lists=[ + dict( + description="this is merge test", + entries=[ + dict( + action="deny", + ge=10, + le=15, + prefix="25.0.0.0/8", + sequence=25, + ), + ], + name="10", + ), + dict( + description="this is for prefix-list", + entries=[ + dict( + action="deny", + ge=10, + le=15, + prefix="35.0.0.0/8", + sequence=5, + ), + dict(action="deny", ge=20, prefix="35.0.0.0/8", sequence=10), + ], + name="test_prefix", + ), + ], + ), + dict( + afi="ipv6", + prefix_lists=[ + dict( + description="this is merged ipv6 prefix-list", + entries=[ + dict( + action="deny", + ge=80, + le=100, + prefix="2001:DB8:0:4::/64", + sequence=20, + ), + ], + name="test_ipv6", + ), + ], + ), + ], + state="merged", + ), + ) + commands = [ + "ip prefix-list 10 description this is merge test", + "ip prefix-list 10 seq 25 deny 25.0.0.0/8 ge 10 le 15", + "ipv6 prefix-list test_ipv6 description this is merged ipv6 prefix-list", + "ipv6 prefix-list test_ipv6 seq 20 deny 2001:DB8:0:4::/64 ge 80 le 100", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_prefix_lists_merged_idempotent(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + prefix_lists=[ + dict( + description="this is test description", + entries=[ + dict(action="deny", le=15, prefix="1.0.0.0/8", sequence=5), + dict(action="deny", ge=10, prefix="35.0.0.0/8", sequence=10), + dict(action="deny", ge=15, prefix="12.0.0.0/8", sequence=15), + dict( + action="deny", + ge=20, + le=21, + prefix="14.0.0.0/8", + sequence=20, + ), + ], + name="10", + ), + dict( + description="this is test", + entries=[ + dict(action="deny", ge=15, prefix="12.0.0.0/8", sequence=50), + ], + name="test", + ), + dict( + description="this is for prefix-list", + entries=[ + dict( + action="deny", + ge=10, + le=15, + prefix="35.0.0.0/8", + sequence=5, + ), + dict(action="deny", ge=20, prefix="35.0.0.0/8", sequence=10), + ], + name="test_prefix", + ), + ], + ), + dict( + afi="ipv6", + prefix_lists=[ + dict( + description="this is ipv6 prefix-list", + entries=[ + dict( + action="deny", + ge=80, + prefix="2001:DB8:0:4::/64", + sequence=10, + ), + ], + name="test_ipv6", + ), + ], + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_prefix_lists_replaced(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + prefix_lists=[ + dict( + description="this is replace test", + entries=[ + dict(action="deny", ge=15, prefix="12.0.0.0/8", sequence=15), + dict( + action="deny", + ge=20, + le=21, + prefix="14.0.0.0/8", + sequence=20, + ), + ], + name="10", + ), + dict( + description="this is replace test", + entries=[ + dict(action="deny", ge=20, prefix="35.0.0.0/8", sequence=10), + ], + name="test_replace", + ), + ], + ), + dict( + afi="ipv6", + prefix_lists=[ + dict( + description="this is ipv6 replace test", + entries=[ + dict( + action="deny", + ge=80, + le=100, + prefix="2001:DB8:0:4::/64", + sequence=10, + ), + ], + name="test_ipv6", + ), + ], + ), + ], + state="replaced", + ), + ) + commands = [ + "ip prefix-list 10 description this is replace test", + "no ip prefix-list 10 seq 10 deny 35.0.0.0/8 ge 10", + "no ip prefix-list 10 seq 5 deny 1.0.0.0/8 le 15", + "ip prefix-list test_replace seq 10 deny 35.0.0.0/8 ge 20", + "ip prefix-list test_replace description this is replace test", + "no ipv6 prefix-list test_ipv6 seq 10 deny 2001:DB8:0:4::/64 ge 80", + "ipv6 prefix-list test_ipv6 seq 10 deny 2001:DB8:0:4::/64 ge 80 le 100", + "ipv6 prefix-list test_ipv6 description this is ipv6 replace test", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_prefix_lists_replaced_idempotent(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + prefix_lists=[ + dict( + description="this is test description", + entries=[ + dict(action="deny", le=15, prefix="1.0.0.0/8", sequence=5), + dict(action="deny", ge=10, prefix="35.0.0.0/8", sequence=10), + dict(action="deny", ge=15, prefix="12.0.0.0/8", sequence=15), + dict( + action="deny", + ge=20, + le=21, + prefix="14.0.0.0/8", + sequence=20, + ), + ], + name="10", + ), + dict( + description="this is test", + entries=[ + dict(action="deny", ge=15, prefix="12.0.0.0/8", sequence=50), + ], + name="test", + ), + dict( + description="this is for prefix-list", + entries=[ + dict( + action="deny", + ge=10, + le=15, + prefix="35.0.0.0/8", + sequence=5, + ), + dict(action="deny", ge=20, prefix="35.0.0.0/8", sequence=10), + ], + name="test_prefix", + ), + ], + ), + dict( + afi="ipv6", + prefix_lists=[ + dict( + description="this is ipv6 prefix-list", + entries=[ + dict( + action="deny", + ge=80, + prefix="2001:DB8:0:4::/64", + sequence=10, + ), + ], + name="test_ipv6", + ), + ], + ), + ], + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_prefix_lists_overridden(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + prefix_lists=[ + dict( + description="this is override test", + entries=[ + dict(action="deny", ge=15, prefix="12.0.0.0/8", sequence=15), + dict( + action="deny", + ge=20, + le=21, + prefix="14.0.0.0/8", + sequence=20, + ), + ], + name="10", + ), + dict( + description="this is override test", + entries=[ + dict(action="deny", ge=20, prefix="35.0.0.0/8", sequence=10), + ], + name="test_override", + ), + ], + ), + dict( + afi="ipv6", + prefix_lists=[ + dict( + description="this is ipv6 override test", + entries=[ + dict( + action="deny", + ge=80, + le=100, + prefix="2001:DB8:0:4::/64", + sequence=10, + ), + ], + name="test_ipv6", + ), + ], + ), + ], + state="overridden", + ), + ) + commands = [ + "no ip prefix-list test", + "no ip prefix-list test_prefix", + "ip prefix-list 10 description this is override test", + "no ip prefix-list 10 seq 10 deny 35.0.0.0/8 ge 10", + "no ip prefix-list 10 seq 5 deny 1.0.0.0/8 le 15", + "ip prefix-list test_override seq 10 deny 35.0.0.0/8 ge 20", + "ip prefix-list test_override description this is override test", + "no ipv6 prefix-list test_ipv6 seq 10 deny 2001:DB8:0:4::/64 ge 80", + "ipv6 prefix-list test_ipv6 seq 10 deny 2001:DB8:0:4::/64 ge 80 le 100", + "ipv6 prefix-list test_ipv6 description this is ipv6 override test", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_prefix_lists_overridden_idempotent(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + prefix_lists=[ + dict( + description="this is test description", + entries=[ + dict(action="deny", le=15, prefix="1.0.0.0/8", sequence=5), + dict(action="deny", ge=10, prefix="35.0.0.0/8", sequence=10), + dict(action="deny", ge=15, prefix="12.0.0.0/8", sequence=15), + dict( + action="deny", + ge=20, + le=21, + prefix="14.0.0.0/8", + sequence=20, + ), + ], + name="10", + ), + dict( + description="this is test", + entries=[ + dict(action="deny", ge=15, prefix="12.0.0.0/8", sequence=50), + ], + name="test", + ), + dict( + description="this is for prefix-list", + entries=[ + dict( + action="deny", + ge=10, + le=15, + prefix="35.0.0.0/8", + sequence=5, + ), + dict(action="deny", ge=20, prefix="35.0.0.0/8", sequence=10), + ], + name="test_prefix", + ), + ], + ), + dict( + afi="ipv6", + prefix_lists=[ + dict( + description="this is ipv6 prefix-list", + entries=[ + dict( + action="deny", + ge=80, + prefix="2001:DB8:0:4::/64", + sequence=10, + ), + ], + name="test_ipv6", + ), + ], + ), + ], + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_prefix_lists_delete_without_config(self): + set_module_args(dict(state="deleted")) + commands = [ + "no ip prefix-list test", + "no ip prefix-list 10", + "no ip prefix-list test_prefix", + "no ipv6 prefix-list test_ipv6", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_prefix_lists_rendered(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + prefix_lists=[ + dict( + description="this is merge test", + entries=[ + dict( + action="deny", + ge=10, + le=15, + prefix="25.0.0.0/8", + sequence=25, + ), + ], + name="10", + ), + dict( + description="this is for prefix-list", + entries=[ + dict( + action="deny", + ge=10, + le=15, + prefix="35.0.0.0/8", + sequence=5, + ), + dict(action="deny", ge=20, prefix="35.0.0.0/8", sequence=10), + ], + name="test_prefix", + ), + ], + ), + dict( + afi="ipv6", + prefix_lists=[ + dict( + description="this is ipv6 prefix-list", + entries=[ + dict( + action="deny", + ge=80, + le=100, + prefix="2001:DB8:0:4::/64", + sequence=10, + ), + ], + name="test_ipv6", + ), + ], + ), + ], + state="rendered", + ), + ) + commands = [ + "ip prefix-list 10 description this is merge test", + "ip prefix-list 10 seq 25 deny 25.0.0.0/8 ge 10 le 15", + "ip prefix-list test_prefix description this is for prefix-list", + "ip prefix-list test_prefix seq 5 deny 35.0.0.0/8 ge 10 le 15", + "ip prefix-list test_prefix seq 10 deny 35.0.0.0/8 ge 20", + "ipv6 prefix-list test_ipv6 description this is ipv6 prefix-list", + "ipv6 prefix-list test_ipv6 seq 10 deny 2001:DB8:0:4::/64 ge 80 le 100", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_route_maps.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_route_maps.py new file mode 100644 index 000000000..c0600290c --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_route_maps.py @@ -0,0 +1,820 @@ +# +# (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 ansible_collections.cisco.ios.plugins.modules import ios_route_maps +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosRouteMapsModule(TestIosModule): + module = ios_route_maps + + def setUp(self): + super(TestIosRouteMapsModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.route_maps.route_maps." + "Route_mapsFacts.get_route_maps_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosRouteMapsModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + return load_fixture("ios_route_maps.cfg") + + self.execute_show_command.side_effect = load_from_file + + def test_ios_route_maps_merged(self): + set_module_args( + dict( + config=[ + dict( + entries=[ + dict( + action="deny", + continue_entry=dict(entry_sequence=20), + description="this is merge test", + match=dict( + additional_paths=dict(all=True), + as_path=dict(acls=[100, 120]), + clns=dict(address="test_osi"), + community=dict(exact_match=True, name=["new_merge"]), + ip=dict(address=dict(acls=[10, 100])), + length=dict(maximum=50000, minimum=5000), + mpls_label=True, + policy_lists=["ip_policy"], + route_type=dict( + external=dict(type_1=True), + nssa_external=dict(type_1=True), + ), + ), + set=dict( + dampening=dict( + penalty_half_time=10, + reuse_route_val=100, + suppress_route_val=100, + max_suppress=10, + ), + extcomm_list="test_excomm", + extcommunity=dict( + vpn_distinguisher=dict(address="192.0.2.1:12"), + ), + ip=dict( + address="192.0.2.1", + df=1, + next_hop=dict( + recursive=dict( + global_route=True, + address="198.51.110.1", + ), + verify_availability=dict( + address="198.51.111.1", + sequence=100, + track=10, + ), + ), + precedence=dict(critical=True), + ), + traffic_index=10, + weight=100, + ), + sequence=10, + ), + ], + route_map="test_1", + ), + dict( + entries=[ + dict( + action="deny", + match=dict( + ipv6=dict( + address=dict(acl="test_ip_acl"), + next_hop=dict(prefix_list="test_new"), + route_source=dict(acl="route_src_acl"), + ), + security_group=dict(source=[10, 20]), + local_preference=dict(value=[55, 105]), + mpls_label=True, + ), + sequence=10, + ), + ], + route_map="test_2", + ), + ], + state="merged", + ), + ) + commands = [ + "route-map test_1 deny 10", + "continue 20", + "description this is merge test", + "match community 98 99 new_merge test_1 test_2 exact-match", + "match length 5000 50000", + "set dampening 10 100 100 10", + "set extcomm-list test_excomm delete", + "set extcommunity vpn-distinguisher 192.0.2.1:12", + "set ip address prefix-list 192.0.2.1", + "set ip df 1", + "set ip next-hop verify-availability 198.51.111.1 100 track 10", + "set ip next-hop recursive global 198.51.110.1", + "set ip precedence critical", + "set weight 100", + "set traffic-index 10", + "route-map test_2 deny 10", + "match ipv6 address test_ip_acl", + "match ipv6 next-hop prefix-list test_new", + "match ipv6 route-source route_src_acl", + "match security-group source tag 10 20", + "match local-preference 105 55", + "match mpls-label", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_route_maps_merged_idempotent(self): + set_module_args( + dict( + config=[ + dict( + entries=[ + dict( + action="deny", + continue_entry=dict(entry_sequence=100), + description="this is test", + match=dict( + additional_paths=dict(all=True), + as_path=dict(acls=[100, 120]), + clns=dict(address="test_osi"), + community=dict( + exact_match=True, + name=["99", "98", "test_1", "test_2"], + ), + extcommunity=["110", "130"], + interfaces=["GigabitEthernet0/1"], + ip=dict(address=dict(acls=[10, 100])), + ipv6=dict(route_source=dict(acl="test_ipv6")), + length=dict(maximum=10000, minimum=1000), + local_preference=dict(value=[100]), + mdt_group=dict(acls=["25", "30"]), + metric=dict(external=True, value=100), + mpls_label=True, + policy_lists=["ip_policy"], + route_type=dict( + external=dict(type_1=True), + nssa_external=dict(type_1=True), + ), + rpki=dict(invalid=True), + security_group=dict(destination=[100]), + tag=dict(tag_list=["test_tag"]), + track=100, + ), + sequence=10, + ), + dict( + action="deny", + sequence=20, + set=dict( + aigp_metric=dict(value=1000), + as_path=dict(prepend=dict(last_as=10)), + automatic_tag=True, + clns="11.1111", + comm_list="test_comm", + community=dict(additive=True, internet=True), + dampening=dict( + penalty_half_time=10, + reuse_route_val=100, + suppress_route_val=100, + max_suppress=10, + ), + extcomm_list="test_excomm", + extcommunity=dict( + vpn_distinguisher=dict(address="192.0.2.1:12"), + ), + global_route=True, + interfaces=["GigabitEthernet0/2", "GigabitEthernet0/1"], + level=dict(level_1_2=True), + lisp="test_lisp", + local_preference=100, + metric=dict(deviation="plus", metric_value=100), + metric_type=dict(type_1=True), + mpls_label=True, + origin=dict(igp=True), + tag=50529027, + traffic_index=10, + weight=100, + ), + ), + ], + route_map="test_1", + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_route_maps_replaced(self): + set_module_args( + dict( + config=[ + dict( + entries=[ + dict( + action="deny", + continue_entry=dict(entry_sequence=20), + description="this is replace test", + match=dict( + additional_paths=dict(all=True), + as_path=dict(acls=[100, 120]), + clns=dict(address="test_osi"), + community=dict(exact_match=True, name=["new_replace"]), + ip=dict(address=dict(acls=[10, 100])), + length=dict(maximum=50000, minimum=5000), + mpls_label=True, + policy_lists=["ip_policy"], + route_type=dict( + external=dict(type_1=True), + nssa_external=dict(type_1=True), + ), + ), + set=dict( + dampening=dict( + penalty_half_time=10, + reuse_route_val=100, + suppress_route_val=100, + max_suppress=10, + ), + extcomm_list="test_excomm", + extcommunity=dict( + vpn_distinguisher=dict(address="192.0.2.1:12"), + ), + ip=dict( + address="192.0.2.1", + df=1, + next_hop=dict( + recursive=dict( + global_route=True, + address="198.51.110.1", + ), + verify_availability=dict( + address="198.51.111.1", + sequence=100, + track=10, + ), + ), + precedence=dict(critical=True), + ), + traffic_index=10, + weight=100, + ), + sequence=10, + ), + ], + route_map="test_1", + ), + dict( + entries=[ + dict( + action="deny", + match=dict( + ipv6=dict( + address=dict(acl="test_ip_acl"), + next_hop=dict(prefix_list="test_new"), + route_source=dict(acl="route_src_acl"), + ), + security_group=dict(source=[10, 20]), + local_preference=dict(value=[55, 105]), + mpls_label=True, + ), + sequence=10, + ), + ], + route_map="test_2", + ), + ], + state="replaced", + ), + ) + commands = [ + "route-map test_1 deny 10", + "no description this is test", + "continue 20", + "description this is replace test", + "match community new_replace exact-match", + "match length 5000 50000", + "no match mdt-group 25 30", + "no match community 98 99 test_1 test_2 exact-match", + "no match extcommunity 110 130", + "no match interface GigabitEthernet0/1", + "no match ipv6 route-source test_ipv6", + "no match local-preference 100", + "no match rpki invalid", + "no match metric external 100", + "no match source-protocol ospfv3 10000 static", + "no match track 100", + "no match tag list test_tag", + "set dampening 10 100 100 10", + "set extcomm-list test_excomm delete", + "set extcommunity vpn-distinguisher 192.0.2.1:12", + "set ip address prefix-list 192.0.2.1", + "set ip df 1", + "set ip next-hop recursive global 198.51.110.1", + "set ip next-hop verify-availability 198.51.111.1 100 track 10", + "set ip precedence critical", + "set traffic-index 10", + "set weight 100", + "no route-map test_1 deny 20", + "route-map test_2 deny 10", + "match ipv6 address test_ip_acl", + "match ipv6 next-hop prefix-list test_new", + "match ipv6 route-source route_src_acl", + "match security-group source tag 10 20", + "match local-preference 105 55", + "match mpls-label", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_route_maps_replaced_idempotent(self): + set_module_args( + dict( + config=[ + dict( + entries=[ + dict( + action="deny", + continue_entry=dict(entry_sequence=100), + description="this is test", + match=dict( + additional_paths=dict(all=True), + as_path=dict(acls=[100, 120]), + clns=dict(address="test_osi"), + community=dict( + exact_match=True, + name=["99", "98", "test_1", "test_2"], + ), + extcommunity=["110", "130"], + interfaces=["GigabitEthernet0/1"], + ip=dict(address=dict(acls=[10, 100])), + ipv6=dict(route_source=dict(acl="test_ipv6")), + length=dict(maximum=10000, minimum=1000), + local_preference=dict(value=[100]), + mdt_group=dict(acls=["25", "30"]), + metric=dict(external=True, value=100), + mpls_label=True, + policy_lists=["ip_policy"], + route_type=dict( + external=dict(type_1=True), + nssa_external=dict(type_1=True), + ), + rpki=dict(invalid=True), + security_group=dict(destination=[100]), + source_protocol=dict(ospfv3=10000, static=True), + tag=dict(tag_list=["test_tag"]), + track=100, + ), + sequence=10, + ), + dict( + action="deny", + sequence=20, + set=dict( + aigp_metric=dict(value=1000), + as_path=dict(prepend=dict(last_as=10)), + automatic_tag=True, + clns="11.1111", + comm_list="test_comm", + community=dict(additive=True, internet=True), + dampening=dict( + penalty_half_time=10, + reuse_route_val=100, + suppress_route_val=100, + max_suppress=10, + ), + extcomm_list="test_excomm", + extcommunity=dict( + vpn_distinguisher=dict(address="192.0.2.1:12"), + ), + global_route=True, + interfaces=["GigabitEthernet0/2", "GigabitEthernet0/1"], + level=dict(level_1_2=True), + lisp="test_lisp", + local_preference=100, + metric=dict(deviation="plus", metric_value=100), + metric_type=dict(type_1=True), + mpls_label=True, + origin=dict(igp=True), + tag=50529027, + traffic_index=10, + weight=100, + ), + ), + ], + route_map="test_1", + ), + ], + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_route_maps_overridden(self): + set_module_args( + dict( + config=[ + dict( + entries=[ + dict( + action="deny", + continue_entry=dict(entry_sequence=20), + description="this is override test", + match=dict( + additional_paths=dict(all=True), + as_path=dict(acls=[100, 120]), + clns=dict(address="test_osi"), + community=dict(exact_match=True, name=["new_override"]), + ip=dict(address=dict(acls=[10, 100])), + length=dict(maximum=50000, minimum=5000), + mpls_label=True, + policy_lists=["ip_policy"], + route_type=dict( + external=dict(type_1=True), + nssa_external=dict(type_1=True), + ), + ), + set=dict( + dampening=dict( + penalty_half_time=10, + reuse_route_val=100, + suppress_route_val=100, + max_suppress=10, + ), + extcomm_list="test_excomm", + extcommunity=dict( + vpn_distinguisher=dict(address="192.0.2.1:12"), + ), + ip=dict( + address="192.0.2.1", + df=1, + next_hop=dict( + recursive=dict( + global_route=True, + address="198.51.110.1", + ), + verify_availability=dict( + address="198.51.111.1", + sequence=100, + track=10, + ), + ), + precedence=dict(critical=True), + ), + traffic_index=10, + weight=100, + ), + sequence=10, + ), + ], + route_map="test_1", + ), + dict( + entries=[ + dict( + action="deny", + match=dict( + ipv6=dict( + address=dict(acl="test_ip_acl"), + next_hop=dict(prefix_list="test_new"), + route_source=dict(acl="route_src_acl"), + ), + security_group=dict(source=[10, 20]), + local_preference=dict(value=[55, 105]), + mpls_label=True, + ), + sequence=10, + ), + ], + route_map="test_2", + ), + ], + state="overridden", + ), + ) + commands = [ + "route-map test_1 deny 10", + "no description this is test", + "continue 20", + "description this is override test", + "match community new_override exact-match", + "match length 5000 50000", + "no match mdt-group 25 30", + "no match community 98 99 test_1 test_2 exact-match", + "no match extcommunity 110 130", + "no match interface GigabitEthernet0/1", + "no match ipv6 route-source test_ipv6", + "no match local-preference 100", + "no match rpki invalid", + "no match metric external 100", + "no match source-protocol ospfv3 10000 static", + "no match track 100", + "no match tag list test_tag", + "set dampening 10 100 100 10", + "set extcomm-list test_excomm delete", + "set extcommunity vpn-distinguisher 192.0.2.1:12", + "set ip address prefix-list 192.0.2.1", + "set ip df 1", + "set ip next-hop recursive global 198.51.110.1", + "set ip next-hop verify-availability 198.51.111.1 100 track 10", + "set ip precedence critical", + "set traffic-index 10", + "set weight 100", + "no route-map test_1 deny 20", + "route-map test_2 deny 10", + "match ipv6 address test_ip_acl", + "match ipv6 next-hop prefix-list test_new", + "match ipv6 route-source route_src_acl", + "match security-group source tag 10 20", + "match local-preference 105 55", + "match mpls-label", + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_route_maps_overridden_idempotent(self): + set_module_args( + dict( + config=[ + dict( + entries=[ + dict( + action="deny", + continue_entry=dict(entry_sequence=100), + description="this is test", + match=dict( + additional_paths=dict(all=True), + as_path=dict(acls=[100, 120]), + clns=dict(address="test_osi"), + community=dict( + exact_match=True, + name=["99", "98", "test_1", "test_2"], + ), + extcommunity=["110", "130"], + interfaces=["GigabitEthernet0/1"], + ip=dict(address=dict(acls=[10, 100])), + ipv6=dict(route_source=dict(acl="test_ipv6")), + length=dict(maximum=10000, minimum=1000), + local_preference=dict(value=[100]), + mdt_group=dict(acls=["25", "30"]), + metric=dict(external=True, value=100), + mpls_label=True, + policy_lists=["ip_policy"], + route_type=dict( + external=dict(type_1=True), + nssa_external=dict(type_1=True), + ), + rpki=dict(invalid=True), + security_group=dict(destination=[100]), + source_protocol=dict(ospfv3=10000, static=True), + tag=dict(tag_list=["test_tag"]), + track=100, + ), + sequence=10, + ), + dict( + action="deny", + sequence=20, + set=dict( + aigp_metric=dict(value=1000), + as_path=dict(prepend=dict(last_as=10)), + automatic_tag=True, + clns="11.1111", + comm_list="test_comm", + community=dict(additive=True, internet=True), + dampening=dict( + penalty_half_time=10, + reuse_route_val=100, + suppress_route_val=100, + max_suppress=10, + ), + extcomm_list="test_excomm", + extcommunity=dict( + vpn_distinguisher=dict(address="192.0.2.1:12"), + ), + global_route=True, + interfaces=["GigabitEthernet0/2", "GigabitEthernet0/1"], + level=dict(level_1_2=True), + lisp="test_lisp", + local_preference=100, + metric=dict(deviation="plus", metric_value=100), + metric_type=dict(type_1=True), + mpls_label=True, + origin=dict(igp=True), + tag=50529027, + traffic_index=10, + weight=100, + ), + ), + ], + route_map="test_1", + ), + ], + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[]) + + def test_ios_route_maps_deleted(self): + set_module_args(dict(config=[dict(route_map="test_1")], state="deleted")) + commands = ["no route-map test_1"] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_route_maps_delete_without_config(self): + set_module_args(dict(state="deleted")) + commands = ["no route-map test_1"] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_ios_route_maps_rendered(self): + set_module_args( + dict( + config=[ + dict( + entries=[ + dict( + action="deny", + continue_entry=dict(entry_sequence=100), + description="this is test", + match=dict( + additional_paths=dict(all=True), + as_path=dict(acls=[100, 120]), + clns=dict(address="test_osi"), + community=dict( + exact_match=True, + name=["99", "98", "test_1", "test_2"], + ), + extcommunity=["110", "130"], + interfaces=["GigabitEthernet0/1", "GigabitEthernet0/2"], + ip=dict(address=dict(acls=[10, 100])), + ipv6=dict(route_source=dict(acl="test_ipv6")), + length=dict(maximum=10000, minimum=1000), + local_preference=dict(value=[100]), + mdt_group=dict(acls=["25", "30"]), + metric=dict(external=True, value=100), + mpls_label=True, + policy_lists=["ip_policy"], + route_type=dict( + external=dict(type_1=True), + nssa_external=dict(type_1=True), + ), + rpki=dict(invalid=True), + security_group=dict(destination=[100]), + source_protocol=dict(ospfv3=10000, static=True), + tag=dict(tag_list=["test_tag"]), + track=100, + ), + sequence=10, + ), + dict( + action="deny", + sequence=30, + set=dict( + as_path=dict( + prepend=dict(as_number=["65512", 65522, "65532", 65543]), + ), + ), + ), + dict( + action="deny", + sequence=20, + set=dict( + aigp_metric=dict(value=1000), + as_path=dict(prepend=dict(last_as=10)), + automatic_tag=True, + clns="11.1111", + comm_list="test_comm", + community=dict(additive=True, internet=True), + dampening=dict( + penalty_half_time=10, + reuse_route_val=100, + suppress_route_val=100, + max_suppress=10, + ), + extcomm_list="test_excomm", + extcommunity=dict( + vpn_distinguisher=dict(address="192.0.2.1:12"), + ), + global_route=True, + interfaces=["GigabitEthernet0/2", "GigabitEthernet0/1"], + level=dict(level_1_2=True), + lisp="test_lisp", + local_preference=100, + metric=dict(deviation="plus", metric_value=100), + metric_type=dict(type_1=True), + mpls_label=True, + origin=dict(igp=True), + tag=50529027, + traffic_index=10, + weight=100, + ), + ), + ], + route_map="test_1", + ), + ], + state="rendered", + ), + ) + commands = [ + "route-map test_1 deny 10", + "continue 100", + "description this is test", + "match additional-paths advertise-set all", + "match as-path 100 120", + "match clns address test_osi", + "match mdt-group 25 30", + "match community 98 99 test_1 test_2 exact-match", + "match extcommunity 110 130", + "match interface GigabitEthernet0/1 GigabitEthernet0/2", + "match ip address 10 100", + "match ipv6 route-source test_ipv6", + "match length 1000 10000", + "match local-preference 100", + "match mpls-label", + "match policy-list ip_policy", + "match rpki invalid", + "match route-type external type-1", + "match metric external 100", + "match source-protocol ospfv3 10000 static", + "match track 100", + "match tag list test_tag", + "route-map test_1 deny 20", + "set aigp-metric 1000", + "set as-path prepend last-as 10", + "set automatic-tag", + "set clns next-hop 11.1111", + "set comm-list test_comm delete", + "set community internet additive", + "set dampening 10 100 100 10", + "set extcomm-list test_excomm delete", + "set extcommunity vpn-distinguisher 192.0.2.1:12", + "set weight 100", + "set lisp locator-set test_lisp", + "set interface GigabitEthernet0/1 GigabitEthernet0/2", + "set tag 50529027", + "set local-preference 100", + "set mpls-label", + "set metric-type type-1", + "set traffic-index 10", + "route-map test_1 deny 30", + "set as-path prepend 65512 65522 65532 65543", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_service.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_service.py new file mode 100644 index 000000000..b77be44f2 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_service.py @@ -0,0 +1,435 @@ +# +# (c) 2021, 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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_service +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosServiceModule(TestIosModule): + module = ios_service + + def setUp(self): + super(TestIosServiceModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.service.service." + "ServiceFacts.get_service_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosServiceModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_service_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + service slave-log + service tcp-keepalives-in + service tcp-keepalives-out + service timestamps debug datetime msec + service timestamps log datetime msec + service private-config-encryption + service prompt config + service counters max age 0 + service dhcp + service call-home + service password-recovery + """, + ) + + playbook = { + "config": { + "call_home": True, + "tcp_keepalives_in": True, + "tcp_keepalives_out": True, + "timestamps": [ + { + "msg": "log", + "timestamp": "datetime", + "datetime_options": { + "msec": True, + }, + }, + { + "msg": "debug", + "timestamp": "datetime", + "datetime_options": { + "msec": True, + }, + }, + ], + }, + } + merged = [] + playbook["state"] = "merged" + set_module_args(playbook) + result = self.execute_module() + + self.assertEqual(sorted(result["commands"]), sorted(merged)) + + def test_ios_service_merged(self): + self.execute_show_command.return_value = dedent( + """\ + service slave-log + service tcp-keepalives-in + service tcp-keepalives-out + service timestamps debug datetime msec + service timestamps log datetime msec + service private-config-encryption + service prompt config + service counters max age 0 + service dhcp + service call-home + service password-recovery + """, + ) + + playbook = { + "config": { + "tcp_keepalives_in": True, + "tcp_keepalives_out": True, + "timestamps": [ + { + "msg": "log", + "timestamp": "datetime", + "datetime_options": { + "localtime": True, + "msec": True, + "show_timezone": True, + "year": True, + }, + }, + { + "msg": "debug", + "timestamp": "uptime", + }, + ], + "pad": False, + "password_encryption": True, + }, + } + merged = [ + "service timestamps debug uptime", + "service timestamps log datetime msec localtime show-timezone year", + "service password-encryption", + ] + playbook["state"] = "merged" + set_module_args(playbook) + result = self.execute_module(changed=True) + + self.assertEqual(sorted(result["commands"]), sorted(merged)) + + def test_ios_snm_server_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + service slave-log + service tcp-keepalives-in + service tcp-keepalives-out + service timestamps debug datetime msec + service timestamps log datetime msec localtime show-timezone year + service password-encryption + service private-config-encryption + service prompt config + service counters max age 0 + service dhcp + service call-home + service password-recovery + """, + ) + playbook = {"config": {}} + deleted = [ + "no service tcp-keepalives-in", + "no service tcp-keepalives-out", + "no service timestamps debug", + "no service timestamps log", + "no service password-encryption", + "no service call-home", + ] + playbook["state"] = "deleted" + set_module_args(playbook) + self.maxDiff = None + result = self.execute_module(changed=True) + + self.assertEqual(sorted(result["commands"]), sorted(deleted)) + + def test_ios_service_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + service call-home + service config + service counters max age 0 + service dhcp + service pad + service password-recovery + service private-config-encryption + service prompt config + service slave-log + service timestamps log datetime msec + """, + ) + playbook = { + "config": { + "timestamps": [ + { + "msg": "log", + "timestamp": "datetime", + "datetime_options": { + "localtime": True, + "msec": True, + "show_timezone": True, + "year": True, + }, + }, + { + "msg": "debug", + "timestamp": "datetime", + }, + ], + "tcp_keepalives_in": True, + "tcp_keepalives_out": True, + "password_encryption": True, + "counters": 5, + }, + } + replaced = [ + "no service call-home", + "no service config", + "no service pad", + "service counters max age 5", + "service password-encryption", + "service tcp-keepalives-in", + "service tcp-keepalives-out", + "service timestamps debug datetime", + "service timestamps log datetime msec localtime show-timezone year", + ] + playbook["state"] = "replaced" + set_module_args(playbook) + result = self.execute_module(changed=True) + + self.assertEqual(sorted(result["commands"]), sorted(replaced)) + + def test_ios_service_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + service slave-log + service timestamps debug datetime msec + service timestamps log datetime msec + service private-config-encryption + service prompt config + service counters max age 0 + service dhcp + service call-home + service password-recovery + """, + ) + playbook = { + "config": { + "call_home": True, + "private_config_encryption": True, + "timestamps": [ + { + "msg": "debug", + "timestamp": "datetime", + "datetime_options": { + "msec": True, + }, + }, + { + "msg": "log", + "timestamp": "datetime", + "datetime_options": { + "msec": True, + }, + }, + ], + }, + } + replaced = [] + playbook["state"] = "replaced" + set_module_args(playbook) + result = self.execute_module(changed=False) + self.maxDiff = None + + self.assertEqual(sorted(result["commands"]), sorted(replaced)) + + #################### + + def test_ios_service_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + service slave-log + service timestamps debug datetime msec + service timestamps log datetime msec + service private-config-encryption + service prompt config + service counters max age 0 + service dhcp + service call-home + service password-recovery + """, + ), + state="parsed", + ), + ) + + parsed = { + "timestamps": [ + { + "msg": "debug", + "timestamp": "datetime", + "datetime_options": { + "msec": True, + }, + }, + { + "msg": "log", + "timestamp": "datetime", + "datetime_options": { + "msec": True, + }, + }, + ], + "prompt": True, + "private_config_encryption": True, + "counters": 0, + "dhcp": True, + "call_home": True, + "password_recovery": True, + "slave_log": True, + } + result = self.execute_module(changed=False) + self.maxDiff = None + + self.assertEqual(result["parsed"], parsed) + + def test_ios_service_gathered(self): + self.execute_show_command.return_value = dedent( + """\ + service timestamps log datetime msec localtime show-timezone year + service timestamps debug uptime + service call-home + """, + ) + set_module_args(dict(state="gathered")) + gathered = { + "timestamps": [ + { + "msg": "debug", + "timestamp": "uptime", + }, + { + "msg": "log", + "timestamp": "datetime", + "datetime_options": { + "localtime": True, + "msec": True, + "show_timezone": True, + "year": True, + }, + }, + ], + "call_home": True, + "dhcp": True, + "counters": 0, + "password_recovery": True, + "prompt": True, + "slave_log": True, + } + result = self.execute_module(changed=False) + self.maxDiff = None + + self.assertEqual(sorted(result["gathered"]), sorted(gathered)) + + def test_ios_service_rendered(self): + set_module_args( + { + "config": { + "call_home": True, + "counters": 5, + "config": False, + "pad": False, + "tcp_keepalives_in": True, + "tcp_keepalives_out": True, + "timestamps": [ + { + "msg": "debug", + "timestamp": "uptime", + }, + { + "msg": "log", + "timestamp": "datetime", + "datetime_options": { + "localtime": True, + "msec": True, + "show_timezone": True, + "year": True, + }, + }, + ], + }, + "state": "rendered", + }, + ) + rendered = [ + "service call-home", + "service counters max age 5", + "service dhcp", + "service password-recovery", + "service prompt config", + "service slave-log", + "service tcp-keepalives-in", + "service tcp-keepalives-out", + "service timestamps debug uptime", + "service timestamps log datetime msec localtime show-timezone year", + ] + result = self.execute_module(changed=False) + self.maxDiff = None + + self.assertEqual(sorted(result["rendered"]), sorted(rendered)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_snmp_server.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_snmp_server.py new file mode 100644 index 000000000..e0319bd7b --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_snmp_server.py @@ -0,0 +1,1802 @@ +# +# (c) 2021, 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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_snmp_server +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosSnmpServerModule(TestIosModule): + module = ios_snmp_server + + def setUp(self): + super(TestIosSnmpServerModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.snmp_server.snmp_server." + "Snmp_serverFacts.get_snmp_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + self.mock_execute_show_command_user = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.snmp_server.snmp_server." + "Snmp_serverFacts.get_snmpv3_user_data", + ) + + self.execute_show_command_user = self.mock_execute_show_command_user.start() + + def tearDown(self): + super(TestIosSnmpServerModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + self.mock_execute_show_command_user.stop() + + def test_ios_snmp_server_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + snmp-server engineID local AB0C5342FA0A + snmp-server engineID remote 172.16.0.2 udp-port 23 AB0C5342FAAB + snmp-server engineID remote 172.16.0.1 udp-port 22 AB0C5342FAAA + snmp-server user newuser newfamily v1 access 24 + snmp-server user paul familypaul v3 access ipv6 ipv6acl + snmp-server user replaceUser replaceUser v3 + snmp-server group group0 v3 auth + snmp-server group group1 v1 notify me access 2 + snmp-server group group2 v3 priv + snmp-server group replaceUser v3 noauth + snmp-server community commu1 view view1 RO ipv6 te + snmp-server community commu2 RO 1322 + snmp-server community commu3 RW paul + snmp-server trap timeout 2 + snmp-server trap-source GigabitEthernet0/0 + snmp-server source-interface informs Loopback999 + snmp-server packetsize 500 + snmp-server queue-length 2 + snmp-server location thi sis a good location + snmp-server ip dscp 2 + snmp-server contact this is contact string + snmp-server chassis-id this is a chassis id string + snmp-server system-shutdown + snmp-server enable traps snmp authentication linkdown linkup coldstart warmstart + snmp-server enable traps flowmon + snmp-server enable traps tty + snmp-server enable traps eigrp + snmp-server enable traps casa + snmp-server enable traps ospf state-change + snmp-server enable traps ospf errors + snmp-server enable traps ospf retransmit + snmp-server enable traps ospf lsa + snmp-server enable traps ospf cisco-specific state-change nssa-trans-change + snmp-server enable traps ospf cisco-specific state-change shamlink interface + snmp-server enable traps ospf cisco-specific state-change shamlink neighbor + snmp-server enable traps ospf cisco-specific errors + snmp-server enable traps ospf cisco-specific retransmit + snmp-server enable traps ospf cisco-specific lsa + snmp-server enable traps ethernet cfm cc mep-up mep-down cross-connect loop config + snmp-server enable traps ethernet cfm crosscheck mep-missing mep-unknown service-up + snmp-server enable traps auth-framework sec-violation + snmp-server enable traps energywise + snmp-server enable traps pw vc + snmp-server enable traps l2tun session + snmp-server enable traps l2tun pseudowire status + snmp-server enable traps ether-oam + snmp-server enable traps ethernet evc status create delete + snmp-server enable traps bridge newroot topologychange + snmp-server enable traps vtp + snmp-server enable traps ike policy add + snmp-server enable traps ike policy delete + snmp-server enable traps ike tunnel start + snmp-server enable traps ike tunnel stop + snmp-server enable traps ipsec cryptomap add + snmp-server enable traps ipsec cryptomap delete + snmp-server enable traps ipsec cryptomap attach + snmp-server enable traps ipsec cryptomap detach + snmp-server enable traps ipsec tunnel start + snmp-server enable traps ipsec tunnel stop + snmp-server enable traps ipsec too-many-sas + snmp-server enable traps bfd + snmp-server enable traps bgp + snmp-server enable traps bgp cbgp2 + snmp-server enable traps cef resource-failure peer-state-change peer-fib-state-change inconsistency + snmp-server enable traps dlsw + snmp-server enable traps frame-relay + snmp-server enable traps frame-relay subif + snmp-server enable traps hsrp + snmp-server enable traps ipmulticast + snmp-server enable traps isis + snmp-server enable traps msdp + snmp-server enable traps mvpn + snmp-server enable traps pim neighbor-change rp-mapping-change invalid-pim-message + snmp-server enable traps rsvp + snmp-server enable traps ipsla + snmp-server enable traps slb real virtual csrp + snmp-server enable traps syslog + snmp-server enable traps event-manager + snmp-server enable traps pki + snmp-server enable traps ethernet cfm alarm + snmp-server enable traps mpls vpn + snmp-server enable traps vrfmib vrf-up vrf-down vnet-trunk-up vnet-trunk-down + snmp-server host 172.16.2.99 informs version 2c check msdp + snmp-server host 172.16.2.99 check slb + snmp-server host 172.16.2.99 checktrap isis + snmp-server host 172.16.2.1 version 3 priv newtera rsrb + snmp-server host 172.16.2.1 version 3 noauth replaceUser slb + snmp-server host 172.16.2.1 version 2c trapsac tty + snmp-server host 172.16.1.1 version 3 auth group0 tty + snmp-server context contextWord1 + snmp-server context contextWord2 + snmp-server file-transfer access-group testAcl protocol ftp + snmp-server file-transfer access-group testAcl protocol rcp + snmp-server cache interval 2 + snmp-server password-policy policy1 define max-len 24 upper-case 12 lower-case 12 special-char 32 digits 23 change 3 + snmp-server password-policy policy2 define min-len 12 upper-case 12 special-char 22 change 9 + snmp-server password-policy policy3 define min-len 12 max-len 12 upper-case 12 special-char 22 digits 23 change 11 + snmp-server accounting commands default + snmp-server inform pending 2 + """, + ) + + playbook = { + "config": { + "accounting": {"command": "default"}, + "cache": 2, + "chassis_id": "this is a chassis id string", + "communities": [ + {"acl_v6": "te", "name": "commu1", "ro": True, "view": "view1"}, + {"acl_v4": "1322", "name": "commu2", "ro": True}, + {"acl_v4": "paul", "name": "commu3", "rw": True}, + ], + "contact": "this is contact string", + "context": ["contextWord2", "contextWord1"], + "engine_id": [ + {"id": "AB0C5342FA0A", "local": True}, + {"id": "AB0C5342FAAA", "remote": {"host": "172.16.0.1", "udp_port": 22}}, + {"id": "AB0C5342FAAB", "remote": {"host": "172.16.0.2", "udp_port": 23}}, + ], + "file_transfer": {"access_group": "testAcl", "protocol": ["ftp", "rcp"]}, + "groups": [ + {"group": "group0", "version": "v3", "version_option": "auth"}, + {"acl_v4": "2", "group": "group1", "notify": "me", "version": "v1"}, + {"group": "group2", "version": "v3", "version_option": "priv"}, + {"group": "replaceUser", "version": "v3", "version_option": "noauth"}, + ], + "hosts": [ + { + "community_string": "group0", + "host": "172.16.1.1", + "traps": ["tty"], + "version": "3", + "version_option": "auth", + }, + { + "community_string": "newtera", + "host": "172.16.2.1", + "traps": ["rsrb"], + "version": "3", + "version_option": "priv", + }, + { + "community_string": "replaceUser", + "host": "172.16.2.1", + "traps": ["slb"], + "version": "3", + "version_option": "noauth", + }, + { + "community_string": "trapsac", + "host": "172.16.2.1", + "traps": ["tty"], + "version": "2c", + }, + { + "community_string": "check", + "host": "172.16.2.99", + "informs": True, + "traps": ["msdp"], + "version": "2c", + }, + {"community_string": "check", "host": "172.16.2.99", "traps": ["slb"]}, + {"community_string": "checktrap", "host": "172.16.2.99", "traps": ["isis"]}, + ], + "inform": {"pending": 2}, + "ip": {"dscp": 2}, + "location": "thi sis a good location", + "packet_size": 500, + "password_policy": [ + { + "change": 3, + "digits": 23, + "lower_case": 12, + "max_len": 24, + "policy_name": "policy1", + "special_char": 32, + "upper_case": 12, + }, + { + "change": 9, + "min_len": 12, + "policy_name": "policy2", + "special_char": 22, + "upper_case": 12, + }, + { + "change": 11, + "digits": 23, + "max_len": 12, + "min_len": 12, + "policy_name": "policy3", + "special_char": 22, + "upper_case": 12, + }, + ], + "queue_length": 2, + "source_interface": "Loopback999", + "system_shutdown": True, + "trap_source": "GigabitEthernet0/0", + "trap_timeout": 2, + "traps": { + "auth_framework": {"enable": True}, + "bfd": {"enable": True}, + "bgp": {"cbgp2": True, "enable": True}, + "bridge": {"enable": True, "newroot": True, "topologychange": True}, + "casa": True, + "cef": { + "enable": True, + "inconsistency": True, + "peer_fib_state_change": True, + "peer_state_change": True, + "resource_failure": True, + }, + "dlsw": {"enable": True}, + "eigrp": True, + "energywise": True, + "ethernet": { + "cfm": { + "alarm": True, + "cc": { + "config": True, + "cross_connect": True, + "loop": True, + "mep_down": True, + "mep_up": True, + }, + "crosscheck": { + "mep_missing": True, + "mep_unknown": True, + "service_up": True, + }, + }, + "evc": {"create": True, "delete": True, "status": True}, + }, + "event_manager": True, + "flowmon": True, + "frame_relay": {"enable": True}, + "hsrp": True, + "ike": { + "policy": {"add": True, "delete": True}, + "tunnel": {"start": True, "stop": True}, + }, + "ipmulticast": True, + "ipsec": { + "cryptomap": {"add": True, "attach": True, "delete": True, "detach": True}, + "too_many_sas": True, + "tunnel": {"start": True, "stop": True}, + }, + "ipsla": True, + "isis": True, + "l2tun": {"pseudowire_status": True, "session": True}, + "mpls_vpn": True, + "msdp": True, + "mvpn": True, + "ospf": { + "cisco_specific": { + "error": True, + "lsa": True, + "retransmit": True, + "state_change": { + "nssa_trans_change": True, + "shamlink": {"interface": True, "neighbor": True}, + }, + }, + "error": True, + "lsa": True, + "retransmit": True, + "state_change": True, + }, + "pim": { + "enable": True, + "invalid_pim_message": True, + "neighbor_change": True, + "rp_mapping_change": True, + }, + "pki": True, + "pw_vc": True, + "rsvp": True, + "snmp": { + "authentication": True, + "coldstart": True, + "linkdown": True, + "linkup": True, + "warmstart": True, + }, + "syslog": True, + "tty": True, + "vrfmib": { + "vnet_trunk_down": True, + "vnet_trunk_up": True, + "vrf_down": True, + "vrf_up": True, + }, + }, + "users": [ + {"acl_v4": "24", "group": "newfamily", "username": "newuser", "version": "v1"}, + { + "acl_v6": "ipv6acl", + "group": "familypaul", + "username": "paul", + "version": "v3", + }, + {"group": "replaceUser", "username": "replaceUser", "version": "v3"}, + ], + }, + } + merged = [] + playbook["state"] = "merged" + set_module_args(playbook) + result = self.execute_module() + + self.assertEqual(sorted(result["commands"]), sorted(merged)) + + def test_ios_snmp_server_merged(self): + self.execute_show_command.return_value = dedent( + """\ + snmp-server engineID local AB0C5342FA0A + snmp-server engineID remote 172.16.0.2 udp-port 23 AB0C5342FAAB + snmp-server enable traps casa + snmp-server enable traps ospf cisco-specific state-change nssa-trans-change + snmp-server enable traps ospf cisco-specific state-change shamlink interface + snmp-server enable traps ospf cisco-specific state-change shamlink neighbor + snmp-server enable traps ospf cisco-specific errors + snmp-server enable traps ospf cisco-specific retransmit + snmp-server enable traps ospf cisco-specific lsa + snmp-server enable traps ethernet cfm cc mep-up mep-down cross-connect loop config + snmp-server enable traps ethernet cfm crosscheck mep-missing mep-unknown service-up + snmp-server enable traps auth-framework sec-violation + snmp-server enable traps ethernet cfm alarm + snmp-server enable traps mpls vpn + snmp-server enable traps vrfmib vrf-up vrf-down vnet-trunk-up vnet-trunk-down + snmp-server host 172.16.2.99 informs version 2c check msdp + snmp-server host 172.16.2.1 version 3 priv newtera rsrb + snmp-server host 172.16.2.1 version 3 noauth replaceUser slb + snmp-server context contextWord1 + snmp-server cache interval 2 + snmp-server password-policy policy1 define max-len 24 upper-case 12 lower-case 12 special-char 32 digits 23 change 3 + snmp-server password-policy policy2 define min-len 12 upper-case 12 special-char 22 change 9 + snmp-server inform pending 2 + """, + ) + + self.execute_show_command_user.return_value = dedent( + """\ + User name: paul + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active access-list: ipv6 + Authentication Protocol: MD5 + Privacy Protocol: AES128 + Group-name: familypaul + + User name: replaceUser + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active access-list: 22 + Authentication Protocol: MD5 + Privacy Protocol: None + Group-snmp-server user paul familypaul v3 access ipv6name: replaceUser + """, + ) + + playbook = { + "config": { + "accounting": {"command": "default"}, + "cache": 2, + "chassis_id": "this is a chassis id string", + "communities": [ + {"acl_v6": "te", "name": "commu1", "ro": True, "view": "view1"}, + {"acl_v4": "1322", "name": "commu2", "ro": True}, + {"acl_v4": "paul", "name": "commu3", "rw": True}, + ], + "contact": "this is contact string", + "context": ["contextWord2", "contextWord1"], + "engine_id": [ + {"id": "AB0C5342FA0A", "local": True}, + { + "id": "AB0C5342FAAA", + "remote": {"host": "172.16.0.1", "udp_port": 22, "vrf": "mgmt"}, + }, + {"id": "AB0C5342FAAB", "remote": {"host": "172.16.0.2", "udp_port": 23}}, + ], + "file_transfer": {"access_group": "testAcl", "protocol": ["ftp", "rcp"]}, + "groups": [ + {"group": "group0", "version": "v3", "version_option": "auth"}, + {"acl_v4": "2", "group": "group1", "notify": "me", "version": "v1"}, + {"group": "group2", "version": "v3", "version_option": "priv"}, + {"group": "replaceUser", "version": "v3", "version_option": "noauth"}, + ], + "hosts": [ + { + "community_string": "group0", + "host": "172.16.1.1", + "traps": ["tty"], + "version": "3", + "version_option": "auth", + }, + { + "community_string": "newtera", + "host": "172.16.2.1", + "traps": ["rsrb"], + "version": "3", + "version_option": "priv", + }, + { + "community_string": "replaceUser", + "host": "172.16.2.1", + "traps": ["slb"], + "version": "3", + "version_option": "noauth", + }, + { + "community_string": "trapsac", + "host": "172.16.2.1", + "traps": ["tty"], + "version": "2c", + }, + { + "community_string": "check", + "host": "172.16.2.99", + "informs": True, + "traps": ["msdp"], + "version": "2c", + }, + {"community_string": "check", "host": "172.16.2.99", "traps": ["slb"]}, + {"community_string": "checktrap", "host": "172.16.2.99", "traps": ["isis"]}, + ], + "inform": {"pending": 2}, + "ip": {"dscp": 2}, + "location": "thi sis a good location", + "packet_size": 500, + "password_policy": [ + { + "change": 3, + "digits": 23, + "lower_case": 12, + "max_len": 24, + "policy_name": "policy1", + "special_char": 32, + "upper_case": 12, + }, + { + "change": 9, + "min_len": 12, + "policy_name": "policy2", + "special_char": 22, + "upper_case": 12, + }, + { + "change": 11, + "digits": 23, + "max_len": 12, + "min_len": 12, + "policy_name": "policy3", + "special_char": 22, + "upper_case": 12, + }, + ], + "queue_length": 2, + "source_interface": "Loopback999", + "system_shutdown": True, + "trap_source": "GigabitEthernet0/0", + "trap_timeout": 2, + "traps": { + "auth_framework": {"enable": True}, + "bfd": {"enable": True}, + "bgp": { + "cbgp2": True, + "enable": True, + "threshold": {"prefix": True}, + "state_changes": { + "enable": True, + "all": True, + "limited": True, + "backward_trans": True, + }, + }, + "bridge": {"enable": True, "newroot": True, "topologychange": True}, + "casa": True, + "cef": { + "enable": True, + "inconsistency": True, + "peer_fib_state_change": True, + "peer_state_change": True, + "resource_failure": True, + }, + "dlsw": {"enable": True}, + "eigrp": True, + "energywise": True, + "ethernet": { + "cfm": { + "alarm": True, + "cc": { + "config": True, + "cross_connect": True, + "loop": True, + "mep_down": True, + "mep_up": True, + }, + "crosscheck": { + "mep_missing": True, + "mep_unknown": True, + "service_up": True, + }, + }, + "evc": {"create": True, "delete": True, "status": True}, + }, + "event_manager": True, + "flowmon": True, + "frame_relay": {"enable": True}, + "hsrp": True, + "ike": { + "policy": {"add": True, "delete": True}, + "tunnel": {"start": True, "stop": True}, + }, + "ipmulticast": True, + "ipsec": { + "cryptomap": {"add": True, "attach": True, "delete": True, "detach": True}, + "too_many_sas": True, + "tunnel": {"start": True, "stop": True}, + }, + "ipsla": True, + "isis": True, + "l2tun": {"pseudowire_status": True, "session": True}, + "mpls_vpn": True, + "msdp": True, + "mvpn": True, + "ospf": { + "cisco_specific": { + "error": True, + "lsa": True, + "retransmit": True, + "state_change": { + "nssa_trans_change": True, + "shamlink": {"interface": True, "neighbor": True}, + }, + }, + "error": True, + "lsa": True, + "retransmit": True, + "state_change": True, + }, + "pim": { + "enable": True, + "invalid_pim_message": True, + "neighbor_change": True, + "rp_mapping_change": True, + }, + "pki": True, + "pw_vc": True, + "rsvp": True, + "snmp": { + "authentication": True, + "coldstart": True, + "linkdown": True, + "linkup": True, + "warmstart": True, + }, + "syslog": True, + "tty": True, + "vrfmib": { + "vnet_trunk_down": True, + "vnet_trunk_up": True, + "vrf_down": True, + "vrf_up": True, + }, + }, + "users": [ + {"acl_v4": "24", "group": "newfamily", "username": "newuser", "version": "v1"}, + {"acl_v4": "ipv6", "group": "familypaul", "username": "paul", "version": "v3"}, + {"group": "replaceUser", "username": "replaceUser", "version": "v3"}, + {"acl_v4": "27", "group": "mfamily", "username": "flow", "version": "v3"}, + ], + }, + } + merged = [ + "snmp-server accounting commands default", + "snmp-server chassis-id this is a chassis id string", + "snmp-server contact this is contact string", + "snmp-server file-transfer access-group testAcl protocol ftp rcp", + "snmp-server ip dscp 2", + "snmp-server location thi sis a good location", + "snmp-server packetsize 500", + "snmp-server queue-length 2", + "snmp-server trap timeout 2", + "snmp-server source-interface informs Loopback999", + "snmp-server trap-source GigabitEthernet0/0", + "snmp-server system-shutdown", + "snmp-server enable traps bfd", + "snmp-server enable traps bgp cbgp2 state-changes all backward-trans limited threshold prefix", + "snmp-server enable traps bridge newroot topologychange", + "snmp-server enable traps eigrp", + "snmp-server enable traps energywise", + "snmp-server enable traps event-manager", + "snmp-server enable traps flowmon", + "snmp-server enable traps hsrp", + "snmp-server enable traps ipsla", + "snmp-server enable traps isis", + "snmp-server enable traps msdp", + "snmp-server enable traps mvpn", + "snmp-server enable traps pki", + "snmp-server enable traps pw vc", + "snmp-server enable traps rsvp", + "snmp-server enable traps syslog", + "snmp-server enable traps tty", + "snmp-server enable traps ipmulticast", + "snmp-server enable traps ike policy add", + "snmp-server enable traps ike policy delete", + "snmp-server enable traps ike tunnel start", + "snmp-server enable traps ike tunnel stop", + "snmp-server enable traps ipsec cryptomap add", + "snmp-server enable traps ipsec cryptomap delete", + "snmp-server enable traps ipsec cryptomap attach", + "snmp-server enable traps ipsec cryptomap detach", + "snmp-server enable traps ipsec tunnel start", + "snmp-server enable traps ipsec tunnel stop", + "snmp-server enable traps ipsec too-many-sas", + "snmp-server enable traps ospf errors", + "snmp-server enable traps ospf retransmit", + "snmp-server enable traps ospf lsa", + "snmp-server enable traps ospf state-change", + "snmp-server enable traps l2tun pseudowire status", + "snmp-server enable traps l2tun session", + "snmp-server enable traps pim neighbor-change rp-mapping-change invalid-pim-message", + "snmp-server enable traps snmp authentication linkdown linkup warmstart coldstart", + "snmp-server enable traps frame-relay", + "snmp-server enable traps cef resource-failure peer-state-change peer-fib-state-change inconsistency", + "snmp-server enable traps dlsw", + "snmp-server enable traps ethernet evc create delete status", + "snmp-server host 172.16.2.1 version 2c trapsac tty", + "snmp-server host 172.16.1.1 version 3 auth group0 tty", + "snmp-server host 172.16.2.99 check slb", + "snmp-server host 172.16.2.99 checktrap isis", + "snmp-server group group0 v3 auth", + "snmp-server group group1 v1 notify me access 2", + "snmp-server group group2 v3 priv", + "snmp-server group replaceUser v3 noauth", + "snmp-server engineID remote 172.16.0.1 udp-port 22 vrf mgmt AB0C5342FAAA", + "snmp-server community commu1 view view1 ro ipv6 te", + "snmp-server community commu2 ro 1322", + "snmp-server community commu3 rw paul", + "snmp-server context contextWord2", + "snmp-server password-policy policy3 define min-len 12 max-len 12 upper-case 12 special-char 22 digits 23 change 11", + "snmp-server user newuser newfamily v1 access 24", + "snmp-server user paul familypaul v3 access ipv6", + "snmp-server user replaceUser replaceUser v3", + "snmp-server user flow mfamily v3 access 27", + ] + playbook["state"] = "merged" + set_module_args(playbook) + result = self.execute_module(changed=True) + + self.assertEqual(sorted(result["commands"]), sorted(merged)) + + def test_ios_snmp_server_deleted(self): + self.execute_show_command.return_value = dedent( + """\ + snmp-server engineID local AB0C5342FA0A + snmp-server engineID remote 172.16.0.2 udp-port 23 AB0C5342FAAB + snmp-server engineID remote 172.16.0.1 udp-port 22 AB0C5342FAAA + snmp-server user new@user! new.family$ v1 access 24 + snmp-server user paul familypaul v3 access ipv6 ipv6acl + snmp-server user replaceUser replaceUser v3 + snmp-server user flow mfamily v3 access 27 + snmp-server group group0 v3 auth + snmp-server group group1 v1 notify me access 2 + snmp-server group group2 v3 priv + snmp-server group replaceUser v3 noauth + snmp-server community commu1 view view1 RO ipv6 te + snmp-server community commu2 RO 1322 + snmp-server community commu3 RW paul + snmp-server trap timeout 2 + snmp-server trap-source GigabitEthernet0/0 + snmp-server source-interface informs Loopback999 + snmp-server packetsize 500 + snmp-server queue-length 2 + snmp-server location thi sis a good location + snmp-server ip dscp 2 + snmp-server contact this is contact string + snmp-server chassis-id this is a chassis id string + snmp-server system-shutdown + snmp-server enable traps snmp authentication linkdown linkup coldstart warmstart + snmp-server enable traps flowmon + snmp-server enable traps tty + snmp-server enable traps eigrp + snmp-server enable traps casa + snmp-server enable traps ospf state-change + snmp-server enable traps ospf errors + snmp-server enable traps ospf retransmit + snmp-server enable traps ospf lsa + snmp-server enable traps ospf cisco-specific state-change nssa-trans-change + snmp-server enable traps ospf cisco-specific state-change shamlink interface + snmp-server enable traps ospf cisco-specific state-change shamlink neighbor + snmp-server enable traps ospf cisco-specific errors + snmp-server enable traps ospf cisco-specific retransmit + snmp-server enable traps ospf cisco-specific lsa + snmp-server enable traps ethernet cfm cc mep-up mep-down cross-connect loop config + snmp-server enable traps ethernet cfm crosscheck mep-missing mep-unknown service-up + snmp-server enable traps auth-framework sec-violation + snmp-server enable traps energywise + snmp-server enable traps pw vc + snmp-server enable traps l2tun session + snmp-server enable traps l2tun pseudowire status + snmp-server enable traps ether-oam + snmp-server enable traps ethernet evc status create delete + snmp-server enable traps bridge newroot topologychange + snmp-server enable traps vtp + snmp-server enable traps ike policy add + snmp-server enable traps ike policy delete + snmp-server enable traps ike tunnel start + snmp-server enable traps ike tunnel stop + snmp-server enable traps ipsec cryptomap add + snmp-server enable traps ipsec cryptomap delete + snmp-server enable traps ipsec cryptomap attach + snmp-server enable traps ipsec cryptomap detach + snmp-server enable traps ipsec tunnel start + snmp-server enable traps ipsec tunnel stop + snmp-server enable traps ipsec too-many-sas + snmp-server enable traps bfd + snmp-server enable traps bgp + snmp-server enable traps bgp cbgp2 + snmp-server enable traps cef resource-failure peer-state-change peer-fib-state-change inconsistency + snmp-server enable traps dlsw + snmp-server enable traps frame-relay + snmp-server enable traps frame-relay subif + snmp-server enable traps hsrp + snmp-server enable traps ipmulticast + snmp-server enable traps isis + snmp-server enable traps msdp + snmp-server enable traps mvpn + snmp-server enable traps pim neighbor-change rp-mapping-change invalid-pim-message + snmp-server enable traps rsvp + snmp-server enable traps ipsla + snmp-server enable traps slb real virtual csrp + snmp-server enable traps syslog + snmp-server enable traps event-manager + snmp-server enable traps pki + snmp-server enable traps ethernet cfm alarm + snmp-server enable traps mpls vpn + snmp-server enable traps vrfmib vrf-up vrf-down vnet-trunk-up vnet-trunk-down + snmp-server host 172.16.2.99 informs version 2c check msdp + snmp-server host 172.16.2.99 check slb + snmp-server host 172.16.2.99 checktrap isis + snmp-server host 172.16.2.1 version 3 priv newtera rsrb + snmp-server host 172.16.2.1 version 3 noauth replaceUser slb + snmp-server host 172.16.2.1 version 2c trapsac tty + snmp-server host 172.16.1.1 version 3 auth group0 tty + snmp-server context contextWord1 + snmp-server context contextWord2 + snmp-server cache interval 2 + snmp-server password-policy policy1 define max-len 24 upper-case 12 lower-case 12 special-char 32 digits 23 change 3 + snmp-server password-policy policy2 define min-len 12 upper-case 12 special-char 22 change 9 + snmp-server password-policy policy3 define min-len 12 max-len 12 upper-case 12 special-char 22 digits 23 change 11 + snmp-server accounting commands default + snmp-server inform pending 2 + """, + ) + playbook = {"config": {}} + deleted = [ + "no snmp-server accounting commands default", + "no snmp-server cache interval 2", + "no snmp-server chassis-id this is a chassis id string", + "no snmp-server contact this is contact string", + "no snmp-server inform pending 2", + "no snmp-server ip dscp 2", + "no snmp-server location thi sis a good location", + "no snmp-server packetsize 500", + "no snmp-server queue-length 2", + "no snmp-server trap timeout 2", + "no snmp-server source-interface informs Loopback999", + "no snmp-server trap-source GigabitEthernet0/0", + "no snmp-server system-shutdown", + "no snmp-server enable traps auth-framework", + "no snmp-server enable traps bfd", + "no snmp-server enable traps bgp", + "no snmp-server enable traps bridge newroot topologychange", + "no snmp-server enable traps casa", + "no snmp-server enable traps eigrp", + "no snmp-server enable traps energywise", + "no snmp-server enable traps event-manager", + "no snmp-server enable traps flowmon", + "no snmp-server enable traps hsrp", + "no snmp-server enable traps ipsla", + "no snmp-server enable traps isis", + "no snmp-server enable traps msdp", + "no snmp-server enable traps mvpn", + "no snmp-server enable traps mpls vpn", + "no snmp-server enable traps pki", + "no snmp-server enable traps pw vc", + "no snmp-server enable traps rsvp", + "no snmp-server enable traps syslog", + "no snmp-server enable traps tty", + "no snmp-server enable traps vrfmib vrf-up vrf-down vnet-trunk-up vnet-trunk-down", + "no snmp-server enable traps ipmulticast", + "no snmp-server enable traps ike policy add", + "no snmp-server enable traps ike policy delete", + "no snmp-server enable traps ike tunnel start", + "no snmp-server enable traps ike tunnel stop", + "no snmp-server enable traps ipsec cryptomap add", + "no snmp-server enable traps ipsec cryptomap delete", + "no snmp-server enable traps ipsec cryptomap attach", + "no snmp-server enable traps ipsec cryptomap detach", + "no snmp-server enable traps ipsec tunnel start", + "no snmp-server enable traps ipsec tunnel stop", + "no snmp-server enable traps ipsec too-many-sas", + "no snmp-server enable traps ospf cisco-specific errors", + "no snmp-server enable traps ospf cisco-specific retransmit", + "no snmp-server enable traps ospf cisco-specific lsa", + "no snmp-server enable traps ospf cisco-specific state-change nssa-trans-change", + "no snmp-server enable traps ospf cisco-specific state-change shamlink interface", + "no snmp-server enable traps ospf cisco-specific state-change shamlink neighbor", + "no snmp-server enable traps ospf errors", + "no snmp-server enable traps ospf retransmit", + "no snmp-server enable traps ospf lsa", + "no snmp-server enable traps ospf state-change", + "no snmp-server enable traps l2tun pseudowire status", + "no snmp-server enable traps l2tun session", + "no snmp-server enable traps pim neighbor-change rp-mapping-change invalid-pim-message", + "no snmp-server enable traps snmp authentication linkdown linkup warmstart coldstart", + "no snmp-server enable traps frame-relay", + "no snmp-server enable traps cef resource-failure peer-state-change peer-fib-state-change inconsistency", + "no snmp-server enable traps dlsw", + "no snmp-server enable traps ethernet evc create delete status", + "no snmp-server enable traps ethernet cfm alarm", + "no snmp-server enable traps ethernet cfm cc mep-up mep-down cross-connect loop config", + "no snmp-server enable traps ethernet cfm crosscheck mep-missing mep-unknown service-up", + "no snmp-server host 172.16.1.1 version 3 auth group0 tty", + "no snmp-server host 172.16.2.1 version 3 priv newtera rsrb", + "no snmp-server host 172.16.2.1 version 3 noauth replaceUser slb", + "no snmp-server host 172.16.2.1 version 2c trapsac tty", + "no snmp-server host 172.16.2.99 informs version 2c check msdp", + "no snmp-server host 172.16.2.99 check slb", + "no snmp-server host 172.16.2.99 checktrap isis", + "no snmp-server group group0 v3 auth", + "no snmp-server group group1 v1 notify me access 2", + "no snmp-server group group2 v3 priv", + "no snmp-server group replaceUser v3 noauth", + "no snmp-server engineID local AB0C5342FA0A", + "no snmp-server engineID remote 172.16.0.1 udp-port 22 AB0C5342FAAA", + "no snmp-server engineID remote 172.16.0.2 udp-port 23 AB0C5342FAAB", + "no snmp-server community commu1 view view1 ro ipv6 te", + "no snmp-server community commu2 ro 1322", + "no snmp-server community commu3 rw paul", + "no snmp-server context contextWord1", + "no snmp-server context contextWord2", + "no snmp-server password-policy policy1 define max-len 24 upper-case 12 lower-case 12 special-char 32 digits 23 change 3", + "no snmp-server password-policy policy2 define min-len 12 upper-case 12 special-char 22 change 9", + "no snmp-server password-policy policy3 define min-len 12 max-len 12 upper-case 12 special-char 22 digits 23 change 11", + "no snmp-server user new@user! new.family$ v1 access 24", + "no snmp-server user paul familypaul v3 access ipv6 ipv6acl", + "no snmp-server user replaceUser replaceUser v3", + "no snmp-server user flow mfamily v3 access 27", + ] + playbook["state"] = "deleted" + set_module_args(playbook) + self.maxDiff = None + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(deleted)) + + def test_ios_snmp_server_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + snmp-server engineID remote 172.16.0.2 udp-port 23 AB0C5342FAAB + snmp-server user newuser newfamily v1 access 24 + snmp-server user replaceUser replaceUser v3 + snmp-server group group2 v3 priv + snmp-server group replaceUser v3 noauth + snmp-server community commu3 RW paul + snmp-server trap timeout 2 + snmp-server source-interface informs Loopback999 + snmp-server packetsize 500 + snmp-server queue-length 2 + snmp-server location thi sis a good location + snmp-server ip dscp 2 + snmp-server contact this is contact string + snmp-server chassis-id this is a chassis id string + snmp-server system-shutdown + snmp-server enable traps snmp authentication linkdown linkup coldstart warmstart + snmp-server enable traps ospf state-change + snmp-server enable traps ospf errors + snmp-server enable traps ospf retransmit + snmp-server enable traps ospf lsa + snmp-server enable traps ospf cisco-specific state-change nssa-trans-change + snmp-server enable traps ospf cisco-specific state-change shamlink interface + snmp-server enable traps ospf cisco-specific state-change shamlink neighbor + snmp-server enable traps ospf cisco-specific errors + snmp-server enable traps ospf cisco-specific retransmit + snmp-server enable traps ospf cisco-specific lsa + snmp-server enable traps ethernet cfm cc mep-up mep-down cross-connect loop config + snmp-server enable traps ethernet cfm crosscheck mep-missing mep-unknown service-up + snmp-server enable traps auth-framework sec-violation + snmp-server enable traps energywise + snmp-server enable traps ethernet evc status create delete + snmp-server enable traps bridge newroot topologychange + snmp-server enable traps vtp + snmp-server enable traps ike policy add + snmp-server enable traps ike policy delete + snmp-server enable traps ike tunnel start + snmp-server enable traps ike tunnel stop + snmp-server enable traps ipsec cryptomap add + snmp-server enable traps ipsec cryptomap delete + snmp-server enable traps ipsec cryptomap attach + snmp-server enable traps ipsec cryptomap detach + snmp-server enable traps ipsec tunnel start + snmp-server enable traps ipsec tunnel stop + snmp-server enable traps ipsec too-many-sas + snmp-server enable traps bfd + snmp-server enable traps bgp + snmp-server enable traps bgp cbgp2 + snmp-server enable traps cef resource-failure peer-state-change peer-fib-state-change inconsistency + snmp-server enable traps dlsw + snmp-server enable traps frame-relay + snmp-server enable traps frame-relay subif + snmp-server enable traps hsrp + snmp-server enable traps ipmulticast + snmp-server enable traps mpls vpn + snmp-server enable traps vrfmib vrf-up vrf-down vnet-trunk-up vnet-trunk-down + snmp-server host 172.16.2.99 informs version 2c check msdp + snmp-server host 172.16.2.99 check slb + snmp-server host 172.16.1.1 version 3 auth group0 tty + snmp-server context contextWord1 + snmp-server context contextBAD + snmp-server file-transfer access-group testAcl protocol ftp + snmp-server file-transfer access-group testAcl protocol rcp + snmp-server password-policy policy3 define min-len 12 max-len 12 upper-case 12 special-char 22 digits 23 change 11 + snmp-server accounting commands default + snmp-server inform pending 2 + snmp-server view no-write.test testiso excluded + snmp-server view test-view! test-test included + """, + ) + + playbook = { + "config": { + "accounting": {"command": "default"}, + "cache": 2, + "chassis_id": "this is a chassis id string", + "communities": [ + {"acl_v6": "te", "name": "commu1", "ro": True, "view": "view1"}, + {"acl_v4": "1322", "name": "commu2", "ro": True}, + {"acl_v4": "paul", "name": "commu3", "rw": True}, + ], + "contact": "this is contact string", + "context": ["contextWord2", "contextWord1"], + "engine_id": [ + {"id": "AB0C5342FA0A", "local": True}, + {"id": "AB0C5342FAAA", "remote": {"host": "172.16.0.1", "udp_port": 22}}, + {"id": "AB0C5342FAAB", "remote": {"host": "172.16.0.2", "udp_port": 23}}, + ], + "file_transfer": {"access_group": "testAcl", "protocol": ["ftp", "rcp"]}, + "groups": [ + {"group": "group0", "version": "v3", "version_option": "auth"}, + {"acl_v4": "2", "group": "group1", "notify": "me", "version": "v1"}, + {"group": "group2", "version": "v3", "version_option": "priv"}, + {"group": "replaceUser", "version": "v3", "version_option": "noauth"}, + ], + "hosts": [ + { + "community_string": "group0", + "host": "172.16.1.1", + "traps": ["tty"], + "version": "3", + "version_option": "auth", + }, + { + "community_string": "newtera", + "host": "172.16.2.1", + "traps": ["rsrb"], + "version": "3", + "version_option": "priv", + }, + { + "community_string": "replaceUser", + "host": "172.16.2.1", + "traps": ["slb"], + "version": "3", + "version_option": "noauth", + }, + { + "community_string": "trapsac", + "host": "172.16.2.1", + "traps": ["tty"], + "version": "2c", + }, + { + "community_string": "check", + "host": "172.16.2.99", + "informs": True, + "traps": ["msdp"], + "version": "2c", + }, + {"community_string": "check", "host": "172.16.2.99", "traps": ["slb"]}, + {"community_string": "checktrap", "host": "172.16.2.99", "traps": ["isis"]}, + ], + "inform": {"pending": 2}, + "ip": {"dscp": 2}, + "location": "thi sis a good location", + "packet_size": 500, + "password_policy": [ + { + "change": 3, + "digits": 23, + "lower_case": 12, + "max_len": 24, + "policy_name": "policy1", + "special_char": 32, + "upper_case": 12, + }, + { + "change": 9, + "min_len": 12, + "policy_name": "policy2", + "special_char": 22, + "upper_case": 12, + }, + { + "change": 11, + "digits": 23, + "max_len": 12, + "min_len": 12, + "policy_name": "policy3", + "special_char": 22, + "upper_case": 12, + }, + ], + "queue_length": 2, + "source_interface": "Loopback999", + "system_shutdown": True, + "trap_source": "GigabitEthernet0/0", + "trap_timeout": 2, + "traps": { + "auth_framework": {"enable": True}, + "bfd": {"enable": True}, + "bgp": {"cbgp2": True, "enable": True}, + "bridge": {"enable": True, "newroot": True, "topologychange": True}, + "casa": True, + "cef": { + "enable": True, + "inconsistency": True, + "peer_fib_state_change": True, + "peer_state_change": True, + "resource_failure": True, + }, + "dlsw": {"enable": True}, + "eigrp": True, + "energywise": True, + "ethernet": { + "cfm": { + "alarm": True, + "cc": { + "config": True, + "cross_connect": True, + "loop": True, + "mep_down": True, + "mep_up": True, + }, + "crosscheck": { + "mep_missing": True, + "mep_unknown": True, + "service_up": True, + }, + }, + "evc": {"create": True, "delete": True, "status": True}, + }, + "event_manager": True, + "flowmon": True, + "frame_relay": {"enable": True}, + "hsrp": True, + "ike": { + "policy": {"add": True, "delete": True}, + "tunnel": {"start": True, "stop": True}, + }, + "ipmulticast": True, + "ipsec": { + "cryptomap": {"add": True, "attach": True, "delete": True, "detach": True}, + "too_many_sas": True, + "tunnel": {"start": True, "stop": True}, + }, + "ipsla": True, + "isis": True, + "l2tun": {"pseudowire_status": True, "session": True}, + "mpls_vpn": True, + "msdp": True, + "mvpn": True, + "ospf": { + "cisco_specific": { + "error": True, + "lsa": True, + "retransmit": True, + "state_change": { + "nssa_trans_change": True, + "shamlink": {"interface": True, "neighbor": True}, + }, + }, + "error": True, + "lsa": True, + "retransmit": True, + "state_change": True, + }, + "pim": { + "enable": True, + "invalid_pim_message": True, + "neighbor_change": True, + "rp_mapping_change": True, + }, + "pki": True, + "pw_vc": True, + "rsvp": True, + "snmp": { + "authentication": True, + "coldstart": True, + "linkdown": True, + "linkup": True, + "warmstart": True, + }, + "syslog": True, + "tty": True, + "vrfmib": { + "vnet_trunk_down": True, + "vnet_trunk_up": True, + "vrf_down": True, + "vrf_up": True, + }, + }, + "users": [ + {"acl_v4": "24", "group": "newfamily", "username": "newuser", "version": "v1"}, + {"acl_v4": "ipv6", "group": "familypaul", "username": "paul", "version": "v3"}, + {"group": "replaceUser", "username": "replaceUser", "version": "v3"}, + ], + "views": [ + {"name": "no-write.test", "family_name": "testiso", "excluded": True}, + {"name": "newView", "family_name": "TestFamilyName", "included": True}, + ], + }, + } + overridden = [ + "snmp-server cache interval 2", + "snmp-server trap-source GigabitEthernet0/0", + "snmp-server enable traps casa", + "snmp-server enable traps eigrp", + "snmp-server enable traps event-manager", + "snmp-server enable traps flowmon", + "snmp-server enable traps ipsla", + "snmp-server enable traps isis", + "snmp-server enable traps msdp", + "snmp-server enable traps mvpn", + "snmp-server enable traps pki", + "snmp-server enable traps pw vc", + "snmp-server enable traps rsvp", + "snmp-server enable traps syslog", + "snmp-server enable traps tty", + "snmp-server enable traps l2tun pseudowire status", + "snmp-server enable traps l2tun session", + "snmp-server enable traps pim neighbor-change rp-mapping-change invalid-pim-message", + "snmp-server enable traps ethernet cfm alarm", + "snmp-server host 172.16.2.1 version 3 priv newtera rsrb", + "snmp-server host 172.16.2.1 version 3 noauth replaceUser slb", + "snmp-server host 172.16.2.1 version 2c trapsac tty", + "snmp-server host 172.16.2.99 checktrap isis", + "snmp-server group group0 v3 auth", + "snmp-server group group1 v1 notify me access 2", + "snmp-server engineID local AB0C5342FA0A", + "snmp-server engineID remote 172.16.0.1 udp-port 22 AB0C5342FAAA", + "snmp-server community commu1 view view1 ro ipv6 te", + "snmp-server community commu2 ro 1322", + "snmp-server context contextWord2", + "no snmp-server context contextBAD", + "snmp-server password-policy policy1 define max-len 24 upper-case 12 lower-case 12 special-char 32 digits 23 change 3", + "snmp-server password-policy policy2 define min-len 12 upper-case 12 special-char 22 change 9", + "snmp-server user paul familypaul v3 access ipv6", + "snmp-server view newView TestFamilyName included", + "no snmp-server view test-view! test-test included", + ] + playbook["state"] = "overridden" + set_module_args(playbook) + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(overridden)) + + def test_ios_snmp_server_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + snmp-server host 172.16.2.99 informs version 2c check msdp + snmp-server host 172.16.2.99 check slb + snmp-server host 172.16.1.1 version 3 auth group0 tty + """, + ) + playbook = { + "config": { + "hosts": [ + { + "community_string": "group0", + "host": "172.16.1.1", + "traps": ["tty"], + "version": "3", + "version_option": "auth", + }, + { + "community_string": "check", + "host": "172.16.2.99", + "informs": True, + "traps": ["msdp"], + "version": "2c", + }, + {"community_string": "check", "host": "172.16.2.99", "traps": ["slb"]}, + ], + }, + } + overridden = [] + playbook["state"] = "replaced" + set_module_args(playbook) + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["commands"]), sorted(overridden)) + + def test_ios_snmp_server_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + snmp-server engineID local AB0C5342FA0A + snmp-server engineID remote 172.16.0.2 udp-port 23 AB0C5342FAAB + snmp-server user paul familypaul v3 access ipv6 ipv6acl + snmp-server enable traps ospf state-change + snmp-server enable traps ospf errors + snmp-server enable traps ospf retransmit + snmp-server enable traps ospf lsa + snmp-server enable traps ospf cisco-specific state-change nssa-trans-change + snmp-server enable traps ospf cisco-specific state-change shamlink interface + snmp-server enable traps ospf cisco-specific state-change shamlink neighbor + snmp-server enable traps ospf cisco-specific errors + snmp-server enable traps ospf cisco-specific retransmit + snmp-server enable traps ospf cisco-specific lsa + snmp-server enable traps envmon status + snmp-server enable traps envmon supply + snmp-server enable traps envmon temperature + snmp-server enable traps envmon fan supply + snmp-server enable traps envmon fan temperature + snmp-server enable traps ethernet cfm cc mep-up mep-down cross-connect loop config + snmp-server enable traps ethernet cfm crosscheck mep-missing mep-unknown service-up + snmp-server host 172.16.2.99 informs version 2c check msdp stun + snmp-server host 172.16.2.99 check slb pki + snmp-server host 172.16.2.99 checktrap isis hsrp + snmp-server host 172.16.2.1 version 3 priv newtera rsrb pim rsvp slb pki + snmp-server host 172.16.2.1 version 3 noauth replaceUser slb pki + snmp-server host 172.16.2.1 version 2c trapsac tty bgp + snmp-server group mygrp v3 priv read readme write writeit notify notifyme access acessing + snmp-server host 172.16.1.1 version 3 auth group0 tty bgp + """, + ), + state="parsed", + ), + ) + parsed = { + "engine_id": [ + {"id": "AB0C5342FA0A", "local": True}, + {"id": "AB0C5342FAAB", "remote": {"host": "172.16.0.2", "udp_port": 23}}, + ], + "users": [ + {"username": "paul", "group": "familypaul", "version": "v3", "acl_v6": "ipv6acl"}, + ], + "traps": { + "ospf": { + "state_change": True, + "error": True, + "retransmit": True, + "lsa": True, + "cisco_specific": { + "state_change": { + "nssa_trans_change": True, + "shamlink": {"interface": True, "neighbor": True}, + }, + "error": True, + "retransmit": True, + "lsa": True, + }, + }, + "envmon": { + "status": True, + "supply": True, + "temperature": True, + "fan": {"supply": True, "temperature": True}, + }, + "ethernet": { + "cfm": { + "cc": { + "mep_up": True, + "mep_down": True, + "cross_connect": True, + "loop": True, + "config": True, + }, + "crosscheck": { + "mep_missing": True, + "mep_unknown": True, + "service_up": True, + }, + }, + }, + }, + "hosts": [ + { + "host": "172.16.1.1", + "community_string": "group0", + "traps": ["tty", "bgp"], + "version": "3", + "version_option": "auth", + }, + { + "host": "172.16.2.1", + "community_string": "newtera", + "traps": ["rsrb", "pim", "rsvp", "slb", "pki"], + "version": "3", + "version_option": "priv", + }, + { + "host": "172.16.2.1", + "community_string": "replaceUser", + "traps": ["slb", "pki"], + "version": "3", + "version_option": "noauth", + }, + { + "host": "172.16.2.1", + "community_string": "trapsac", + "traps": ["tty", "bgp"], + "version": "2c", + }, + { + "host": "172.16.2.99", + "informs": True, + "community_string": "check", + "traps": ["msdp", "stun"], + "version": "2c", + }, + {"host": "172.16.2.99", "community_string": "check", "traps": ["slb", "pki"]}, + {"host": "172.16.2.99", "community_string": "checktrap", "traps": ["isis", "hsrp"]}, + ], + "groups": [ + { + "group": "mygrp", + "version": "v3", + "version_option": "priv", + "notify": "notifyme", + "read": "readme", + "write": "writeit", + "acl_v4": "acessing", + }, + ], + } + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(result["parsed"], parsed) + + def test_ios_snmp_server_gathered(self): + self.execute_show_command.return_value = dedent( + """\ + snmp-server host 172.16.2.99 checktrap isis hsrp + snmp-server host 172.16.2.1 version 3 priv newtera rsrb pim rsvp slb pki + snmp-server host 172.16.2.1 version 3 noauth replace-User! slb pki + """, + ) + self.execute_show_command_user.return_value = dedent( + """\ + User name: TESTU22 + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active IPv6 access-list: testv6acl + Authentication Protocol: MD5 + Privacy Protocol: AES128 + Group-name: TESTG + + User name: TESTU23 + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active access-list: aclWord + Authentication Protocol: MD5 + Privacy Protocol: AES128 + Group-name: TESTG + + User name: TESTU24 + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active access-list: 22 + Authentication Protocol: MD5 + Privacy Protocol: None + Group-name: TESTG + + User name: TESTU25 + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active access-list: 22 + Authentication Protocol: MD5 + Privacy Protocol: None + Group-name: TESTG + + User name: testus2 + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active + Authentication Protocol: MD5 + Privacy Protocol: AES128 + Group-name: TESTG + + User name: TESTU + Engine ID: 800000090300525400012D4A + storage-type: nonvolatile active + Authentication Protocol: MD5 + Privacy Protocol: AES128 + Group-name: TESTG + """, + ) + set_module_args(dict(state="gathered")) + gathered = { + "hosts": [ + { + "host": "172.16.2.1", + "community_string": "newtera", + "traps": ["rsrb", "pim", "rsvp", "slb", "pki"], + "version": "3", + "version_option": "priv", + }, + { + "host": "172.16.2.1", + "community_string": "replace-User!", + "traps": ["slb", "pki"], + "version": "3", + "version_option": "noauth", + }, + {"host": "172.16.2.99", "community_string": "checktrap", "traps": ["isis", "hsrp"]}, + ], + "users": [ + { + "group": "TESTG", + "username": "TESTU", + }, + { + "acl_v6": "testv6acl", + "group": "TESTG", + "username": "TESTU22", + }, + { + "acl_v4": "aclWord", + "group": "TESTG", + "username": "TESTU23", + }, + { + "acl_v4": "22", + "group": "TESTG", + "username": "TESTU24", + }, + { + "acl_v4": "22", + "group": "TESTG", + "username": "TESTU25", + }, + { + "group": "TESTG", + "username": "testus2", + }, + { + "acl_v4": "22", + "group": "usrgrp", + "username": "us1", + "version": "v1", + }, + ], + } + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["gathered"]), sorted(gathered)) + + def test_ios_snmp_server_rendered(self): + set_module_args( + { + "config": { + "engine_id": [ + {"id": "AB0C5342FA0A", "local": True}, + {"id": "AB0C5342FAAB", "remote": {"host": "172.16.0.2", "udp_port": 23}}, + ], + "views": [ + {"family_name": "iso", "name": "ro"}, + {"family_name": "internet", "included": True, "name": "ro"}, + {"family_name": "iso", "included": True, "name": "rw"}, + {"family_name": "internet", "included": True, "name": "rw"}, + ], + "groups": [ + { + "group": "mygrp", + "version": "v3", + "version_option": "priv", + "notify": "notifyme", + "read": "readme", + "write": "writeit", + "acl_v4": "acessing", + }, + ], + "users": [ + { + "username": "paul", + "group": "familypaul", + "version": "v3", + "acl_v4": "ipv6", + }, + ], + "traps": { + "ospf": { + "state_change": True, + "error": True, + "retransmit": True, + "lsa": True, + "cisco_specific": { + "state_change": { + "nssa_trans_change": True, + "shamlink": {"interface": True, "neighbor": True}, + }, + "error": True, + "retransmit": True, + "lsa": True, + }, + }, + "ethernet": { + "cfm": { + "cc": { + "mep_up": True, + "mep_down": True, + "cross_connect": True, + "loop": True, + "config": True, + }, + "crosscheck": { + "mep_missing": True, + "mep_unknown": True, + "service_up": True, + }, + }, + }, + }, + "hosts": [ + { + "host": "172.16.1.1", + "community_string": "group0", + "traps": ["tty"], + "version": "3", + "version_option": "auth", + "vrf": "mgmt", + }, + { + "host": "172.16.2.1", + "community_string": "newtera", + "traps": ["rsrb"], + "version": "3", + "version_option": "priv", + }, + { + "host": "172.16.2.1", + "community_string": "replaceUser", + "traps": ["slb"], + "version": "3", + "version_option": "noauth", + }, + { + "host": "172.16.2.1", + "community_string": "trapsac", + "traps": ["tty"], + "version": "2c", + }, + { + "host": "172.16.2.99", + "informs": True, + "community_string": "check", + "traps": ["msdp"], + "version": "2c", + }, + {"host": "172.16.2.99", "community_string": "check", "traps": ["slb"]}, + {"host": "172.16.2.99", "community_string": "checktrap", "traps": ["isis"]}, + ], + }, + "state": "rendered", + }, + ) + rendered = [ + "snmp-server enable traps ospf cisco-specific errors", + "snmp-server enable traps ospf cisco-specific retransmit", + "snmp-server enable traps ospf cisco-specific lsa", + "snmp-server enable traps ospf cisco-specific state-change nssa-trans-change", + "snmp-server enable traps ospf cisco-specific state-change shamlink interface", + "snmp-server enable traps ospf cisco-specific state-change shamlink neighbor", + "snmp-server enable traps ospf errors", + "snmp-server enable traps ospf retransmit", + "snmp-server enable traps ospf lsa", + "snmp-server enable traps ospf state-change", + "snmp-server enable traps ethernet cfm cc mep-up mep-down cross-connect loop config", + "snmp-server enable traps ethernet cfm crosscheck mep-missing mep-unknown service-up", + "snmp-server host 172.16.1.1 version 3 auth vrf mgmt group0 tty", + "snmp-server host 172.16.2.1 version 3 priv newtera rsrb", + "snmp-server host 172.16.2.1 version 3 noauth replaceUser slb", + "snmp-server host 172.16.2.1 version 2c trapsac tty", + "snmp-server host 172.16.2.99 informs version 2c check msdp", + "snmp-server host 172.16.2.99 check slb", + "snmp-server host 172.16.2.99 checktrap isis", + "snmp-server group mygrp v3 priv read readme write writeit notify notifyme access acessing", + "snmp-server engineID local AB0C5342FA0A", + "snmp-server engineID remote 172.16.0.2 udp-port 23 AB0C5342FAAB", + "snmp-server user paul familypaul v3 access ipv6", + "snmp-server view ro iso", + "snmp-server view ro internet included", + "snmp-server view rw iso included", + "snmp-server view rw internet included", + ] + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["rendered"]), sorted(rendered)) + + def test_ios_snmp_server_rendered_user_options(self): + set_module_args( + { + "config": { + "users": [ + { + "username": "paul", + "group": "familypaul", + "version": "v3", + "authentication": {"algorithm": "md5", "password": "somepass"}, + "encryption": { + "priv": "aes", + "priv_option": 128, + "password": "somepass", + }, + }, + ], + }, + "state": "rendered", + }, + ) + rendered = ["snmp-server user paul familypaul v3 auth md5 somepass priv aes 128 somepass"] + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(sorted(result["rendered"]), sorted(rendered)) + + def test_ios_snmpv3_user_server_merged(self): + self.execute_show_command.return_value = dedent( + """\ + snmp-server user rhcisco testfamily v3 access ipv6 + """, + ) + + self.execute_show_command_user.return_value = dedent( + """\ + User name: replaceUser + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active access-list: 22 + Authentication Protocol: MD5 + Privacy Protocol: None + Group-name: replaceUser + + User name: paul + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active access-list: ipv6 + Authentication Protocol: MD5 + Privacy Protocol: None + Group-name: familypaul + + User name: flow + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active access-list: 27 + Authentication Protocol: MD5 + Privacy Protocol: None + Group-name: mfamily + """, + ) + + playbook = { + "config": { + "users": [ + {"acl_v4": "ipv6", "group": "familypaul", "username": "paul", "version": "v3"}, + {"acl_v4": "27", "group": "mfamily", "username": "flow", "version": "v3"}, + ], + }, + } + merged = [ + "snmp-server user paul familypaul v3 access ipv6", + "snmp-server user flow mfamily v3 access 27", + ] + + playbook["state"] = "merged" + set_module_args(playbook) + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(merged)) + + def test_ios_snmpv3_user_server_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + snmp-server user newuser newfamily v1 access 24 + """, + ) + + self.execute_show_command_user.return_value = dedent( + """\ + User name: replaceUser + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active access-list: 22 + Authentication Protocol: MD5 + Privacy Protocol: None + Group-name: replaceUser + + User name: flow + Engine ID: 000000090200000000000A0B + storage-type: nonvolatile active access-list: 27 + Authentication Protocol: MD5 + Privacy Protocol: None + Group-name: mfamily + """, + ) + + playbook = { + "config": { + "users": [ + {"group": "replaceUser", "username": "replaceUser", "version": "v3"}, + {"acl_v4": "27", "group": "mfamily", "username": "flow", "version": "v3"}, + ], + }, + } + overridden = [ + "no snmp-server user flow mfamily v3", + "snmp-server user flow mfamily v3 access 27", + "no snmp-server user newuser newfamily v1 access 24", + ] + playbook["state"] = "overridden" + set_module_args(playbook) + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(overridden)) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_static_routes.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_static_routes.py new file mode 100644 index 000000000..bbc2b2e0f --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_static_routes.py @@ -0,0 +1,2166 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_static_routes +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule + + +class TestIosStaticRoutesModule(TestIosModule): + module = ios_static_routes + + def setUp(self): + super(TestIosStaticRoutesModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.static_routes.static_routes." + "Static_routesFacts.get_static_routes_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestIosStaticRoutesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def test_ios_static_routes_merged(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf ansible_vrf 0.0.0.0 0.0.0.0 198.51.101.1 name test_vrf_1 track 150 tag 100 + ip route vrf ansible_vrf 192.0.2.0 255.255.255.0 192.0.2.1 name test_vrf_2 track 175 tag 50 + ip route vrf ansible_vrf 192.51.110.0 255.255.255.255 GigabitEthernet0/2 192.51.111.1 10 name partner + ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 multicast name route_1 tag 60 + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet0/2 2001:DB8:0:3::2 tag 105 name test_v6 + ipv6 route vrf ansible_vrf 2001:DB8:0:4::/64 2001:DB8:0:4::2 tag 115 name test_v6_vrf + """, + ) + set_module_args( + dict( + config=[ + dict( + vrf="ansible_vrf", + address_families=[ + dict( + afi="ipv4", + routes=[ + dict( + dest="192.0.2.0 255.255.255.0", + next_hops=[ + dict( + forward_router_address="192.0.2.1", + name="test_vrf", + tag=50, + track=150, + ), + ], + ), + ], + ), + ], + ), + dict( + address_families=[ + dict( + afi="ipv4", + routes=[ + dict( + dest="198.51.100.0 255.255.255.0", + next_hops=[ + dict( + forward_router_address="198.51.101.1", + name="route_1", + distance_metric=110, + tag=40, + multicast=True, + ), + ], + ), + ], + ), + ], + ), + ], + state="merged", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "ip route vrf ansible_vrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150", + "ip route 198.51.100.0 255.255.255.0 198.51.101.1 110 tag 40 name route_1 multicast", + ] + self.assertEqual(result["commands"], commands) + + def test_ios_static_routes_merged_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.12", + "distance_metric": 34, + "tag": 22, + "name": "nm12", + "track": 11, + }, + { + "forward_router_address": "198.51.101.1", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.20", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.3", + "name": "merged_route_3", + }, + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.11", + "distance_metric": 22, + "tag": 22, + "permanent": True, + "name": "nm1", + "multicast": True, + }, + ], + "dest": "198.51.100.0/24", + }, + ], + }, + { + "afi": "ipv6", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet4", + "permanent": True, + "name": "onlyname", + "multicast": True, + }, + { + "interface": "GigabitEthernet3", + "tag": 11, + "permanent": True, + "name": "qq", + "unicast": True, + }, + { + "interface": "GigabitEthernet2", + "tag": 2, + "permanent": True, + "name": "nm1", + }, + {"interface": "Null0"}, + { + "forward_router_address": "2001:DB8:0:3::3", + "distance_metric": 22, + "permanent": True, + "name": "pp1", + "multicast": True, + }, + { + "forward_router_address": "2001:DB8:0:3::2", + "tag": 22, + "name": "name1", + "track": 22, + }, + ], + "dest": "2001:DB8:0:3::/64", + }, + ], + }, + ], + }, + { + "vrf": "testVrf2", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + {"forward_router_address": "10.1.1.2", "track": 10}, + { + "forward_router_address": "10.1.1.3", + "distance_metric": 22, + "track": 10, + }, + ], + "dest": "192.168.1.0/24", + }, + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + { + "vrf": "testVrf", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + { + "forward_router_address": "192.0.2.1", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + { + "forward_router_address": "192.0.2.2", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + ], + state="merged", + ), + ) + result = self.execute_module(changed=False) + commands = [] + self.assertEqual(result["commands"], commands) + + def test_ios_static_routes_replaced(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.12", + "distance_metric": 34, + "tag": 22, + "name": "nm12", + "track": 11, + }, + { + "forward_router_address": "198.51.101.1", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.20", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.3", + "name": "merged_route_3", + }, + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.11", + "distance_metric": 22, + "tag": 22, + "permanent": True, + "name": "nm1", + "multicast": True, + }, + ], + "dest": "198.51.100.1/24", + }, + ], + }, + { + "afi": "ipv6", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet4", + "permanent": True, + "name": "onlyname", + "multicast": True, + }, + { + "interface": "GigabitEthernet3", + "tag": 11, + "permanent": True, + "name": "qq", + "unicast": True, + }, + { + "interface": "GigabitEthernet2", + "tag": 2, + "permanent": True, + "name": "nm1", + }, + {"interface": "Null0"}, + { + "forward_router_address": "2001:DB8:0:3::3", + "distance_metric": 22, + "permanent": True, + "name": "pp1", + "multicast": True, + }, + { + "forward_router_address": "2001:DB8:0:3::2", + "tag": 22, + "name": "name1", + "track": 22, + }, + ], + "dest": "2001:DB8:0:4::/64", + }, + ], + }, + ], + }, + { + "vrf": "testVrf2", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + {"forward_router_address": "10.1.1.2", "track": 10}, + { + "forward_router_address": "10.1.1.3", + "distance_metric": 22, + "track": 10, + }, + ], + "dest": "192.168.1.1/24", + }, + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + { + "vrf": "testVrf", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + { + "forward_router_address": "192.0.2.1", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + { + "forward_router_address": "192.0.2.2", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + ], + state="replaced", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "ip route 198.51.100.1 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11", + "ip route 198.51.100.1 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "ip route 198.51.100.1 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150", + "ip route 198.51.100.1 255.255.255.0 198.51.101.3 name merged_route_3", + "ip route 198.51.100.1 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast", + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3", + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast", + "ipv6 route 2001:DB8:0:4::/64 GigabitEthernet4 multicast permanent name onlyname", + "ipv6 route 2001:DB8:0:4::/64 GigabitEthernet3 unicast tag 11 permanent name qq", + "ipv6 route 2001:DB8:0:4::/64 GigabitEthernet2 tag 2 permanent name nm1", + "ipv6 route 2001:DB8:0:4::/64 Null0", + "ipv6 route 2001:DB8:0:4::/64 2001:DB8:0:3::3 22 multicast permanent name pp1", + "ipv6 route 2001:DB8:0:4::/64 2001:DB8:0:3::2 tag 22 track 22 name name1", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1", + "no ipv6 route 2001:DB8:0:3::/64 Null0", + "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1", + "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1", + "ip route vrf testVrf2 192.168.1.1 255.255.255.0 10.1.1.2 track 10", + "ip route vrf testVrf2 192.168.1.1 255.255.255.0 10.1.1.3 22 track 10", + "no ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10", + "no ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10", + ] + self.assertEqual(result["commands"], commands) + + def test_ios_static_routes_replaced_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.12", + "distance_metric": 34, + "tag": 22, + "name": "nm12", + "track": 11, + }, + { + "forward_router_address": "198.51.101.1", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.20", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.3", + "name": "merged_route_3", + }, + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.11", + "distance_metric": 22, + "tag": 22, + "permanent": True, + "name": "nm1", + "multicast": True, + }, + ], + "dest": "198.51.100.0/24", + }, + ], + }, + { + "afi": "ipv6", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet4", + "permanent": True, + "name": "onlyname", + "multicast": True, + }, + { + "interface": "GigabitEthernet3", + "tag": 11, + "permanent": True, + "name": "qq", + "unicast": True, + }, + { + "interface": "GigabitEthernet2", + "tag": 2, + "permanent": True, + "name": "nm1", + }, + {"interface": "Null0"}, + { + "forward_router_address": "2001:DB8:0:3::3", + "distance_metric": 22, + "permanent": True, + "name": "pp1", + "multicast": True, + }, + { + "forward_router_address": "2001:DB8:0:3::2", + "tag": 22, + "name": "name1", + "track": 22, + }, + ], + "dest": "2001:DB8:0:3::/64", + }, + ], + }, + ], + }, + { + "vrf": "testVrf2", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + {"forward_router_address": "10.1.1.2", "track": 10}, + { + "forward_router_address": "10.1.1.3", + "distance_metric": 22, + "track": 10, + }, + ], + "dest": "192.168.1.0/24", + }, + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + { + "vrf": "testVrf", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + { + "forward_router_address": "192.0.2.1", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + { + "forward_router_address": "192.0.2.2", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + ], + state="replaced", + ), + ) + result = self.execute_module(changed=False) + commands = [] + self.assertEqual(result["commands"], commands) + + def test_ios_static_routes_overridden(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.12", + "distance_metric": 34, + "tag": 22, + "name": "nm12", + "track": 11, + }, + { + "forward_router_address": "198.51.101.1", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.20", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.3", + "name": "merged_route_3", + }, + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.11", + "distance_metric": 22, + "tag": 22, + "permanent": True, + "name": "nm1", + "multicast": True, + }, + ], + "dest": "198.51.100.1/24", + }, + ], + }, + { + "afi": "ipv6", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet4", + "permanent": True, + "name": "onlyname", + "multicast": True, + }, + { + "interface": "GigabitEthernet3", + "tag": 11, + "permanent": True, + "name": "qq", + "unicast": True, + }, + { + "interface": "GigabitEthernet2", + "tag": 2, + "permanent": True, + "name": "nm1", + }, + {"interface": "Null0"}, + { + "forward_router_address": "2001:DB8:0:3::3", + "distance_metric": 22, + "permanent": True, + "name": "pp1", + "multicast": True, + }, + { + "forward_router_address": "2001:DB8:0:3::2", + "tag": 22, + "name": "name1", + "track": 22, + }, + ], + "dest": "2001:DB8:0:4::/64", + }, + ], + }, + ], + }, + { + "vrf": "testVrf2", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + {"forward_router_address": "10.1.1.2", "track": 10}, + { + "forward_router_address": "10.1.1.3", + "distance_metric": 22, + "track": 10, + }, + ], + "dest": "192.168.1.1/24", + }, + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + { + "vrf": "testVrf", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + { + "forward_router_address": "192.0.2.1", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + { + "forward_router_address": "192.0.2.2", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + ], + state="overridden", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "ip route 198.51.100.1 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11", + "ip route 198.51.100.1 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "ip route 198.51.100.1 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150", + "ip route 198.51.100.1 255.255.255.0 198.51.101.3 name merged_route_3", + "ip route 198.51.100.1 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast", + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3", + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast", + "ipv6 route 2001:DB8:0:4::/64 GigabitEthernet4 multicast permanent name onlyname", + "ipv6 route 2001:DB8:0:4::/64 GigabitEthernet3 unicast tag 11 permanent name qq", + "ipv6 route 2001:DB8:0:4::/64 GigabitEthernet2 tag 2 permanent name nm1", + "ipv6 route 2001:DB8:0:4::/64 Null0", + "ipv6 route 2001:DB8:0:4::/64 2001:DB8:0:3::3 22 multicast permanent name pp1", + "ipv6 route 2001:DB8:0:4::/64 2001:DB8:0:3::2 tag 22 track 22 name name1", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1", + "no ipv6 route 2001:DB8:0:3::/64 Null0", + "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1", + "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1", + "ip route vrf testVrf2 192.168.1.1 255.255.255.0 10.1.1.2 track 10", + "ip route vrf testVrf2 192.168.1.1 255.255.255.0 10.1.1.3 22 track 10", + "no ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10", + "no ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10", + ] + self.assertEqual(result["commands"], commands) + + def test_ios_static_routes_overridden_idempotent(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.12", + "distance_metric": 34, + "tag": 22, + "name": "nm12", + "track": 11, + }, + { + "forward_router_address": "198.51.101.1", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.20", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.3", + "name": "merged_route_3", + }, + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.11", + "distance_metric": 22, + "tag": 22, + "permanent": True, + "name": "nm1", + "multicast": True, + }, + ], + "dest": "198.51.100.0/24", + }, + ], + }, + { + "afi": "ipv6", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet4", + "permanent": True, + "name": "onlyname", + "multicast": True, + }, + { + "interface": "GigabitEthernet3", + "tag": 11, + "permanent": True, + "name": "qq", + "unicast": True, + }, + { + "interface": "GigabitEthernet2", + "tag": 2, + "permanent": True, + "name": "nm1", + }, + {"interface": "Null0"}, + { + "forward_router_address": "2001:DB8:0:3::3", + "distance_metric": 22, + "permanent": True, + "name": "pp1", + "multicast": True, + }, + { + "forward_router_address": "2001:DB8:0:3::2", + "tag": 22, + "name": "name1", + "track": 22, + }, + ], + "dest": "2001:DB8:0:3::/64", + }, + ], + }, + ], + }, + { + "vrf": "testVrf2", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + {"forward_router_address": "10.1.1.2", "track": 10}, + { + "forward_router_address": "10.1.1.3", + "distance_metric": 22, + "track": 10, + }, + ], + "dest": "192.168.1.0/24", + }, + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + { + "vrf": "testVrf", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + { + "forward_router_address": "192.0.2.1", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + { + "forward_router_address": "192.0.2.2", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + ], + state="overridden", + ), + ) + result = self.execute_module(changed=False) + commands = [] + self.assertEqual(result["commands"], commands) + + def test_ios_delete_static_route_config_del_all(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.12", + "distance_metric": 34, + "tag": 22, + "name": "nm12", + "track": 11, + }, + { + "forward_router_address": "198.51.101.1", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.20", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.3", + "name": "merged_route_3", + }, + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.11", + "distance_metric": 22, + "tag": 22, + "permanent": True, + "name": "nm1", + "multicast": True, + }, + ], + "dest": "198.51.100.0/24", + }, + ], + }, + { + "afi": "ipv6", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet4", + "permanent": True, + "name": "onlyname", + "multicast": True, + }, + { + "interface": "GigabitEthernet3", + "tag": 11, + "permanent": True, + "name": "qq", + "unicast": True, + }, + { + "interface": "GigabitEthernet2", + "tag": 2, + "permanent": True, + "name": "nm1", + }, + {"interface": "Null0"}, + { + "forward_router_address": "2001:DB8:0:3::3", + "distance_metric": 22, + "permanent": True, + "name": "pp1", + "multicast": True, + }, + { + "forward_router_address": "2001:DB8:0:3::2", + "tag": 22, + "name": "name1", + "track": 22, + }, + ], + "dest": "2001:DB8:0:3::/64", + }, + ], + }, + ], + }, + { + "vrf": "testVrf2", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + {"forward_router_address": "10.1.1.2", "track": 10}, + { + "forward_router_address": "10.1.1.3", + "distance_metric": 22, + "track": 10, + }, + ], + "dest": "192.168.1.0/24", + }, + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + { + "vrf": "testVrf", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + { + "forward_router_address": "192.0.2.1", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + { + "forward_router_address": "192.0.2.2", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + ], + state="deleted", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3", + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1", + "no ipv6 route 2001:DB8:0:3::/64 Null0", + "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1", + "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1", + "no ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10", + "no ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10", + "no ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120", + "no ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120", + "no ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150", + "no ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150", + ] + self.assertEqual(result["commands"], commands) + + def test_ios_delete_static_route_config_empty_del_all(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[], + state="deleted", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3", + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1", + "no ipv6 route 2001:DB8:0:3::/64 Null0", + "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1", + "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1", + "no ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10", + "no ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10", + "no ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120", + "no ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120", + "no ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150", + "no ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150", + ] + self.assertEqual(result["commands"], commands) + + def test_ios_delete_static_route_config(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.12", + "distance_metric": 34, + "tag": 22, + "name": "nm12", + "track": 11, + }, + { + "forward_router_address": "198.51.101.1", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.20", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.3", + "name": "merged_route_3", + }, + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.11", + "distance_metric": 22, + "tag": 22, + "permanent": True, + "name": "nm1", + "multicast": True, + }, + ], + "dest": "198.51.100.0/24", + }, + ], + }, + ], + }, + ], + state="deleted", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3", + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast", + ] + self.assertEqual(result["commands"], commands) + + def test_ios_delete_static_route_config_second_check(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.12", + "distance_metric": 34, + "tag": 22, + "name": "nm12", + "track": 11, + }, + { + "forward_router_address": "198.51.101.1", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + ], + "dest": "198.51.100.0/24", + }, + ], + }, + { + "afi": "ipv6", + "routes": [ + { + "next_hops": [ + {"interface": "Null0"}, + ], + "dest": "2001:DB8:0:3::/64", + }, + ], + }, + ], + }, + { + "vrf": "testVrf2", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + ], + state="deleted", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "no ipv6 route 2001:DB8:0:3::/64 Null0", + "no ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120", + ] + self.assertEqual(result["commands"], commands) + + def test_ios_delete_static_route_dest_based(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "dest": "198.51.100.0/24", + }, + { + "dest": "198.51.100.2/24", + }, + ], + }, + { + "afi": "ipv6", + "routes": [ + { + "dest": "2001:DB8:0:3::/64", + }, + ], + }, + ], + }, + ], + state="deleted", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3", + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq", + "no ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1", + "no ipv6 route 2001:DB8:0:3::/64 Null0", + "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1", + "no ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1", + ] + self.assertEqual(result["commands"], commands) + + def test_ios_delete_static_route_dest_based_second_check(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route 198.51.100.1 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.1 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "dest": "198.51.100.0/24", + }, + { + "dest": "198.51.100.1/24", + }, + { + "dest": "198.51.100.2/24", + }, + ], + }, + ], + }, + ], + state="deleted", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3", + "no ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast", + "no ip route 198.51.100.1 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "no ip route 198.51.100.1 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150", + ] + self.assertEqual(result["commands"], commands) + + def test_ios_delete_static_route_vrf_based(self): + self.execute_show_command.return_value = dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route 198.51.100.1 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.1 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + ipv6 route vrf testVrfv6 2001:DB8:0:4::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route vrf testVrfv6 2001:DB8:0:4::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ) + set_module_args( + dict( + config=[ + dict( + vrf="testVrf2", + address_families=[dict(afi="ipv4", routes=[dict(dest="192.0.2.0/24")])], + ), + dict( + vrf="testVrfv6", + address_families=[ + dict(afi="ipv6", routes=[dict(dest="2001:DB8:0:4::/64")]), + ], + ), + ], + state="deleted", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "no ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120", + "no ipv6 route vrf testVrfv6 2001:DB8:0:4::/64 2001:DB8:0:3::3 22 multicast permanent name pp1", + "no ipv6 route vrf testVrfv6 2001:DB8:0:4::/64 2001:DB8:0:3::2 tag 22 track 22 name name1", + ] + self.assertEqual(result["commands"], commands) + + def test_ios_static_route_rendered(self): + set_module_args( + dict( + config=[ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.12", + "distance_metric": 34, + "tag": 22, + "name": "nm12", + "track": 11, + }, + { + "forward_router_address": "198.51.101.1", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.20", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.3", + "name": "merged_route_3", + }, + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.11", + "distance_metric": 22, + "tag": 22, + "permanent": True, + "name": "nm1", + "multicast": True, + }, + ], + "dest": "198.51.100.0/24", + }, + ], + }, + { + "afi": "ipv6", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet4", + "permanent": True, + "name": "onlyname", + "multicast": True, + }, + { + "interface": "GigabitEthernet3", + "tag": 11, + "permanent": True, + "name": "qq", + "unicast": True, + }, + { + "interface": "GigabitEthernet2", + "tag": 2, + "permanent": True, + "name": "nm1", + }, + {"interface": "Null0"}, + { + "forward_router_address": "2001:DB8:0:3::3", + "distance_metric": 22, + "permanent": True, + "name": "pp1", + "multicast": True, + }, + { + "forward_router_address": "2001:DB8:0:3::2", + "tag": 22, + "name": "name1", + "track": 22, + }, + ], + "dest": "2001:DB8:0:3::/64", + }, + ], + }, + ], + }, + { + "vrf": "testVrf2", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + {"forward_router_address": "10.1.1.2", "track": 10}, + { + "forward_router_address": "10.1.1.3", + "distance_metric": 22, + "track": 10, + }, + ], + "dest": "192.168.1.0/24", + }, + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + { + "vrf": "testVrf", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + { + "forward_router_address": "192.0.2.1", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + { + "forward_router_address": "192.0.2.2", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + ], + state="rendered", + ), + ) + commands = [ + "ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11", + "ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150", + "ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150", + "ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3", + "ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast", + "ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname", + "ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq", + "ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1", + "ipv6 route 2001:DB8:0:3::/64 Null0", + "ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1", + "ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1", + "ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10", + "ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10", + "ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120", + "ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120", + "ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150", + "ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150", + ] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), sorted(commands)) + + def test_ios_static_routes_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.2 track 10 + ip route vrf testVrf2 192.168.1.0 255.255.255.0 10.1.1.3 22 track 10 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.12 34 tag 22 name nm12 track 11 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route vrf testVrf2 192.0.2.0 255.255.255.0 192.0.2.3 tag 30 name test_vrf2 track 120 + ip route 198.51.100.0 255.255.255.0 198.51.101.1 175 tag 70 name replaced_route track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.20 175 tag 70 name replaced_route track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.1 tag 50 name test_vrf track 150 + ip route vrf testVrf 192.0.2.0 255.255.255.0 192.0.2.2 tag 50 name test_vrf track 150 + ip route 198.51.100.0 255.255.255.0 198.51.101.3 name merged_route_3 + ip route 198.51.100.0 255.255.255.0 GigabitEthernet2 198.51.101.11 22 tag 22 permanent name nm1 multicast + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet4 multicast permanent name onlyname + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet3 unicast tag 11 permanent name qq + ipv6 route 2001:DB8:0:3::/64 GigabitEthernet2 tag 2 permanent name nm1 + ipv6 route 2001:DB8:0:3::/64 Null0 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::3 22 multicast permanent name pp1 + ipv6 route 2001:DB8:0:3::/64 2001:DB8:0:3::2 tag 22 track 22 name name1 + """, + ), + state="parsed", + ), + ) + parsed = [ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.12", + "distance_metric": 34, + "tag": 22, + "name": "nm12", + "track": 11, + }, + { + "forward_router_address": "198.51.101.1", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.20", + "distance_metric": 175, + "tag": 70, + "name": "replaced_route", + "track": 150, + }, + { + "forward_router_address": "198.51.101.3", + "name": "merged_route_3", + }, + { + "interface": "GigabitEthernet2", + "forward_router_address": "198.51.101.11", + "distance_metric": 22, + "tag": 22, + "permanent": True, + "name": "nm1", + "multicast": True, + }, + ], + "dest": "198.51.100.0/24", + }, + ], + }, + { + "afi": "ipv6", + "routes": [ + { + "next_hops": [ + { + "interface": "GigabitEthernet4", + "permanent": True, + "name": "onlyname", + "multicast": True, + }, + { + "interface": "GigabitEthernet3", + "tag": 11, + "permanent": True, + "name": "qq", + "unicast": True, + }, + { + "interface": "GigabitEthernet2", + "tag": 2, + "permanent": True, + "name": "nm1", + }, + {"interface": "Null0"}, + { + "forward_router_address": "2001:DB8:0:3::3", + "distance_metric": 22, + "permanent": True, + "name": "pp1", + "multicast": True, + }, + { + "forward_router_address": "2001:DB8:0:3::2", + "tag": 22, + "name": "name1", + "track": 22, + }, + ], + "dest": "2001:DB8:0:3::/64", + }, + ], + }, + ], + }, + { + "vrf": "testVrf2", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + {"forward_router_address": "10.1.1.2", "track": 10}, + { + "forward_router_address": "10.1.1.3", + "distance_metric": 22, + "track": 10, + }, + ], + "dest": "192.168.1.0/24", + }, + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + { + "vrf": "testVrf", + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "next_hops": [ + { + "forward_router_address": "192.0.2.3", + "tag": 30, + "name": "test_vrf2", + "track": 120, + }, + { + "forward_router_address": "192.0.2.1", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + { + "forward_router_address": "192.0.2.2", + "tag": 50, + "name": "test_vrf", + "track": 150, + }, + ], + "dest": "192.0.2.0/24", + }, + ], + }, + ], + }, + ] + result = self.execute_module(changed=False) + self.assertEqual(result["parsed"], parsed) + + def test_ios_static_route_gathered(self): + self.execute_show_command.return_value = dedent( + """\ + ip route 10.0.0.0 255.0.0.0 Null0 permanent + """, + ) + set_module_args(dict(state="gathered")) + gathered = [ + { + "address_families": [ + { + "afi": "ipv4", + "routes": [ + { + "dest": "10.0.0.0/8", + "next_hops": [{"interface": "Null0", "permanent": True}], + }, + ], + }, + ], + }, + ] + result = self.execute_module(changed=False) + self.maxDiff = None + print(result["gathered"]) + self.assertEqual(sorted(result["gathered"]), sorted(gathered)) + # self.assertEqual(result["gathered"], gathered) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_system.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_system.py new file mode 100644 index 000000000..b82d7ae48 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_system.py @@ -0,0 +1,144 @@ +# +# (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 <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.cisco.ios.plugins.modules import ios_system +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosSystemModule(TestIosModule): + module = ios_system + + def setUp(self): + super(TestIosSystemModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_system.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_system.load_config", + ) + self.load_config = self.mock_load_config.start() + + def tearDown(self): + super(TestIosSystemModule, self).tearDown() + self.mock_get_config.stop() + self.mock_load_config.stop() + + def load_fixtures(self, commands=None): + self.get_config.return_value = load_fixture("ios_system_config.cfg") + self.load_config.return_value = None + + def test_ios_system_hostname_changed(self): + set_module_args(dict(hostname="foo")) + commands = ["hostname foo"] + self.execute_module(changed=True, commands=commands) + + def test_ios_system_domain_name(self): + set_module_args(dict(domain_name=["test.com"])) + commands = [ + "ip domain name test.com", + "no ip domain name eng.example.net", + "no ip domain name vrf management eng.example.net", + ] + self.execute_module(changed=True, commands=commands) + + def test_ios_system_domain_name_complex(self): + set_module_args( + dict(domain_name=[{"name": "test.com", "vrf": "test"}, {"name": "eng.example.net"}]), + ) + commands = [ + "ip domain name vrf test test.com", + "no ip domain name vrf management eng.example.net", + ] + self.execute_module(changed=True, commands=commands) + + def test_ios_system_domain_search(self): + set_module_args(dict(domain_search=["ansible.com", "redhat.com"])) + commands = [ + "no ip domain list vrf management example.net", + "no ip domain list example.net", + "no ip domain list example.com", + "ip domain list ansible.com", + "ip domain list redhat.com", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_system_domain_search_complex(self): + set_module_args(dict(domain_search=[{"name": "ansible.com", "vrf": "test"}])) + commands = [ + "no ip domain list vrf management example.net", + "no ip domain list example.net", + "no ip domain list example.com", + "ip domain list vrf test ansible.com", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_system_lookup_source(self): + set_module_args(dict(lookup_source="Ethernet1")) + commands = ["ip domain lookup source-interface Ethernet1"] + self.execute_module(changed=True, commands=commands) + + def test_ios_system_name_servers(self): + name_servers = ["8.8.8.8", "8.8.4.4"] + set_module_args(dict(name_servers=name_servers)) + commands = ["no ip name-server vrf management 8.8.8.8", "ip name-server 8.8.4.4"] + self.execute_module(changed=True, commands=commands, sort=False) + + def rest_ios_system_name_servers_complex(self): + name_servers = dict(server="8.8.8.8", vrf="test") + set_module_args(dict(name_servers=name_servers)) + commands = [ + "no name-server 8.8.8.8", + "no name-server vrf management 8.8.8.8", + "ip name-server vrf test 8.8.8.8", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_system_state_absent(self): + set_module_args(dict(state="absent")) + commands = [ + "no hostname", + "no ip domain lookup source-interface GigabitEthernet0/0", + "no ip domain list vrf management", + "no ip domain list", + "no ip domain name vrf management", + "no ip domain name", + "no ip name-server vrf management", + "no ip name-server", + ] + self.execute_module(changed=True, commands=commands) + + def test_ios_system_no_change(self): + set_module_args(dict(hostname="ios01")) + self.execute_module(commands=[]) + + def test_ios_system_missing_vrf(self): + name_servers = dict(server="8.8.8.8", vrf="missing") + set_module_args(dict(name_servers=name_servers)) + self.execute_module(failed=True) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_user.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_user.py new file mode 100644 index 000000000..28d5126c2 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_user.py @@ -0,0 +1,153 @@ +# (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 <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.cisco.ios.plugins.modules import ios_user +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosUserModule(TestIosModule): + module = ios_user + + def setUp(self): + super(TestIosUserModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_user.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_user.load_config", + ) + self.load_config = self.mock_load_config.start() + + def tearDown(self): + super(TestIosUserModule, self).tearDown() + self.mock_get_config.stop() + self.mock_load_config.stop() + + def load_fixtures(self, commands=None, transport="cli"): + self.get_config.return_value = load_fixture("ios_user_config.cfg") + self.load_config.return_value = dict(diff=None, session="session") + + def test_ios_user_create(self): + set_module_args(dict(name="test", nopassword=True)) + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], ["username test nopassword"]) + + def test_ios_user_delete(self): + set_module_args(dict(name="ansible", state="absent")) + result = self.execute_module(changed=True) + cmds = [ + { + "command": "no username ansible", + "answer": "y", + "newline": False, + "prompt": "This operation will remove all username related configurations with same name", + }, + ] + + result_cmd = [] + for i in result["commands"]: + result_cmd.append(i) + + self.assertEqual(result_cmd, cmds) + + def test_ios_user_password(self): + set_module_args(dict(name="ansible", configured_password="test")) + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], ["username ansible secret test"]) + + def test_ios_user_privilege(self): + set_module_args(dict(name="ansible", privilege=15)) + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], ["username ansible privilege 15"]) + + def test_ios_user_privilege_invalid(self): + set_module_args(dict(name="ansible", privilege=25)) + self.execute_module(failed=True) + + def test_ios_user_purge(self): + set_module_args(dict(purge=True)) + result = self.execute_module(changed=True) + cmd = { + "command": "no username ansible", + "answer": "y", + "newline": False, + "prompt": "This operation will remove all username related configurations with same name", + } + + result_cmd = [] + for i in result["commands"]: + result_cmd.append(i) + + self.assertEqual(result_cmd, [cmd]) + + def test_ios_user_view(self): + set_module_args(dict(name="ansible", view="test")) + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], ["username ansible view test"]) + + def test_ios_user_update_password_changed(self): + set_module_args(dict(name="test", configured_password="test", update_password="on_create")) + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], ["username test secret test"]) + + def test_ios_user_update_password_on_create_ok(self): + set_module_args( + dict(name="ansible", configured_password="test", update_password="on_create"), + ) + self.execute_module() + + def test_ios_user_update_password_always(self): + set_module_args(dict(name="ansible", configured_password="test", update_password="always")) + result = self.execute_module(changed=True) + self.assertEqual(result["commands"], ["username ansible secret test"]) + + def test_ios_user_set_sshkey(self): + set_module_args(dict(name="ansible", sshkey="dGVzdA==")) + commands = [ + "ip ssh pubkey-chain", + "username ansible", + "key-hash ssh-rsa 098F6BCD4621D373CADE4E832627B4F6", + "exit", + "exit", + ] + result = self.execute_module(changed=True, commands=commands) + self.assertEqual(result["commands"], commands) + + def test_ios_user_set_sshkey_multiple(self): + set_module_args(dict(name="ansible", sshkey=["dGVzdA==", "eHWacB2=="])) + commands = [ + "ip ssh pubkey-chain", + "username ansible", + "key-hash ssh-rsa 098F6BCD4621D373CADE4E832627B4F6", + "key-hash ssh-rsa A019918340A1E9183388D9A675603036", + "exit", + "exit", + ] + result = self.execute_module(changed=True, commands=commands) + self.assertEqual(result["commands"], commands) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_vlans.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_vlans.py new file mode 100644 index 000000000..201e814c5 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_vlans.py @@ -0,0 +1,650 @@ +# +# (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 textwrap import dedent + +from ansible_collections.cisco.ios.plugins.modules import ios_vlans +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosVlansModule(TestIosModule): + module = ios_vlans + + def setUp(self): + super(TestIosVlansModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base." + "get_resource_connection", + ) + self.get_resource_connection_config = self.mock_get_resource_connection_config.start() + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts." + "get_resource_connection", + ) + self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start() + + self.mock_edit_config = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.providers.providers.CliProvider.edit_config", + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + "ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.vlans.vlans." + "VlansFacts.get_vlans_data", + ) + self.execute_show_command = self.mock_execute_show_command.start() + self.mock_l2_device_command = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_vlans._is_l2_device", + ) + self._l2_device_command = self.mock_l2_device_command.start() + + def tearDown(self): + super(TestIosVlansModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_edit_config.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + self.mock_l2_device_command.stop() + + def load_fixtures(self, commands=None, transport="cli"): + def load_from_file(*args, **kwargs): + return load_fixture("ios_vlans_config.cfg") + + self.mock_l2_device_command.side_effect = True + self.execute_show_command.side_effect = load_from_file + + def test_ios_vlans_merged(self): + set_module_args( + dict( + config=[ + dict( + name="test_vlan_200", + state="active", + shutdown="disabled", + remote_span=True, + vlan_id=200, + ), + ], + state="merged", + ), + ) + result = self.execute_module(changed=True) + commands = ["vlan 200", "name test_vlan_200", "state active", "remote-span", "no shutdown"] + self.assertEqual(result["commands"], commands) + + def test_ios_vlans_merged_idempotent(self): + set_module_args( + dict( + config=[ + dict(mtu=1500, name="default", shutdown="disabled", state="active", vlan_id=1), + dict( + mtu=610, + name="RemoteIsInMyName", + shutdown="enabled", + state="active", + vlan_id=123, + ), + dict( + mtu=1500, + name="VLAN0150", + remote_span=True, + shutdown="disabled", + state="active", + vlan_id=150, + ), + dict( + mtu=1500, + name="a_very_long_vlan_name_a_very_long_vlan_name", + shutdown="disabled", + state="active", + vlan_id=888, + ), + dict( + mtu=1500, + name="fddi-default", + shutdown="enabled", + state="active", + vlan_id=1002, + ), + dict( + mtu=4472, + name="trcrf-default", + shutdown="enabled", + state="active", + vlan_id=1003, + ), + dict( + mtu=1500, + name="fddinet-default", + shutdown="enabled", + state="active", + vlan_id=1004, + ), + dict( + mtu=4472, + name="trbrf-default", + shutdown="enabled", + state="active", + vlan_id=1005, + ), + ], + state="merged", + ), + ) + self.execute_module(changed=False, commands=[], sort=True) + + def test_ios_vlans_replaced(self): + set_module_args( + dict( + config=[ + dict( + name="test_vlan_200", + state="active", + shutdown="disabled", + remote_span=True, + vlan_id=200, + ), + dict(name="Replace_RemoteIsInMyName", remote_span=True, vlan_id=123), + dict( + name="pvlan-primary", + private_vlan=dict(type="primary", associated=[11, 12]), + vlan_id=10, + ), + dict(name="pvlan-community", private_vlan=dict(type="community"), vlan_id=11), + dict(name="pvlan-isolated", private_vlan=dict(type="isolated"), vlan_id=12), + ], + state="replaced", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "vlan 200", + "name test_vlan_200", + "state active", + "remote-span", + "no shutdown", + "vlan 123", + "no state active", + "no shutdown", + "no mtu 610", + "name Replace_RemoteIsInMyName", + "remote-span", + "vlan 10", + "no state active", + "no shutdown", + "no mtu 4472", + "name pvlan-primary", + "private-vlan primary", + "private-vlan association 11,12", + "vlan 11", + "no state active", + "no shutdown", + "no mtu 4472", + "name pvlan-community", + "private-vlan community", + "vlan 12", + "no state active", + "no shutdown", + "no mtu 4472", + "name pvlan-isolated", + "private-vlan isolated", + ] + self.assertEqual(result["commands"], commands) + + def test_ios_vlans_replaced_idempotent(self): + set_module_args( + dict( + config=[ + dict(mtu=1500, name="default", shutdown="disabled", state="active", vlan_id=1), + dict( + mtu=610, + name="RemoteIsInMyName", + shutdown="enabled", + state="active", + vlan_id=123, + ), + dict( + mtu=1500, + name="VLAN0150", + remote_span=True, + shutdown="disabled", + state="active", + vlan_id=150, + ), + dict( + mtu=1500, + name="a_very_long_vlan_name_a_very_long_vlan_name", + shutdown="disabled", + state="active", + vlan_id=888, + ), + dict( + mtu=1500, + name="fddi-default", + shutdown="enabled", + state="active", + vlan_id=1002, + ), + dict( + mtu=4472, + name="trcrf-default", + shutdown="enabled", + state="active", + vlan_id=1003, + ), + dict( + mtu=1500, + name="fddinet-default", + shutdown="enabled", + state="active", + vlan_id=1004, + ), + dict( + mtu=4472, + name="trbrf-default", + shutdown="enabled", + state="active", + vlan_id=1005, + ), + ], + state="replaced", + ), + ) + self.execute_module(changed=False, commands=[], sort=True) + + def test_ios_vlans_overridden(self): + set_module_args( + dict( + config=[ + dict( + name="test_vlan_200", + state="active", + shutdown="disabled", + remote_span=True, + vlan_id=200, + ), + dict(name="Override_RemoteIsInMyName", remote_span=True, vlan_id=123), + ], + state="overridden", + ), + ) + result = self.execute_module(changed=True) + commands = [ + "vlan 123", + "no state active", + "no shutdown", + "no mtu 610", + "name Override_RemoteIsInMyName", + "remote-span", + "no vlan 150", + "no vlan 888", + "vlan 200", + "name test_vlan_200", + "state active", + "remote-span", + "no shutdown", + ] + + self.assertEqual(result["commands"], commands) + + def test_ios_vlans_overridden_idempotent(self): + set_module_args( + dict( + config=[ + dict(mtu=1500, name="default", shutdown="disabled", state="active", vlan_id=1), + dict( + mtu=610, + name="RemoteIsInMyName", + shutdown="enabled", + state="active", + vlan_id=123, + ), + dict( + mtu=1500, + name="VLAN0150", + remote_span=True, + shutdown="disabled", + state="active", + vlan_id=150, + ), + dict( + mtu=1500, + name="a_very_long_vlan_name_a_very_long_vlan_name", + shutdown="disabled", + state="active", + vlan_id=888, + ), + dict( + mtu=1500, + name="fddi-default", + shutdown="enabled", + state="active", + vlan_id=1002, + ), + dict( + mtu=4472, + name="trcrf-default", + shutdown="enabled", + state="active", + vlan_id=1003, + ), + dict( + mtu=1500, + name="fddinet-default", + shutdown="enabled", + state="active", + vlan_id=1004, + ), + dict( + mtu=4472, + name="trbrf-default", + shutdown="enabled", + state="active", + vlan_id=1005, + ), + ], + state="overridden", + ), + ) + self.execute_module(changed=False, commands=[], sort=True) + + def test_ios_delete_vlans_config(self): + set_module_args(dict(config=[dict(vlan_id=150)], state="deleted")) + result = self.execute_module(changed=True) + commands = ["no vlan 150"] + self.assertEqual(result["commands"], commands) + + def test_vlans_rendered(self): + set_module_args( + dict( + config=[ + dict( + name="test_vlan_200", + state="active", + shutdown="disabled", + remote_span=True, + vlan_id=200, + ), + ], + state="rendered", + ), + ) + commands = ["name test_vlan_200", "no shutdown", "remote-span", "state active", "vlan 200"] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["rendered"]), commands) + + def test_vlan_parsed(self): + set_module_args( + dict( + running_config=dedent( + """\ + VLAN Name Status Ports + ---- -------------------------------- --------- ------------------------------- + 1 default with space active Fa0/40, Fa0/41, Fa0/42, Fa0/43, Fa0/44, Fa0/45 + 2 dummy_NETWORK active + 3 dummy_RACK_INFRA active Fa0/46, Fa0/47, Fa0/48 + 10 pvlan-primary active + 11 pvlan-isolated active + 12 pvlan-community active + 20 pvlan-2p active + 21 pvlan-2i active + 22 pvlan-2c active + 1002 dummy-default act/unsup + 1003 dummy-ring-default act/unsup + 1004 dummy-default act/unsup + 1005 dummy-t-default act/unsup + 1101 dummy_1101 active + 1102 dummy_1102 active + 1107 dummy_1107 active + + VLAN Type SAID MTU Parent RingNo BridgeNo Stp BrdgMode Trans1 Trans2 + ---- ----- ---------- ----- ------ ------ -------- ---- -------- ------ ------ + 1 enet 100001 1500 - - - - - 0 0 + 2 enet 100002 1500 - - - - - 0 0 + 3 enet 100003 1500 - - - - - 0 0 + 10 enet 100010 1500 - - - - - 0 0 + 11 enet 100011 1500 - - - - - 0 0 + 12 enet 100012 1500 - - - - - 0 0 + 20 enet 100020 1500 - - - - - 0 0 + 21 enet 100021 1500 - - - - - 0 0 + 22 enet 100022 1500 - - - - - 0 0 + 1002 fddi 101002 1500 - - - - - 0 0 + 1003 tr 101003 1500 - - - - - 0 0 + 1004 fdnet 101004 1500 - - - ieee - 0 0 + 1005 trnet 101005 1500 - - - ibm - 0 0 + 1101 enet 101101 1500 - - - - - 0 0 + 1102 enet 101102 1500 - - - - - 0 0 + 1107 enet 101107 1500 - - - - - 0 0 + + Remote SPAN VLANs + ------------------------------------------------------------------------------ + 1101-1105,1107 + + Primary Secondary Type Ports + ------- --------- ----------------- ------------------------------------------ + 10 none primary + none 11 isolated + none 12 community + 20 21 isolated + 20 22 community + """, + ), + state="parsed", + ), + ) + parsed = [ + { + "name": "default with space", + "vlan_id": 1, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + }, + { + "name": "dummy_NETWORK", + "vlan_id": 2, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + }, + { + "name": "dummy_RACK_INFRA", + "vlan_id": 3, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + }, + { + "name": "pvlan-primary", + "vlan_id": 10, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + "private_vlan": {"type": "primary"}, + }, + { + "name": "pvlan-isolated", + "vlan_id": 11, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + "private_vlan": {"type": "isolated"}, + }, + { + "name": "pvlan-community", + "vlan_id": 12, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + "private_vlan": {"type": "community"}, + }, + { + "name": "pvlan-2p", + "vlan_id": 20, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + "private_vlan": {"type": "primary", "associated": [21, 22]}, + }, + { + "name": "pvlan-2i", + "vlan_id": 21, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + "private_vlan": {"type": "isolated"}, + }, + { + "name": "pvlan-2c", + "vlan_id": 22, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + "private_vlan": {"type": "community"}, + }, + { + "name": "dummy-default", + "vlan_id": 1002, + "state": "active", + "shutdown": "enabled", + "mtu": 1500, + }, + { + "name": "dummy-ring-default", + "vlan_id": 1003, + "state": "active", + "shutdown": "enabled", + "mtu": 1500, + }, + { + "name": "dummy-default", + "vlan_id": 1004, + "state": "active", + "shutdown": "enabled", + "mtu": 1500, + }, + { + "name": "dummy-t-default", + "vlan_id": 1005, + "state": "active", + "shutdown": "enabled", + "mtu": 1500, + }, + { + "name": "dummy_1101", + "vlan_id": 1101, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + "remote_span": True, + }, + { + "name": "dummy_1102", + "vlan_id": 1102, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + "remote_span": True, + }, + { + "name": "dummy_1107", + "vlan_id": 1107, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + "remote_span": True, + }, + ] + result = self.execute_module(changed=False) + self.maxDiff = None + self.assertEqual(result["parsed"], parsed) + + def test_ios_vlans_gathered(self): + set_module_args(dict(state="gathered")) + gathered = [ + { + "name": "default", + "vlan_id": 1, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + }, + { + "name": "RemoteIsInMyName", + "vlan_id": 123, + "state": "active", + "shutdown": "enabled", + "mtu": 610, + }, + { + "name": "VLAN0150", + "vlan_id": 150, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + "remote_span": True, + }, + { + "name": "a_very_long_vlan_name_a_very_long_vlan_name", + "vlan_id": 888, + "state": "active", + "shutdown": "disabled", + "mtu": 1500, + }, + { + "name": "fddi-default", + "vlan_id": 1002, + "state": "active", + "shutdown": "enabled", + "mtu": 1500, + }, + { + "name": "trcrf-default", + "vlan_id": 1003, + "state": "active", + "shutdown": "enabled", + "mtu": 4472, + }, + { + "name": "fddinet-default", + "vlan_id": 1004, + "state": "active", + "shutdown": "enabled", + "mtu": 1500, + }, + { + "name": "trbrf-default", + "vlan_id": 1005, + "state": "active", + "shutdown": "enabled", + "mtu": 4472, + }, + ] + result = self.execute_module(changed=False) + + self.maxDiff = None + self.assertEqual(result["gathered"], gathered) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_vrf.py b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_vrf.py new file mode 100644 index 000000000..d4043689e --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_vrf.py @@ -0,0 +1,390 @@ +# +# (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 <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.cisco.ios.plugins.modules import ios_vrf +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch +from ansible_collections.cisco.ios.tests.unit.modules.utils import set_module_args + +from .ios_module import TestIosModule, load_fixture + + +class TestIosVrfModule(TestIosModule): + module = ios_vrf + + def setUp(self): + super(TestIosVrfModule, self).setUp() + + self.mock_get_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_vrf.get_config", + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_vrf.load_config", + ) + self.load_config = self.mock_load_config.start() + + self.mock_exec_command = patch( + "ansible_collections.cisco.ios.plugins.modules.ios_vrf.exec_command", + ) + self.exec_command = self.mock_exec_command.start() + + def tearDown(self): + super(TestIosVrfModule, self).tearDown() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_exec_command.stop() + + def load_fixtures(self, commands=None): + self.get_config.return_value = load_fixture("ios_vrf_config.cfg") + self.exec_command.return_value = (0, load_fixture("ios_vrf_config.cfg").strip(), None) + self.load_config.return_value = None + + def test_ios_vrf_name(self): + set_module_args(dict(name="test_4")) + commands = ["vrf definition test_4"] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_name_unchanged(self): + set_module_args(dict(name="test_1", rd="1:100", description="test vrf 1")) + self.execute_module() + + def test_ios_vrf_description(self): + set_module_args(dict(name="test_1", description="test string")) + commands = ["vrf definition test_1", "description test string"] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_rd(self): + set_module_args(dict(name="test_1", rd="2:100")) + commands = ["vrf definition test_1", "rd 2:100"] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_interfaces(self): + set_module_args(dict(name="test_1", interfaces=["Ethernet1"])) + commands = [ + "interface Ethernet2", + "no vrf forwarding test_1", + "interface Ethernet1", + "vrf forwarding test_1", + "ip address 1.2.3.4/5", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_state_absent(self): + set_module_args(dict(name="test_1", state="absent")) + commands = ["no vrf definition test_1"] + self.execute_module(changed=True, commands=commands) + + def test_ios_vrf_purge_all(self): + set_module_args(dict(purge=True)) + commands = [ + "no vrf definition test_1", + "no vrf definition test_2", + "no vrf definition test_3", + "no vrf definition test_17", + "no vrf definition test_18", + "no vrf definition test_19", + ] + self.execute_module(changed=True, commands=commands) + + def test_ios_vrf_purge_all_but_one(self): + set_module_args(dict(name="test_1", purge=True)) + commands = [ + "no vrf definition test_2", + "no vrf definition test_3", + "no vrf definition test_17", + "no vrf definition test_18", + "no vrf definition test_19", + ] + self.execute_module(changed=True, commands=commands) + + def test_ios_vrfs_no_purge(self): + vrfs = [{"name": "test_1"}, {"name": "test_4"}] + set_module_args(dict(vrfs=vrfs)) + commands = ["vrf definition test_4"] + self.execute_module(changed=True, commands=commands) + + def test_ios_vrfs_purge(self): + vrfs = [{"name": "test_1"}, {"name": "test_4"}] + set_module_args(dict(vrfs=vrfs, purge=True)) + commands = [ + "vrf definition test_4", + "no vrf definition test_2", + "no vrf definition test_3", + "no vrf definition test_17", + "no vrf definition test_18", + "no vrf definition test_19", + ] + self.execute_module(changed=True, commands=commands) + + def test_ios_vrfs_global_arg(self): + vrfs = [{"name": "test_1"}, {"name": "test_2"}] + set_module_args(dict(vrfs=vrfs, description="test string")) + commands = [ + "vrf definition test_1", + "description test string", + "vrf definition test_2", + "description test string", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrfs_local_override_description(self): + vrfs = [{"name": "test_1", "description": "test vrf 1"}, {"name": "test_2"}] + set_module_args(dict(vrfs=vrfs, description="test string")) + commands = ["vrf definition test_2", "description test string"] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrfs_local_override_state(self): + vrfs = [{"name": "test_1", "state": "absent"}, {"name": "test_2"}] + set_module_args(dict(vrfs=vrfs, description="test string")) + commands = ["no vrf definition test_1", "vrf definition test_2", "description test string"] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_route_both(self): + set_module_args(dict(name="test_5", rd="2:100", route_both=["2:100", "3:100"])) + commands = [ + "vrf definition test_5", + "address-family ipv4", + "exit", + "address-family ipv6", + "exit", + "rd 2:100", + "route-target import 2:100", + "route-target import 3:100", + "route-target export 2:100", + "route-target export 3:100", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_route_import(self): + set_module_args(dict(name="test_6", rd="3:100", route_import=["3:100", "4:100"])) + commands = [ + "vrf definition test_6", + "rd 3:100", + "route-target import 3:100", + "route-target import 4:100", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_route_export(self): + set_module_args(dict(name="test_7", rd="4:100", route_export=["3:100", "4:100"])) + commands = [ + "vrf definition test_7", + "rd 4:100", + "route-target export 3:100", + "route-target export 4:100", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_route_both_mixed(self): + set_module_args( + dict( + name="test_8", + rd="5:100", + route_both=["3:100", "4:100"], + route_export=["3:100", "4:100"], + ), + ) + self.execute_module(changed=True) + + def test_ios_vrf_route_both_ipv4(self): + set_module_args( + dict(name="test_9", rd="168.0.0.9:100", route_both_ipv4=["168.0.0.9:100", "3:100"]), + ) + commands = [ + "vrf definition test_9", + "address-family ipv4", + "exit", + "rd 168.0.0.9:100", + "address-family ipv4", + "route-target import 168.0.0.9:100", + "route-target import 3:100", + "exit-address-family", + "address-family ipv4", + "route-target export 168.0.0.9:100", + "route-target export 3:100", + "exit-address-family", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_route_import_ipv4(self): + set_module_args( + dict( + name="test_10", + rd="168.0.0.10:100", + route_import_ipv4=["168.0.0.10:100", "3:100"], + ), + ) + commands = [ + "vrf definition test_10", + "address-family ipv4", + "exit", + "rd 168.0.0.10:100", + "address-family ipv4", + "route-target import 168.0.0.10:100", + "route-target import 3:100", + "exit-address-family", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_route_export_ipv4(self): + set_module_args( + dict( + name="test_11", + rd="168.0.0.11:100", + route_export_ipv4=["168.0.0.11:100", "3:100"], + ), + ) + commands = [ + "vrf definition test_11", + "address-family ipv4", + "exit", + "rd 168.0.0.11:100", + "address-family ipv4", + "route-target export 168.0.0.11:100", + "route-target export 3:100", + "exit-address-family", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_route_both_ipv4_mixed(self): + set_module_args( + dict( + name="test_12", + rd="168.0.0.12:100", + route_both_ipv4=["168.0.0.12:100", "3:100"], + route_export_ipv4=["168.0.0.15:100", "6:100"], + ), + ) + self.execute_module(changed=True) + + def test_ios_vrf_route_both_ipv6(self): + set_module_args( + dict(name="test_13", rd="2:100", route_both_ipv6=["2:100", "168.0.0.13:100"]), + ) + commands = [ + "vrf definition test_13", + "address-family ipv6", + "exit", + "rd 2:100", + "address-family ipv6", + "route-target import 2:100", + "route-target import 168.0.0.13:100", + "exit-address-family", + "address-family ipv6", + "route-target export 2:100", + "route-target export 168.0.0.13:100", + "exit-address-family", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_route_import_ipv6(self): + set_module_args( + dict(name="test_14", rd="3:100", route_import_ipv6=["3:100", "168.0.0.14:100"]), + ) + commands = [ + "vrf definition test_14", + "address-family ipv6", + "exit", + "rd 3:100", + "address-family ipv6", + "route-target import 3:100", + "route-target import 168.0.0.14:100", + "exit-address-family", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_route_export_ipv6(self): + set_module_args( + dict(name="test_15", rd="4:100", route_export_ipv6=["168.0.0.15:100", "4:100"]), + ) + commands = [ + "vrf definition test_15", + "address-family ipv6", + "exit", + "rd 4:100", + "address-family ipv6", + "route-target export 168.0.0.15:100", + "route-target export 4:100", + "exit-address-family", + ] + self.execute_module(changed=True, commands=commands, sort=False) + + def test_ios_vrf_route_both_ipv6_mixed(self): + set_module_args( + dict( + name="test_16", + rd="5:100", + route_both_ipv6=["168.0.0.9:100", "4:100"], + route_export_ipv6=["168.0.0.12:100", "6:100"], + ), + ) + self.execute_module(changed=True) + + def test_ios_vrf_route_both_ipv6_mixed_idempotent(self): + set_module_args( + dict( + name="test_17", + rd="2:100", + route_import_ipv6=["168.0.0.14:100"], + route_both_ipv6=["2:100", "168.0.0.13:100"], + route_export_ipv6=["168.0.0.15:100", "4:100"], + ), + ) + self.execute_module(changed=False, commands=[], sort=False) + + def test_ios_vrf_route_both_ipv4_mixed_idempotent(self): + set_module_args( + dict( + name="test_18", + rd="168.0.0.9:100", + route_import_ipv4=["168.0.0.10:600"], + route_export_ipv4=["168.0.0.10:100"], + route_both_ipv4=["168.0.0.9:100", "3:100"], + ), + ) + self.execute_module(changed=False, commands=[], sort=False) + + def test_ios_vrf_all_route_both_idempotent(self): + set_module_args( + dict( + name="test_19", + rd="10:700", + route_both=["2:100", "2:101"], + route_export=["2:102", "2:103"], + route_import=["2:104", "2:105"], + route_both_ipv4=["2:100", "2:101"], + route_export_ipv4=["2:102", "2:103"], + route_import_ipv4=["2:104", "2:105"], + route_both_ipv6=["2:100", "2:101"], + route_export_ipv6=["2:102", "2:103"], + route_import_ipv6=["2:104", "2:105"], + ), + ) + self.execute_module(changed=False, commands=[], sort=False) + + def test_ios_vrf_interface_brownfield(self): + set_module_args(dict(name="test_19", interfaces=["Ethernet1"])) + commands = ["interface Ethernet1", "vrf forwarding test_19", "ip address 1.2.3.4/5"] + self.execute_module(changed=True, commands=commands, sort=False) diff --git a/ansible_collections/cisco/ios/tests/unit/modules/utils.py b/ansible_collections/cisco/ios/tests/unit/modules/utils.py new file mode 100644 index 000000000..6184c48c3 --- /dev/null +++ b/ansible_collections/cisco/ios/tests/unit/modules/utils.py @@ -0,0 +1,55 @@ +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type +import json + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + +from ansible_collections.cisco.ios.tests.unit.compat import unittest +from ansible_collections.cisco.ios.tests.unit.compat.mock import patch + + +def set_module_args(args): + if "_ansible_remote_tmp" not in args: + args["_ansible_remote_tmp"] = "/tmp" + if "_ansible_keep_remote_files" not in args: + args["_ansible_keep_remote_files"] = False + + args = json.dumps({"ANSIBLE_MODULE_ARGS": args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +class AnsibleExitJson(Exception): + pass + + +class AnsibleFailJson(Exception): + pass + + +def exit_json(*args, **kwargs): + if "changed" not in kwargs: + kwargs["changed"] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + kwargs["failed"] = True + raise AnsibleFailJson(kwargs) + + +class ModuleTestCase(unittest.TestCase): + def setUp(self): + self.mock_module = patch.multiple( + basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json, + ) + self.mock_module.start() + self.mock_sleep = patch("time.sleep") + self.mock_sleep.start() + set_module_args({}) + self.addCleanup(self.mock_module.stop) + self.addCleanup(self.mock_sleep.stop) |