summaryrefslogtreecommitdiffstats
path: root/ansible_collections/cisco/ios/tests/unit/modules
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/cisco/ios/tests/unit/modules
parentInitial commit. (diff)
downloadansible-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')
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/__init__.py0
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/conftest.py32
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/__init__.py0
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/__init__.py0
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/__init__.py0
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/configure_terminal0
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_banner_show_banner.txt3
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_banner_show_running_config_ios12.txt15
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_bgp_config.cfg24
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_config.cfg12
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_defaults.cfg13
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_config_src.cfg11
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_dir23
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_cdp4
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_cdp_neighbors_detail40
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_interfaces91
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_inventory101
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_ip_interface0
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_ipv6_interface0
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_lldp6
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_lldp_neighbors_detail48
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_memory_statistics4
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_switch_virtual6
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_version68
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_facts_show_virtual_switch20
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_l2_interfaces.cfg19
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_l3_interfaces.cfg16
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_config.cfg11
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_config_ios12.cfg6
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_logging_global.cfg35
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ntp_config.cfg7
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ospfv2.cfg10
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ospfv3.cfg8
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_10.255.255.250_repeat_24
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_repeat_24
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_size_14004
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_ping_ping_8.8.8.8_size_1400_df-bit5
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_prefix_lists.cfg12
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_route_maps.cfg45
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_system_config.cfg14
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_user_config.cfg2
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vlan_config.cfg9
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vlans_config.cfg38
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/ios_vrf_config.cfg81
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/fixtures/show_version43
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/ios_module.py92
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_acl_interfaces.py483
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_acls.py1481
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_banner.py96
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp.py344
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp_address_family.py1374
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_bgp_global.py736
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_command.py130
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_config.py303
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_facts.py173
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_hostname.py121
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_interfaces.py785
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_l2_interfaces.py787
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_l3_interfaces.py473
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lacp.py158
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lacp_interfaces.py312
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lag_interfaces.py509
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lldp_global.py173
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_lldp_inteface.py318
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_logging.py153
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_logging_global.py711
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ntp.py110
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ntp_global.py470
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospf_interfaces.py666
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospfv2.py943
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ospfv3.py785
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_ping.py149
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_prefix_lists.py557
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_route_maps.py820
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_service.py435
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_snmp_server.py1802
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_static_routes.py2166
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_system.py144
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_user.py153
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_vlans.py650
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/network/ios/test_ios_vrf.py390
-rw-r--r--ansible_collections/cisco/ios/tests/unit/modules/utils.py55
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)