summaryrefslogtreecommitdiffstats
path: root/ansible_collections/dellemc/enterprise_sonic/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:35 +0000
commit7fec0b69a082aaeec72fee0612766aa42f6b1b4d (patch)
treeefb569b86ca4da888717f5433e757145fa322e08 /ansible_collections/dellemc/enterprise_sonic/plugins
parentReleasing progress-linux version 7.7.0+dfsg-3~progress7.99u1. (diff)
downloadansible-7fec0b69a082aaeec72fee0612766aa42f6b1b4d.tar.xz
ansible-7fec0b69a082aaeec72fee0612766aa42f6b1b4d.zip
Merging upstream version 9.4.0+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/dellemc/enterprise_sonic/plugins')
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/sonic.py2
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/aaa/aaa.py4
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/acl_interfaces/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/acl_interfaces/acl_interfaces.py82
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bfd/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bfd/bfd.py89
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp/bgp.py3
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_af/bgp_af.py19
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_as_paths/bgp_as_paths.py2
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_communities/bgp_communities.py4
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_ext_communities/bgp_ext_communities.py4
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/copp/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/copp/copp.py59
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_relay/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_relay/dhcp_relay.py94
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_snooping/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_snooping/dhcp_snooping.py81
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/facts/facts.py20
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/interfaces/interfaces.py25
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ip_neighbor/ip_neighbor.py56
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_acls/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_acls/l2_acls.py129
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_interfaces/l2_interfaces.py4
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_acls/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_acls/l3_acls.py223
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_interfaces/l3_interfaces.py4
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py2
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lldp_global/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lldp_global/lldp_global.py81
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/logging/logging.py64
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mac/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mac/mac.py66
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mclag/mclag.py16
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ntp/ntp.py6
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/pki/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/pki/pki.py78
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_breakout/port_breakout.py8
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_group/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_group/port_group.py66
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/prefix_lists/prefix_lists.py4
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/radius_server/radius_server.py6
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/route_maps/route_maps.py196
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/static_routes/static_routes.py2
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/stp/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/stp/stp.py152
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/system/system.py2
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/tacacs_server/tacacs_server.py4
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/users/users.py4
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlan_mapping/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlan_mapping/vlan_mapping.py64
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlans/vlans.py2
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vrfs/vrfs.py2
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vxlans/vxlans.py3
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/aaa/aaa.py132
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/acl_interfaces/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/acl_interfaces/acl_interfaces.py499
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bfd/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bfd/bfd.py734
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp/bgp.py228
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_af/bgp_af.py672
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_as_paths/bgp_as_paths.py195
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_communities/bgp_communities.py290
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_ext_communities/bgp_ext_communities.py287
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors/bgp_neighbors.py294
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors_af/bgp_neighbors_af.py7
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/copp/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/copp/copp.py393
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_relay/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_relay/dhcp_relay.py695
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_snooping/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_snooping/dhcp_snooping.py649
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/interfaces/interfaces.py556
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/ip_neighbor/ip_neighbor.py420
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_acls/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_acls/l2_acls.py602
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_interfaces/l2_interfaces.py560
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_acls/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_acls/l3_acls.py763
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_interfaces/l3_interfaces.py159
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py75
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lldp_global/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lldp_global/lldp_global.py296
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/logging/logging.py458
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mac/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mac/mac.py431
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mclag/mclag.py328
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/ntp/ntp.py188
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/pki/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/pki/pki.py563
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/port_breakout/port_breakout.py71
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/port_group/port_group.py380
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/prefix_lists/prefix_lists.py63
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/radius_server/radius_server.py83
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/route_maps/route_maps.py2354
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/static_routes/static_routes.py187
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/stp/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/stp/stp.py1404
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/system/system.py169
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/tacacs_server/tacacs_server.py83
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/users/users.py94
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlan_mapping/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlan_mapping/vlan_mapping.py517
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlans/vlans.py114
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vrfs/vrfs.py192
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vxlans/vxlans.py139
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/aaa/aaa.py3
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/acl_interfaces/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/acl_interfaces/acl_interfaces.py148
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bfd/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bfd/bfd.py236
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp/bgp.py6
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_af/bgp_af.py44
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_as_paths/bgp_as_paths.py3
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_communities/bgp_communities.py54
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_ext_communities/bgp_ext_communities.py69
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors/bgp_neighbors.py5
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors_af/bgp_neighbors_af.py8
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/copp/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/copp/copp.py127
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_relay/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_relay/dhcp_relay.py208
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_snooping/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_snooping/dhcp_snooping.py213
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/facts.py34
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/interfaces/interfaces.py31
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ip_neighbor/ip_neighbor.py126
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_acls/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_acls/l2_acls.py236
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py26
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_acls/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_acls/l3_acls.py322
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_interfaces/l3_interfaces.py1
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py1
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lldp_global/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lldp_global/lldp_global.py114
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/logging/logging.py128
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mac/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mac/mac.py151
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mclag/mclag.py40
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ntp/ntp.py14
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/pki/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/pki/pki.py144
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/port_breakout/port_breakout.py13
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/port_group/port_group.py116
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/radius_server/radius_server.py4
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/route_maps/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/route_maps/route_maps.py517
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/static_routes/static_routes.py3
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/stp/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/stp/stp.py364
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/system/system.py1
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/tacacs_server/tacacs_server.py2
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/users/users.py20
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlan_mapping/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlan_mapping/vlan_mapping.py225
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlans/vlans.py1
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vrfs/vrfs.py1
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vxlans/vxlans.py8
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/sonic.py3
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/bgp_utils.py11
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/formatted_diff_utils.py588
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/interfaces_util.py86
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/utils.py242
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_aaa.py72
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_acl_interfaces.py385
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bfd.py684
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp.py275
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_af.py375
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_as_paths.py133
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_communities.py169
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_ext_communities.py145
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors.py2
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors_af.py2
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_config.py2
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_copp.py295
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_dhcp_relay.py781
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_dhcp_snooping.py499
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_facts.py18
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_interfaces.py296
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ip_neighbor.py300
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l2_acls.py582
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l2_interfaces.py159
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l3_acls.py1058
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l3_interfaces.py198
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_lag_interfaces.py107
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_lldp_global.py301
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_logging.py274
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_mac.py319
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_mclag.py686
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ntp.py163
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_pki.py301
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_port_breakout.py209
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_port_group.py370
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_prefix_lists.py97
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_radius_server.py108
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_route_maps.py1606
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_static_routes.py83
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_stp.py677
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_system.py85
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_tacacs_server.py111
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_users.py159
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vlan_mapping.py543
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vlans.py60
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vrfs.py84
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vxlans.py87
205 files changed, 33854 insertions, 1759 deletions
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/sonic.py b/ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/sonic.py
index 37f1d872a..e5cc7630f 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/sonic.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/sonic.py
@@ -32,8 +32,6 @@ description:
import json
-from itertools import chain
-
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.common._collections_compat import Mapping
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/aaa/aaa.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/aaa/aaa.py
index 86040892a..a61b7307b 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/aaa/aaa.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/aaa/aaa.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2021 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -60,7 +60,7 @@ class AaaArgs(object): # pylint: disable=R0903
'type': 'dict'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'overridden', 'replaced'],
'default': 'merged', 'type': 'str'
}
} # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/acl_interfaces/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/acl_interfaces/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/acl_interfaces/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/acl_interfaces/acl_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/acl_interfaces/acl_interfaces.py
new file mode 100644
index 000000000..45f7bf480
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/acl_interfaces/acl_interfaces.py
@@ -0,0 +1,82 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_acl_interfaces module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Acl_interfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_acl_interfaces module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'access_groups': {
+ 'elements': 'dict',
+ 'options': {
+ 'acls': {
+ 'elements': 'dict',
+ 'options': {
+ 'direction': {
+ 'choices': ['in', 'out'],
+ 'required': True,
+ 'type': 'str'
+ },
+ 'name': {
+ 'required': True,
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'type': {
+ 'choices': ['mac', 'ipv4', 'ipv6'],
+ 'required': True,
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'name': {
+ 'required': True,
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'replaced', 'overridden', 'deleted'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bfd/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bfd/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bfd/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bfd/bfd.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bfd/bfd.py
new file mode 100644
index 000000000..57532795e
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bfd/bfd.py
@@ -0,0 +1,89 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_bfd module
+"""
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+class BfdArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_bfd module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'multi_hops': {
+ 'elements': 'dict',
+ 'options': {
+ 'detect_multiplier': {'default': 3, 'type': 'int'},
+ 'enabled': {'default': True, 'type': 'bool'},
+ 'local_address': {'required': True, 'type': 'str'},
+ 'min_ttl': {'default': 254, 'type': 'int'},
+ 'passive_mode': {'default': False, 'type': 'bool'},
+ 'profile_name': {'type': 'str'},
+ 'receive_interval': {'default': 300, 'type': 'int'},
+ 'remote_address': {'required': True, 'type': 'str'},
+ 'transmit_interval': {'default': 300, 'type': 'int'},
+ 'vrf': {'required': True, 'type': 'str'}},
+ 'type': 'list'},
+ 'profiles': {
+ 'elements': 'dict',
+ 'options': {
+ 'detect_multiplier': {'default': 3, 'type': 'int'},
+ 'echo_interval': {'default': 300, 'type': 'int'},
+ 'echo_mode': {'default': False, 'type': 'bool'},
+ 'enabled': {'default': True, 'type': 'bool'},
+ 'min_ttl': {'default': 254, 'type': 'int'},
+ 'passive_mode': {'default': False, 'type': 'bool'},
+ 'profile_name': {'required': True, 'type': 'str'},
+ 'receive_interval': {'default': 300, 'type': 'int'},
+ 'transmit_interval': {'default': 300, 'type': 'int'}},
+ 'type': 'list'},
+ 'single_hops': {
+ 'elements': 'dict',
+ 'options': {
+ 'detect_multiplier': {'default': 3, 'type': 'int'},
+ 'echo_interval': {'default': 300, 'type': 'int'},
+ 'echo_mode': {'default': False, 'type': 'bool'},
+ 'enabled': {'default': True, 'type': 'bool'},
+ 'interface': {'required': True, 'type': 'str'},
+ 'local_address': {'required': True, 'type': 'str'},
+ 'passive_mode': {'default': False, 'type': 'bool'},
+ 'profile_name': {'type': 'str'},
+ 'receive_interval': {'default': 300, 'type': 'int'},
+ 'remote_address': {'required': True, 'type': 'str'},
+ 'transmit_interval': {'default': 300, 'type': 'int'},
+ 'vrf': {'required': True, 'type': 'str'}},
+ 'type': 'list'}
+ },
+ 'type': 'dict'
+ },
+ 'state': {'choices': ['merged', 'deleted', 'replaced', 'overridden'], 'default': 'merged', 'type': 'str'}
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp/bgp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp/bgp.py
index fb7618133..8d494dddd 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp/bgp.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp/bgp.py
@@ -79,6 +79,7 @@ class BgpArgs(object): # pylint: disable=R0903
},
"type": "dict"
},
+ 'rt_delay': {'type': 'int'},
'timers': {
'options': {
'holdtime': {'type': 'int'},
@@ -91,7 +92,7 @@ class BgpArgs(object): # pylint: disable=R0903
'type': 'list'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
'default': 'merged'
}
} # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_af/bgp_af.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_af/bgp_af.py
index ac22210ee..336d49b47 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_af/bgp_af.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_af/bgp_af.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2019 Red Hat
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -71,6 +71,21 @@ class Bgp_afArgs(object): # pylint: disable=R0903
'required': True,
'type': 'str'
},
+ 'rd': {'type': 'str'},
+ 'rt_in': {'type': 'list', 'elements': 'str'},
+ 'rt_out': {'type': 'list', 'elements': 'str'},
+ 'vnis': {
+ 'elements': 'dict',
+ 'options': {
+ 'advertise_default_gw': {'type': 'bool'},
+ 'advertise_svi_ip': {'type': 'bool'},
+ 'rd': {'type': 'str'},
+ 'rt_in': {'type': 'list', 'elements': 'str'},
+ 'rt_out': {'type': 'list', 'elements': 'str'},
+ 'vni_number': {'required': True, 'type': 'int'}
+ },
+ 'type': 'list'
+ },
'max_path': {
'options': {
'ebgp': {'type': 'int'},
@@ -111,7 +126,7 @@ class Bgp_afArgs(object): # pylint: disable=R0903
'type': 'list'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'overridden', 'replaced'],
'default': 'merged'
}
} # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_as_paths/bgp_as_paths.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_as_paths/bgp_as_paths.py
index dec9b930e..d9d4ed766 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_as_paths/bgp_as_paths.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_as_paths/bgp_as_paths.py
@@ -43,6 +43,6 @@ class Bgp_as_pathsArgs(object): # pylint: disable=R0903
'type': 'list'},
'name': {'required': True, 'type': 'str'}},
'type': 'list'},
- 'state': {'choices': ['merged', 'deleted'],
+ 'state': {'choices': ['merged', 'deleted', 'replaced', 'overridden'],
'default': 'merged',
'type': 'str'}} # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_communities/bgp_communities.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_communities/bgp_communities.py
index 867e55204..c90fab8e9 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_communities/bgp_communities.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_communities/bgp_communities.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -54,6 +54,6 @@ class Bgp_communitiesArgs(object): # pylint: disable=R0903
'default': 'standard',
'type': 'str'}},
'type': 'list'},
- 'state': {'choices': ['merged', 'deleted'],
+ 'state': {'choices': ['merged', 'deleted', 'replaced', 'overridden'],
'default': 'merged',
'type': 'str'}} # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_ext_communities/bgp_ext_communities.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_ext_communities/bgp_ext_communities.py
index aec0f364a..4cee6182b 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_ext_communities/bgp_ext_communities.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_ext_communities/bgp_ext_communities.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -68,7 +68,7 @@ class Bgp_ext_communitiesArgs(object): # pylint: disable=R0903
'type': 'list'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
'default': 'merged',
'type': 'str'
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/copp/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/copp/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/copp/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/copp/copp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/copp/copp.py
new file mode 100644
index 000000000..889c614c6
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/copp/copp.py
@@ -0,0 +1,59 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_copp module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class CoppArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_copp module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'copp_groups': {
+ 'elements': 'dict',
+ 'options': {
+ 'cbs': {'type': 'str'},
+ 'cir': {'type': 'str'},
+ 'copp_name': {'required': True, 'type': 'str'},
+ 'queue': {'type': 'int'},
+ 'trap_action': {'type': 'str'},
+ 'trap_priority': {'type': 'int'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'state': {'choices': ['merged', 'deleted', 'replaced', 'overridden'], 'default': 'merged', 'type': 'str'}
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_relay/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_relay/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_relay/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_relay/dhcp_relay.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_relay/dhcp_relay.py
new file mode 100644
index 000000000..0ca834487
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_relay/dhcp_relay.py
@@ -0,0 +1,94 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_dhcp_relay module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Dhcp_relayArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_dhcp_relay module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'ipv4': {
+ 'options': {
+ 'circuit_id': {
+ 'choices': ['%h:%p', '%i', '%p'],
+ 'type': 'str'
+ },
+ 'link_select': {'type': 'bool'},
+ 'max_hop_count': {'type': 'int'},
+ 'policy_action': {
+ 'choices': ['append', 'discard', 'replace'],
+ 'type': 'str'
+ },
+ 'server_addresses': {
+ 'elements': 'dict',
+ 'options': {
+ 'address': {'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'source_interface': {'type': 'str'},
+ 'vrf_name': {'type': 'str'},
+ 'vrf_select': {'type': 'bool'}
+ },
+ 'type': 'dict'
+ },
+ 'ipv6': {
+ 'options': {
+ 'max_hop_count': {'type': 'int'},
+ 'server_addresses': {
+ 'elements': 'dict',
+ 'options': {
+ 'address': {'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'source_interface': {'type': 'str'},
+ 'vrf_name': {'type': 'str'},
+ 'vrf_select': {'type': 'bool'}
+ },
+ 'type': 'dict'
+ },
+ 'name': {'required': True, 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_snooping/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_snooping/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_snooping/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_snooping/dhcp_snooping.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_snooping/dhcp_snooping.py
new file mode 100644
index 000000000..6cf2ecacd
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/dhcp_snooping/dhcp_snooping.py
@@ -0,0 +1,81 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_dhcp_snooping module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Dhcp_snoopingArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_dhcp_snooping module"""
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'afis': {
+ 'elements': 'dict',
+ 'options': {
+ 'afi': {
+ 'choices': ['ipv4', 'ipv6'],
+ 'required': True,
+ 'type': 'str',
+ },
+ 'enabled': {'type': 'bool'},
+ 'source_bindings': {
+ 'elements': 'dict',
+ 'options': {
+ 'mac_addr': {'required': True, 'type': 'str'},
+ 'ip_addr': {'type': 'str'},
+ 'intf_name': {'type': 'str'},
+ 'vlan_id': {'type': 'int'},
+ },
+ 'type': 'list',
+ },
+ 'trusted': {
+ 'elements': 'dict',
+ 'options': {
+ 'intf_name': {'required': True, 'type': 'str'},
+ },
+ 'type': 'list',
+ },
+ 'verify_mac': {'type': 'bool'},
+ 'vlans': {'elements': 'str', 'type': 'list'},
+ },
+ 'type': 'list',
+ }
+ },
+ 'type': 'dict',
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted', 'overridden', 'replaced'],
+ 'default': 'merged',
+ 'type': 'str',
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/facts/facts.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/facts/facts.py
index 3a4d02989..6b27194eb 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/facts/facts.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/facts/facts.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2021 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -35,6 +35,7 @@ class FactsArgs(object): # pylint: disable=R0903
'bgp_ext_communities',
'mclag',
'prefix_lists',
+ 'vlan_mapping',
'vrfs',
'vxlans',
'users',
@@ -44,7 +45,22 @@ class FactsArgs(object): # pylint: disable=R0903
'tacacs_server',
'radius_server',
'static_routes',
- 'ntp'
+ 'ntp',
+ 'logging',
+ 'pki',
+ 'ip_neighbor',
+ 'port_group',
+ 'dhcp_relay',
+ 'dhcp_snooping',
+ 'acl_interfaces',
+ 'l2_acls',
+ 'l3_acls',
+ 'lldp_global',
+ 'mac',
+ 'bfd',
+ 'copp',
+ 'route_maps',
+ 'stp'
]
argument_spec = {
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/interfaces/interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/interfaces/interfaces.py
index 76c36a90b..407dbde6b 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/interfaces/interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/interfaces/interfaces.py
@@ -44,12 +44,33 @@ class InterfacesArgs(object): # pylint: disable=R0903
"description": {"type": "str"},
"enabled": {"type": "bool"},
"mtu": {"type": "int"},
- "name": {"required": True, "type": "str"}
+ "name": {"required": True, "type": "str"},
+ "speed": {"type": "str",
+ "choices": ["SPEED_10MB",
+ "SPEED_100MB",
+ "SPEED_1GB",
+ "SPEED_2500MB",
+ "SPEED_5GB",
+ "SPEED_10GB",
+ "SPEED_20GB",
+ "SPEED_25GB",
+ "SPEED_40GB",
+ "SPEED_50GB",
+ "SPEED_100GB",
+ "SPEED_400GB"]},
+ "auto_negotiate": {"type": "bool"},
+ "advertised_speed": {"type": "list", "elements": "str"},
+ "fec": {"type": "str",
+ "choices": ["FEC_RS",
+ "FEC_FC",
+ "FEC_DISABLED",
+ "FEC_DEFAULT",
+ "FEC_AUTO"]}
},
"type": "list"
},
"state": {
- "choices": ["merged", "deleted"],
+ "choices": ["merged", "replaced", "overridden", "deleted"],
"default": "merged",
"type": "str"
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ip_neighbor/ip_neighbor.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ip_neighbor/ip_neighbor.py
new file mode 100644
index 000000000..fef1c67c0
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ip_neighbor/ip_neighbor.py
@@ -0,0 +1,56 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_ip_neighbor module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Ip_neighborArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_ip_neighbor module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'ipv4_arp_timeout': {'type': 'int'},
+ 'ipv4_drop_neighbor_aging_time': {'type': 'int'},
+ 'ipv6_drop_neighbor_aging_time': {'type': 'int'},
+ 'ipv6_nd_cache_expiry': {'type': 'int'},
+ 'num_local_neigh': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'state': {
+ 'choices': ['merged', 'replaced', 'overridden', 'deleted'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_acls/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_acls/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_acls/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_acls/l2_acls.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_acls/l2_acls.py
new file mode 100644
index 000000000..5b8ba4f87
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_acls/l2_acls.py
@@ -0,0 +1,129 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_l2_acls module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class L2_aclsArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_l2_acls module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'name': {'required': True, 'type': 'str'},
+ 'remark': {'type': 'str'},
+ 'rules': {
+ 'elements': 'dict',
+ 'mutually_exclusive': [['ethertype', 'vlan_tag_format']],
+ 'options': {
+ 'action': {
+ 'choices': ['deny', 'discard', 'do-not-nat', 'permit', 'transit'],
+ 'type': 'str'
+ },
+ 'dei': {
+ 'choices': [0, 1],
+ 'type': 'int'
+ },
+ 'destination': {
+ 'mutually_exclusive': [['any', 'host', 'address']],
+ 'options': {
+ 'address': {'type': 'str'},
+ 'address_mask': {'type': 'str'},
+ 'any': {'type': 'bool'},
+ 'host': {'type': 'str'}
+ },
+ 'required_one_of': [['any', 'host', 'address']],
+ 'required_together': [['address', 'address_mask']],
+ 'type': 'dict'
+ },
+ 'ethertype': {
+ 'mutually_exclusive': [['value', 'arp', 'ipv4', 'ipv6']],
+ 'options': {
+ 'arp': {'type': 'bool'},
+ 'ipv4': {'type': 'bool'},
+ 'ipv6': {'type': 'bool'},
+ 'value': {'type': 'str'}
+ },
+ 'type': 'dict'
+ },
+ 'pcp': {
+ 'mutually_exclusive': [
+ ['value', 'traffic_type'],
+ ['mask', 'traffic_type']
+ ],
+ 'options': {
+ 'mask': {'type': 'int'},
+ 'traffic_type': {
+ 'choices': ['be', 'bk', 'ee', 'ca', 'vi', 'vo', 'ic', 'nc'],
+ 'type': 'str'
+ },
+ 'value': {'type': 'int'}
+ },
+ 'required_by': {'mask': ['value']},
+ 'type': 'dict'
+ },
+ 'remark': {'type': 'str'},
+ 'sequence_num': {'required': True, 'type': 'int'},
+ 'source': {
+ 'mutually_exclusive': [['any', 'host', 'address']],
+ 'options': {
+ 'address': {'type': 'str'},
+ 'address_mask': {'type': 'str'},
+ 'any': {'type': 'bool'},
+ 'host': {'type': 'str'}
+ },
+ 'required_one_of': [['any', 'host', 'address']],
+ 'required_together': [['address', 'address_mask']],
+ 'type': 'dict'
+ },
+ 'vlan_id': {'type': 'int'},
+ 'vlan_tag_format': {
+ 'options': {
+ 'multi_tagged': {'type': 'bool'}
+ },
+ 'type': 'dict'
+ }
+ },
+ 'required_together': [['action', 'source', 'destination']],
+ 'type': 'list'
+ }
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'replaced', 'overridden', 'deleted'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_interfaces/l2_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_interfaces/l2_interfaces.py
index bbebe2d54..b5ce4ad8e 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_interfaces/l2_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_interfaces/l2_interfaces.py
@@ -53,7 +53,7 @@ class L2_interfacesArgs(object): # pylint: disable=R0903
'allowed_vlans': {
'elements': 'dict',
'options': {
- 'vlan': {'type': 'int'}
+ 'vlan': {'type': 'str'}
},
'type': 'list'
}
@@ -64,7 +64,7 @@ class L2_interfacesArgs(object): # pylint: disable=R0903
'type': 'list'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
'default': 'merged',
'type': 'str'
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_acls/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_acls/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_acls/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_acls/l3_acls.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_acls/l3_acls.py
new file mode 100644
index 000000000..7339201ef
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_acls/l3_acls.py
@@ -0,0 +1,223 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_l3_acls module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class L3_aclsArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_l3_acls module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'acls': {
+ 'elements': 'dict',
+ 'options': {
+ 'name': {'required': True, 'type': 'str'},
+ 'remark': {'type': 'str'},
+ 'rules': {
+ 'elements': 'dict',
+ 'options': {
+ 'action': {
+ 'choices': ['deny', 'discard', 'do-not-nat', 'permit', 'transit'],
+ 'type': 'str'
+ },
+ 'destination': {
+ 'mutually_exclusive': [['any', 'host', 'prefix']],
+ 'options': {
+ 'any': {'type': 'bool'},
+ 'host': {'type': 'str'},
+ 'port_number': {
+ 'mutually_exclusive': [['eq', 'gt', 'lt', 'range']],
+ 'options': {
+ 'eq': {'type': 'int'},
+ 'gt': {'type': 'int'},
+ 'lt': {'type': 'int'},
+ 'range': {
+ 'options': {
+ 'begin': {'type': 'int'},
+ 'end': {'type': 'int'}
+ },
+ 'required_together': [['begin', 'end']],
+ 'type': 'dict'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'prefix': {'type': 'str'}
+ },
+ 'required_one_of': [['any', 'host', 'prefix']],
+ 'type': 'dict'
+ },
+ 'dscp': {
+ 'mutually_exclusive': [[
+ 'value', 'af11', 'af12', 'af13', 'af21', 'af22', 'af23', 'af31', 'af32', 'af33',
+ 'cs1', 'cs2', 'cs3', 'cs4', 'cs5', 'cs6', 'cs7', 'default', 'ef', 'voice_admit'
+ ]],
+ 'options': {
+ 'af11': {'type': 'bool'},
+ 'af12': {'type': 'bool'},
+ 'af13': {'type': 'bool'},
+ 'af21': {'type': 'bool'},
+ 'af22': {'type': 'bool'},
+ 'af23': {'type': 'bool'},
+ 'af31': {'type': 'bool'},
+ 'af32': {'type': 'bool'},
+ 'af33': {'type': 'bool'},
+ 'af41': {'type': 'bool'},
+ 'af42': {'type': 'bool'},
+ 'af43': {'type': 'bool'},
+ 'cs1': {'type': 'bool'},
+ 'cs2': {'type': 'bool'},
+ 'cs3': {'type': 'bool'},
+ 'cs4': {'type': 'bool'},
+ 'cs5': {'type': 'bool'},
+ 'cs6': {'type': 'bool'},
+ 'cs7': {'type': 'bool'},
+ 'default': {'type': 'bool'},
+ 'ef': {'type': 'bool'},
+ 'value': {'type': 'int'},
+ 'voice_admit': {'type': 'bool'}
+ },
+ 'type': 'dict'
+ },
+ 'protocol': {
+ 'mutually_exclusive': [['name', 'number']],
+ 'options': {
+ 'name': {
+ 'choices': ['ip', 'ipv6', 'icmp', 'icmpv6', 'tcp', 'udp'],
+ 'type': 'str'
+ },
+ 'number': {'type': 'int'}
+ },
+ 'required_one_of': [['name', 'number']],
+ 'type': 'dict'
+ },
+ 'protocol_options': {
+ 'mutually_exclusive': [['icmp', 'icmpv6', 'tcp']],
+ 'options': {
+ 'icmp': {
+ 'options': {
+ 'code': {'type': 'int'},
+ 'type': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'icmpv6': {
+ 'options': {
+ 'code': {'type': 'int'},
+ 'type': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'tcp': {
+ 'mutually_exclusive': [
+ ['established', 'ack', 'not_ack'],
+ ['established', 'fin', 'not_fin'],
+ ['established', 'psh', 'not_psh'],
+ ['established', 'rst', 'not_rst'],
+ ['established', 'syn', 'not_syn'],
+ ['established', 'urg', 'not_urg']
+ ],
+ 'options': {
+ 'ack': {'type': 'bool'},
+ 'established': {'type': 'bool'},
+ 'fin': {'type': 'bool'},
+ 'not_ack': {'type': 'bool'},
+ 'not_fin': {'type': 'bool'},
+ 'not_psh': {'type': 'bool'},
+ 'not_rst': {'type': 'bool'},
+ 'not_syn': {'type': 'bool'},
+ 'not_urg': {'type': 'bool'},
+ 'psh': {'type': 'bool'},
+ 'rst': {'type': 'bool'},
+ 'syn': {'type': 'bool'},
+ 'urg': {'type': 'bool'}
+ },
+ 'type': 'dict'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'remark': {'type': 'str'},
+ 'sequence_num': {'required': True, 'type': 'int'},
+ 'source': {
+ 'mutually_exclusive': [['any', 'host', 'prefix']],
+ 'options': {
+ 'any': {'type': 'bool'},
+ 'host': {'type': 'str'},
+ 'port_number': {
+ 'mutually_exclusive': [['eq', 'gt', 'lt', 'range']],
+ 'options': {
+ 'eq': {'type': 'int'},
+ 'gt': {'type': 'int'},
+ 'lt': {'type': 'int'},
+ 'range': {
+ 'options': {
+ 'begin': {'type': 'int'},
+ 'end': {'type': 'int'}
+ },
+ 'required_together': [['begin', 'end']],
+ 'type': 'dict'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'prefix': {'type': 'str'}
+ },
+ 'required_one_of': [['any', 'host', 'prefix']],
+ 'type': 'dict'
+ },
+ 'vlan_id': {'type': 'int'}
+ },
+ 'required_together': [['action', 'protocol', 'source', 'destination']],
+ 'type': 'list'
+ }
+ },
+ 'type': 'list'
+ },
+ 'address_family': {
+ 'choices': ['ipv4', 'ipv6'],
+ 'required': True,
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'replaced', 'overridden', 'deleted'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_interfaces/l3_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_interfaces/l3_interfaces.py
index 6e83289cc..b32d7f92e 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_interfaces/l3_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_interfaces/l3_interfaces.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -74,7 +74,7 @@ class L3_interfacesArgs(object): # pylint: disable=R0903
'type': 'list'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
'default': 'merged',
'type': 'str'
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py
index 867d61a27..fc349232f 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py
@@ -60,7 +60,7 @@ class Lag_interfacesArgs(object): # pylint: disable=R0903
"type": "list"
},
"state": {
- "choices": ["merged", "deleted"],
+ "choices": ["merged", "replaced", "overridden", "deleted"],
"default": "merged",
"type": "str"
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lldp_global/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lldp_global/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lldp_global/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lldp_global/lldp_global.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lldp_global/lldp_global.py
new file mode 100644
index 000000000..8f9cd9af0
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lldp_global/lldp_global.py
@@ -0,0 +1,81 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_lldp_global module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Lldp_globalArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_lldp_global module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'enable': {
+ 'type': 'bool'
+ },
+ 'hello_time': {
+ 'type': 'int'
+ },
+ 'mode': {
+ 'choices': ['receive', 'transmit'],
+ 'type': 'str'
+ },
+ 'multiplier': {
+ 'type': 'int'
+ },
+ 'system_description': {
+ 'type': 'str'
+ },
+ 'system_name': {
+ 'type': 'str'
+ },
+ 'tlv_select': {
+ 'options': {
+ 'management_address': {
+ 'type': 'bool'
+ },
+ 'system_capabilities': {
+ 'type': 'bool'
+ }
+ },
+ 'type': 'dict'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/logging/logging.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/logging/logging.py
new file mode 100644
index 000000000..a83d9eef4
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/logging/logging.py
@@ -0,0 +1,64 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_logging module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class LoggingArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_logging module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'remote_servers': {
+ 'elements': 'dict',
+ 'options': {
+ 'host': {'required': True,
+ 'type': 'str'},
+ 'message_type': {'choices': ['log', 'event'],
+ 'type': 'str'},
+ 'remote_port': {'type': 'int'},
+ 'source_interface': {'type': 'str'},
+ 'vrf': {'type': 'str'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'state': {
+ 'choices': ['merged', "replaced", "overridden", 'deleted'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mac/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mac/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mac/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mac/mac.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mac/mac.py
new file mode 100644
index 000000000..d46155377
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mac/mac.py
@@ -0,0 +1,66 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_mac module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class MacArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_mac module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'mac': {
+ 'options': {
+ 'aging_time': {'default': '600', 'type': 'int'},
+ 'dampening_interval': {'default': '5', 'type': 'int'},
+ 'dampening_threshold': {'default': '5', 'type': 'int'},
+ 'mac_table_entries': {
+ 'elements': 'dict',
+ 'options': {
+ 'interface': {'type': 'str'},
+ 'mac_address': {'required': True, 'type': 'str'},
+ 'vlan_id': {'required': True, 'type': 'int'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'vrf_name': {'default': 'default', 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'state': {'choices': ['merged', 'deleted', 'replaced', 'overridden'], 'default': 'merged', 'type': 'str'}
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mclag/mclag.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mclag/mclag.py
index be3c38ca2..0d9b45eb0 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mclag/mclag.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mclag/mclag.py
@@ -41,6 +41,8 @@ class MclagArgs(object): # pylint: disable=R0903
'config': {
'options': {
'domain_id': {'required': True, 'type': 'int'},
+ 'gateway_mac': {'type': 'str'},
+ 'delay_restore': {'type': 'int'},
'keepalive': {'type': 'int'},
'peer_address': {'type': 'str'},
'peer_link': {'type': 'str'},
@@ -56,6 +58,18 @@ class MclagArgs(object): # pylint: disable=R0903
},
'type': 'dict'
},
+ 'peer_gateway': {
+ 'options': {
+ 'vlans': {
+ 'elements': 'dict',
+ 'options': {
+ 'vlan': {'type': 'str'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'dict'
+ },
'session_timeout': {'type': 'int'},
'source_address': {'type': 'str'},
'system_mac': {'type': 'str'},
@@ -75,7 +89,7 @@ class MclagArgs(object): # pylint: disable=R0903
'type': 'dict'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
'default': 'merged',
'type': 'str'
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ntp/ntp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ntp/ntp.py
index 062520af9..0d357cc2e 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ntp/ntp.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ntp/ntp.py
@@ -64,8 +64,10 @@ class NtpArgs(object): # pylint: disable=R0903
'type': 'str'},
'key_id': {'type': 'int', 'no_log': True},
'maxpoll': {'type': 'int'},
- 'minpoll': {'type': 'int'}
+ 'minpoll': {'type': 'int'},
+ 'prefer': {'type': 'bool'}
},
+ 'required_together': [['minpoll', 'maxpoll']],
'type': 'list'
},
'source_interfaces': {
@@ -82,7 +84,7 @@ class NtpArgs(object): # pylint: disable=R0903
'type': 'dict'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', "replaced", "overridden", 'deleted'],
'default': 'merged',
'type': 'str'
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/pki/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/pki/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/pki/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/pki/pki.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/pki/pki.py
new file mode 100644
index 000000000..5f4aa32af
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/pki/pki.py
@@ -0,0 +1,78 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell EMC
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_pki module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class PkiArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_pki module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'security_profiles': {
+ 'elements': 'dict',
+ 'options':
+ {
+ 'cdp_list': {'elements': 'str', 'type': 'list'},
+ 'certificate_name': {'type': 'str'},
+ 'key_usage_check': {'type': 'bool'},
+ 'ocsp_responder_list':
+ {
+ 'elements': 'str',
+ 'type': 'list'
+ },
+ 'peer_name_check': {'type': 'bool'},
+ 'profile_name': {'required': True, 'type': 'str'},
+ 'revocation_check': {'type': 'bool'},
+ 'trust_store': {'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'trust_stores': {
+ 'elements': 'dict',
+ 'options': {
+ 'ca_name': {'elements': 'str', 'type': 'list'},
+ 'name': {'required': True, 'type': 'str'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_breakout/port_breakout.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_breakout/port_breakout.py
index 3b8f4a5a3..90d736ff3 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_breakout/port_breakout.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_breakout/port_breakout.py
@@ -42,8 +42,10 @@ class Port_breakoutArgs(object): # pylint: disable=R0903
'elements': 'dict',
'options': {
'mode': {
- 'choices': ['1x100G', '1x400G', '1x40G', '2x100G', '2x200G',
- '2x50G', '4x100G', '4x10G', '4x25G', '4x50G'],
+ 'choices': ['1x10G', '1x25G', '1x40G', '1x50G', '1x100G',
+ '1x200G', '1x400G', '2x10G', '2x25G', '2x40G',
+ '2x50G', '2x100G', '2x200G', '4x10G', '4x25G',
+ '4x50G', '4x100G', '8x10G', '8x25G', '8x50G'],
'type': 'str'
},
'name': {'required': True, 'type': 'str'}
@@ -51,7 +53,7 @@ class Port_breakoutArgs(object): # pylint: disable=R0903
'type': 'list'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
'default': 'merged'
}
} # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_group/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_group/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_group/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_group/port_group.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_group/port_group.py
new file mode 100644
index 000000000..9db29de2e
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_group/port_group.py
@@ -0,0 +1,66 @@
+#
+# -*- coding: utf-8 -*-
+# © Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_port_group module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Port_groupArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_port_group module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'id': {'required': True, 'type': 'str'},
+ 'speed': {'choices': ['SPEED_10MB',
+ 'SPEED_100MB',
+ 'SPEED_1GB',
+ 'SPEED_2500MB',
+ 'SPEED_5GB',
+ 'SPEED_10GB',
+ 'SPEED_20GB',
+ 'SPEED_25GB',
+ 'SPEED_40GB',
+ 'SPEED_50GB',
+ 'SPEED_100GB',
+ 'SPEED_400GB'],
+ 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'replaced', 'overridden', 'deleted'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/prefix_lists/prefix_lists.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/prefix_lists/prefix_lists.py
index d043ae6f8..17de2eeae 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/prefix_lists/prefix_lists.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/prefix_lists/prefix_lists.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2019 Red Hat
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -64,7 +64,7 @@ class Prefix_listsArgs: # pylint: disable=R0903
'type': 'list'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
'default': 'merged',
'type': 'str'
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/radius_server/radius_server.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/radius_server/radius_server.py
index a56147a5b..0ef029d7b 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/radius_server/radius_server.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/radius_server/radius_server.py
@@ -59,7 +59,7 @@ class Radius_serverArgs(object): # pylint: disable=R0903
},
'key': {'type': 'str', 'no_log': True},
'name': {'type': 'str'},
- 'port': {'type': 'int'},
+ 'port': {'type': 'int', 'default': 1812},
'priority': {'type': 'int'},
'retransmit': {'type': 'int'},
'source_interface': {'type': 'str'},
@@ -72,12 +72,12 @@ class Radius_serverArgs(object): # pylint: disable=R0903
'type': 'dict'
},
'statistics': {'type': 'bool'},
- 'timeout': {'type': 'int'}
+ 'timeout': {'type': 'int', 'default': 5}
},
'type': 'dict'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged'
}
} # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/route_maps/route_maps.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/route_maps/route_maps.py
new file mode 100644
index 000000000..f36fbbcb0
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/route_maps/route_maps.py
@@ -0,0 +1,196 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_route_maps module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Route_mapsArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_route_maps module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'map_name': {'required': True, 'type': 'str'},
+ 'action': {
+ 'choices': ['permit', 'deny'],
+ 'type': 'str'
+ },
+ 'sequence_num': {
+ 'type': 'int'
+ },
+ 'match': {
+ 'options': {
+ 'as_path': {'type': 'str'},
+ 'community': {'type': 'str'},
+ 'evpn': {
+ 'options': {
+ 'default_route': {'type': 'bool'},
+ 'route_type': {
+ 'choices': ['macip', 'multicast', 'prefix'],
+ 'type': 'str'
+ },
+ 'vni': {'type': 'int'}
+ },
+ 'required_one_of': [['default_route', 'route_type', 'vni']],
+ 'type': 'dict'
+ },
+ 'ext_comm': {'type': 'str'},
+ 'interface': {'type': 'str'},
+ 'ip': {
+ 'options': {
+ 'address': {'type': 'str'},
+ 'next_hop': {'type': 'str'}
+ },
+ 'required_one_of': [['address', 'next_hop']],
+ 'type': 'dict'
+ },
+ 'ipv6': {
+ 'options': {
+ 'address': {
+ 'required': True,
+ 'type': 'str'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'local_preference': {'type': 'int'},
+ 'metric': {'type': 'int'},
+ 'origin': {
+ 'choices': ['egp', 'igp', 'incomplete'],
+ 'type': 'str'
+ },
+ 'peer': {
+ 'mutually_exclusive': [['ip', 'ipv6', 'interface']],
+ 'options': {
+ 'interface': {'type': 'str'},
+ 'ip': {'type': 'str'},
+ 'ipv6': {'type': 'str'}
+ },
+ 'required_one_of': [['ip', 'ipv6', 'interface']],
+ 'type': 'dict'
+ },
+ 'source_protocol': {
+ 'choices': ['bgp', 'connected', 'ospf', 'static'],
+ 'type': 'str'
+ },
+ 'source_vrf': {'type': 'str'},
+ 'tag': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'set': {
+ 'options': {
+ 'as_path_prepend': {'type': 'str'},
+ 'comm_list_delete': {'type': 'str'},
+ 'community': {
+ 'options': {
+ 'community_number': {
+ 'elements': 'str',
+ 'type': 'list'
+ },
+ 'community_attributes': {
+ 'elements': 'str',
+ 'type': 'list',
+ 'mutually_exclusive': [
+ ['none', 'local_as'],
+ ['none', 'no_advertise'],
+ ['none', 'no_export'],
+ ['none', 'no_peer'],
+ ['none', 'additive']
+ ],
+ 'choices': [
+ 'local_as',
+ 'no_advertise',
+ 'no_export',
+ 'no_peer',
+ 'additive',
+ 'none'
+ ]
+ },
+ },
+ 'type': 'dict'
+ },
+ 'extcommunity': {
+ 'options': {
+ 'rt': {
+ 'elements': 'str',
+ 'type': 'list'
+ },
+ 'soo': {
+ 'elements': 'str',
+ 'type': 'list'
+ }
+ },
+ 'required_one_of': [['rt', 'soo']],
+ 'type': 'dict'
+ },
+ 'ip_next_hop': {'type': 'str'},
+ 'ipv6_next_hop': {
+ 'options': {
+ 'global_addr': {'type': 'str'},
+ 'prefer_global': {'type': 'bool'}
+ },
+ 'required_one_of': [['global_addr', 'prefer_global']],
+ 'type': 'dict'},
+ 'local_preference': {'type': 'int'},
+ 'metric': {
+ 'mutually_exclusive': [['value', 'rtt_action']],
+ 'required_one_of': [['value', 'rtt_action']],
+ 'options': {
+ 'rtt_action': {
+ 'choices': ['set', 'add', 'subtract'],
+ 'type': 'str'
+ },
+ 'value': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'origin': {
+ 'choices': ['egp', 'igp', 'incomplete'],
+ 'type': 'str'
+ },
+ 'weight': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'call': {'type': 'str'},
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/static_routes/static_routes.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/static_routes/static_routes.py
index a146f1ecd..3dbaf3045 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/static_routes/static_routes.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/static_routes/static_routes.py
@@ -72,7 +72,7 @@ class Static_routesArgs(object): # pylint: disable=R0903
'type': 'list'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'overridden', 'replaced'],
'default': 'merged',
'type': 'str'
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/stp/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/stp/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/stp/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/stp/stp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/stp/stp.py
new file mode 100644
index 000000000..145632051
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/stp/stp.py
@@ -0,0 +1,152 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_stp module
+"""
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+class StpArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_stp module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'mutually_exclusive': [['mstp', 'pvst', 'rapid_pvst']],
+ 'options': {
+ 'global': {
+ 'options': {
+ 'bpdu_filter': {'default': False, 'type': 'bool'},
+ 'bridge_priority': {'default': 32768, 'type': 'int'},
+ 'disabled_vlans': {'elements': 'str', 'type': 'list'},
+ 'enabled_protocol': {'choices': ['mst', 'pvst', 'rapid_pvst'], 'type': 'str'},
+ 'fwd_delay': {'default': 15, 'type': 'int'},
+ 'hello_time': {'default': 2, 'type': 'int'},
+ 'loop_guard': {'default': False, 'type': 'bool'},
+ 'max_age': {'default': 20, 'type': 'int'},
+ 'portfast': {'default': False, 'type': 'bool'},
+ 'root_guard_timeout': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'interfaces': {
+ 'elements': 'dict',
+ 'options': {
+ 'bpdu_filter': {'default': False, 'type': 'bool'},
+ 'bpdu_guard': {'default': False, 'type': 'bool'},
+ 'cost': {'type': 'int'},
+ 'edge_port': {'default': False, 'type': 'bool'},
+ 'guard': {'choices': ['loop', 'root', 'none'], 'type': 'str'},
+ 'intf_name': {'required': True, 'type': 'str'},
+ 'link_type': {'choices': ['point-to-point', 'shared'], 'type': 'str'},
+ 'port_priority': {'type': 'int'},
+ 'portfast': {'default': False, 'type': 'bool'},
+ 'shutdown': {'default': False, 'type': 'bool'},
+ 'stp_enable': {'default': True, 'type': 'bool'},
+ 'uplink_fast': {'default': False, 'type': 'bool'}
+ },
+ 'type': 'list'
+ },
+ 'mstp': {
+ 'options': {
+ 'fwd_delay': {'type': 'int'},
+ 'hello_time': {'type': 'int'},
+ 'max_age': {'type': 'int'},
+ 'max_hop': {'type': 'int'},
+ 'mst_instances': {
+ 'elements': 'dict',
+ 'options': {
+ 'bridge_priority': {'type': 'int'},
+ 'mst_id': {'required': True, 'type': 'int'},
+ 'vlans': {'elements': 'str', 'type': 'list'},
+ 'interfaces': {
+ 'elements': 'dict',
+ 'options': {
+ 'cost': {'type': 'int'},
+ 'intf_name': {'required': True, 'type': 'str'},
+ 'port_priority': {'type': 'int'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'list'
+ },
+ 'mst_name': {'type': 'str'},
+ 'revision': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'pvst': {
+ 'elements': 'dict',
+ 'options': {
+ 'bridge_priority': {'type': 'int'},
+ 'fwd_delay': {'type': 'int'},
+ 'hello_time': {'type': 'int'},
+ 'vlan_id': {'required': True, 'type': 'int'},
+ 'max_age': {'type': 'int'},
+ 'interfaces': {
+ 'elements': 'dict',
+ 'options': {
+ 'cost': {'type': 'int'},
+ 'intf_name': {'required': True, 'type': 'str'},
+ 'port_priority': {'type': 'int'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'list'
+ },
+ 'rapid_pvst': {
+ 'elements': 'dict',
+ 'options': {
+ 'bridge_priority': {'type': 'int'},
+ 'fwd_delay': {'type': 'int'},
+ 'hello_time': {'type': 'int'},
+ 'vlan_id': {'required': True, 'type': 'int'},
+ 'max_age': {'type': 'int'},
+ 'interfaces': {
+ 'elements': 'dict',
+ 'options': {
+ 'cost': {'type': 'int'},
+ 'intf_name': {'required': True, 'type': 'str'},
+ 'port_priority': {'type': 'int'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
+ 'default': 'merged', 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/system/system.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/system/system.py
index b08c5f4bc..df835c156 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/system/system.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/system/system.py
@@ -57,7 +57,7 @@ class SystemArgs(object): # pylint: disable=R0903
'type': 'dict'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/tacacs_server/tacacs_server.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/tacacs_server/tacacs_server.py
index aad1746d4..98df26913 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/tacacs_server/tacacs_server.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/tacacs_server/tacacs_server.py
@@ -69,12 +69,12 @@ class Tacacs_serverArgs(object): # pylint: disable=R0903
'type': 'dict'
},
'source_interface': {'type': 'str'},
- 'timeout': {'type': 'int'}
+ 'timeout': {'type': 'int', 'default': 5}
},
'type': 'dict'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged'
}
} # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/users/users.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/users/users.py
index db23d78e0..7adca72a1 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/users/users.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/users/users.py
@@ -44,7 +44,7 @@ class UsersArgs(object): # pylint: disable=R0903
'name': {'required': True, 'type': 'str'},
'password': {'type': 'str', 'no_log': True},
'role': {
- 'choices': ['admin', 'operator'],
+ 'choices': ['admin', 'operator', 'netadmin', 'secadmin'],
'type': 'str'
},
'update_password': {
@@ -56,7 +56,7 @@ class UsersArgs(object): # pylint: disable=R0903
'type': 'list'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'overridden', 'replaced'],
'default': 'merged'
}
} # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlan_mapping/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlan_mapping/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlan_mapping/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlan_mapping/vlan_mapping.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlan_mapping/vlan_mapping.py
new file mode 100644
index 000000000..ced5833fa
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlan_mapping/vlan_mapping.py
@@ -0,0 +1,64 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the sonic_vlan_mapping module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Vlan_mappingArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_vlan_mapping module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'mapping': {
+ 'elements': 'dict',
+ 'options': {
+ 'dot1q_tunnel': {'type': 'bool', 'default': False},
+ 'inner_vlan': {'type': 'int'},
+ 'priority': {'type': 'int'},
+ 'service_vlan': {'required': True, 'type': 'int'},
+ 'vlan_ids': {'elements': 'str', 'type': 'list'}
+ },
+ 'type': 'list'
+ },
+ 'name': {'required': True, 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlans/vlans.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlans/vlans.py
index 971fc8571..7ae8b5a47 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlans/vlans.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlans/vlans.py
@@ -47,7 +47,7 @@ class VlansArgs(object): # pylint: disable=R0903
'type': 'list'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'replaced', 'overridden', 'deleted'],
'default': 'merged',
'type': 'str'
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vrfs/vrfs.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vrfs/vrfs.py
index e074936a7..992906044 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vrfs/vrfs.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vrfs/vrfs.py
@@ -59,7 +59,7 @@ class VrfsArgs(object): # pylint: disable=R0903
"type": "list"
},
"state": {
- "choices": ["merged", "deleted"],
+ "choices": ["merged", "replaced", "overridden", "deleted"],
"default": "merged",
"type": "str"
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vxlans/vxlans.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vxlans/vxlans.py
index dd475b78a..e610eaca8 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vxlans/vxlans.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vxlans/vxlans.py
@@ -62,11 +62,10 @@ class VxlansArgs(object): # pylint: disable=R0903
'type': 'list'
}
},
- 'required_together': [['source_ip', 'evpn_nvo']],
'type': 'list'
},
'state': {
- 'choices': ['merged', 'deleted'],
+ 'choices': ['merged', 'deleted', 'replaced', 'overridden'],
'default': 'merged',
'type': 'str'
}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/aaa/aaa.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/aaa/aaa.py
index 85f93bc73..036567f0a 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/aaa/aaa.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/aaa/aaa.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2021 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -31,6 +31,10 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
to_request,
edit_config
)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ get_new_config,
+ get_formatted_config_diff
+)
PATCH = 'patch'
DELETE = 'delete'
@@ -89,6 +93,16 @@ class Aaa(ConfigBase):
if result['changed']:
result['after'] = changed_aaa_facts
+ new_config = changed_aaa_facts
+ old_config = existing_aaa_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_aaa_facts)
+ result['after(generated)'] = new_config
+ if self._module._diff:
+ result['diff'] = get_formatted_config_diff(old_config,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
@@ -123,15 +137,22 @@ class Aaa(ConfigBase):
state = self._module.params['state']
if not want:
want = {}
+ if not have:
+ have = {}
+
+ diff = self.get_diff_aaa(want, have)
if state == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'merged':
- diff = get_diff(want, have)
- commands = self._state_merged(want, have, diff)
+ commands = self._state_merged(diff)
+ elif state == 'replaced':
+ commands = self._state_replaced(diff)
+ elif state == 'overridden':
+ commands = self._state_overridden(want, have)
return commands
- def _state_merged(self, want, have, diff):
+ def _state_merged(self, diff):
""" The command generator when state is merged
:rtype: A list
@@ -171,6 +192,49 @@ class Aaa(ConfigBase):
commands = update_states(diff_want, "deleted")
return commands, requests
+ def _state_replaced(self, diff):
+ """ The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = []
+ requests = []
+ if diff:
+ requests = self.get_create_aaa_request(diff)
+ if len(requests) > 0:
+ commands = update_states(diff, "replaced")
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ if have and have != want:
+ del_requests = self.get_delete_all_aaa_request(have)
+ requests.extend(del_requests)
+ commands.extend(update_states(have, "deleted"))
+ have = []
+
+ if not have and want:
+ mod_commands = want
+ mod_requests = self.get_create_aaa_request(mod_commands)
+
+ if len(mod_requests) > 0:
+ requests.extend(mod_requests)
+ commands.extend(update_states(mod_commands, "overridden"))
+
+ return commands, requests
+
def get_create_aaa_request(self, commands):
requests = []
aaa_path = 'data/openconfig-system:system/aaa'
@@ -183,13 +247,17 @@ class Aaa(ConfigBase):
def build_create_aaa_payload(self, commands):
payload = {}
+ auth_method_list = []
if "authentication" in commands and commands["authentication"]:
- payload = {"openconfig-system:aaa": {"authentication": {"config": {"authentication-method": []}}}}
+ payload = {"openconfig-system:aaa": {"authentication": {"config": {}}}}
if "local" in commands["authentication"]["data"] and commands["authentication"]["data"]["local"]:
- payload['openconfig-system:aaa']['authentication']['config']['authentication-method'].append("local")
+ auth_method_list.append('local')
if "group" in commands["authentication"]["data"] and commands["authentication"]["data"]["group"]:
auth_method = commands["authentication"]["data"]["group"]
- payload['openconfig-system:aaa']['authentication']['config']['authentication-method'].append(auth_method)
+ auth_method_list.append(auth_method)
+ if auth_method_list:
+ cfg = {'authentication-method': auth_method_list}
+ payload['openconfig-system:aaa']['authentication']['config'].update(cfg)
if "fail_through" in commands["authentication"]["data"]:
cfg = {'failthrough': str(commands["authentication"]["data"]["fail_through"])}
payload['openconfig-system:aaa']['authentication']['config'].update(cfg)
@@ -234,3 +302,53 @@ class Aaa(ConfigBase):
method = DELETE
request = {'path': path, 'method': method}
return request
+
+ # Current SONiC code behavior for patch overwrites the OC authentication-method leaf-list
+ # This function serves as a workaround for the issue, allowing the user to append to the
+ # OC authentication-method leaf-list.
+ def get_diff_aaa(self, want, have):
+ diff_cfg = {}
+ diff_authentication = {}
+ diff_data = {}
+
+ authentication = want.get('authentication', None)
+ if authentication:
+ data = authentication.get('data', None)
+ if data:
+ fail_through = data.get('fail_through', None)
+ local = data.get('local', None)
+ group = data.get('group', None)
+
+ cfg_authentication = have.get('authentication', None)
+ if cfg_authentication:
+ cfg_data = cfg_authentication.get('data', None)
+ if cfg_data:
+ cfg_fail_through = cfg_data.get('fail_through', None)
+ cfg_local = cfg_data.get('local', None)
+ cfg_group = cfg_data.get('group', None)
+
+ if fail_through is not None and fail_through != cfg_fail_through:
+ diff_data['fail_through'] = fail_through
+ if local and local != cfg_local:
+ diff_data['local'] = local
+ if group and group != cfg_group:
+ diff_data['group'] = group
+
+ diff_local = diff_data.get('local', None)
+ diff_group = diff_data.get('group', None)
+ if diff_local and not diff_group and cfg_group:
+ diff_data['group'] = cfg_group
+ if diff_group and not diff_local and cfg_local:
+ diff_data['local'] = cfg_local
+ else:
+ if fail_through is not None:
+ diff_data['fail_through'] = fail_through
+ if local:
+ diff_data['local'] = local
+ if group:
+ diff_data['group'] = group
+ if diff_data:
+ diff_authentication['data'] = diff_data
+ diff_cfg['authentication'] = diff_authentication
+
+ return diff_cfg
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/acl_interfaces/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/acl_interfaces/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/acl_interfaces/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/acl_interfaces/acl_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/acl_interfaces/acl_interfaces.py
new file mode 100644
index 000000000..0413a585d
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/acl_interfaces/acl_interfaces.py
@@ -0,0 +1,499 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_acl_interfaces class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+ remove_empties,
+ validate_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_diff,
+ update_states,
+ normalize_interface_name
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ get_new_config,
+ get_formatted_config_diff
+)
+from ansible.module_utils.connection import ConnectionError
+
+DELETE = 'delete'
+POST = 'post'
+
+TEST_KEYS = [
+ {'config': {'name': ''}},
+ {'access_groups': {'type': ''}},
+ {'acls': {'name': ''}}
+]
+
+TEST_KEYS_formatted_diff = [
+ {'config': {'name': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+ {'access_groups': {'type': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+ {'acls': {'name': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+]
+
+acl_type_to_payload_map = {
+ 'mac': 'ACL_L2',
+ 'ipv4': 'ACL_IPV4',
+ 'ipv6': 'ACL_IPV6'
+}
+
+
+class Acl_interfaces(ConfigBase):
+ """
+ The sonic_acl_interfaces class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'acl_interfaces',
+ ]
+
+ acl_interfaces_path = 'data/openconfig-acl:acl/interfaces/interface={intf_name}'
+ ingress_acl_set_path = acl_interfaces_path + '/ingress-acl-sets/ingress-acl-set={acl_name},{acl_type}'
+ egress_acl_set_path = acl_interfaces_path + '/egress-acl-sets/egress-acl-set={acl_name},{acl_type}'
+
+ def __init__(self, module):
+ super(Acl_interfaces, self).__init__(module)
+
+ def get_acl_interfaces_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ acl_interfaces_facts = facts['ansible_network_resources'].get('acl_interfaces')
+ if not acl_interfaces_facts:
+ return []
+ return acl_interfaces_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = []
+
+ existing_acl_interfaces_facts = self.get_acl_interfaces_facts()
+ commands, requests = self.set_config(existing_acl_interfaces_facts)
+ if commands:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+
+ changed_acl_interfaces_facts = self.get_acl_interfaces_facts()
+
+ result['before'] = existing_acl_interfaces_facts
+ if result['changed']:
+ result['after'] = changed_acl_interfaces_facts
+
+ result['commands'] = commands
+
+ new_config = changed_acl_interfaces_facts
+ old_config = existing_acl_interfaces_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_acl_interfaces_facts,
+ TEST_KEYS_formatted_diff)
+ self.post_process_generated_config(new_config)
+ result['after(generated)'] = new_config
+ if self._module._diff:
+ self.sort_config(new_config)
+ self.sort_config(old_config)
+ result['diff'] = get_formatted_config_diff(old_config,
+ new_config,
+ self._module._verbosity)
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_acl_interfaces_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ if want:
+ want = self.validate_and_normalize_config(want)
+ else:
+ want = []
+
+ have = existing_acl_interfaces_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params['state']
+ if state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'merged':
+ commands, requests = self._state_merged(want, have)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have)
+
+ return commands, requests
+
+ def _state_replaced(self, want, have):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ del_commands = []
+ add_commands = []
+
+ have_interfaces = self.get_interface_names(have)
+ want_interfaces = self.get_interface_names(want)
+ interfaces_to_replace = have_interfaces.intersection(want_interfaces)
+
+ del_diff = get_diff(have, want, TEST_KEYS)
+ for cmd in del_diff:
+ if cmd['name'] in interfaces_to_replace:
+ del_commands.append(cmd)
+
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+ requests.extend(self.get_interfaces_acl_unbind_requests(del_commands))
+
+ add_diff = get_diff(want, have, TEST_KEYS)
+ # Handle scenarios in replaced state, when only the interface
+ # name is specified for deleting all ACL bindings in it.
+ for cmd in add_diff:
+ if cmd.get('access_groups'):
+ add_commands.append(cmd)
+
+ if add_commands:
+ commands.extend(update_states(add_commands, 'replaced'))
+ requests.extend(self.get_interfaces_acl_bind_requests(add_commands))
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ del_commands = []
+
+ have_interfaces = self.get_interface_names(have)
+ want_interfaces = self.get_interface_names(want)
+ interfaces_to_delete = have_interfaces.difference(want_interfaces)
+ interfaces_to_override = have_interfaces.intersection(want_interfaces)
+
+ del_diff = get_diff(have, want, TEST_KEYS)
+ for cmd in del_diff:
+ if cmd['name'] in interfaces_to_delete:
+ del_commands.append({'name': cmd['name']})
+ elif cmd['name'] in interfaces_to_override:
+ del_commands.append(cmd)
+
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+ requests.extend(self.get_interfaces_acl_unbind_requests(del_commands))
+
+ diff = get_diff(want, have, TEST_KEYS)
+ if diff:
+ commands.extend(update_states(diff, 'overridden'))
+ requests.extend(self.get_interfaces_acl_bind_requests(diff))
+
+ return commands, requests
+
+ def _state_merged(self, want, have):
+ """ The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = []
+ requests = []
+
+ diff = get_diff(want, have, TEST_KEYS)
+ if diff:
+ requests = self.get_interfaces_acl_bind_requests(diff)
+ commands = update_states(diff, 'merged')
+
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ requests = []
+
+ if not want:
+ # Delete all interface ACL bindings in the chassis
+ for cfg in have:
+ commands.append({'name': cfg['name']})
+ else:
+ want_dict = self._convert_config_list_to_dict(want)
+ have_dict = self._convert_config_list_to_dict(have)
+
+ for intf_name, access_groups in want_dict.items():
+ have_obj = have_dict.get(intf_name)
+ if not have_obj:
+ continue
+
+ if not access_groups:
+ commands.append({'name': intf_name})
+ else:
+ access_groups_to_del = []
+ for acl_type, acls in access_groups.items():
+ acls_to_delete = []
+ if not have_obj.get(acl_type):
+ continue
+
+ # Delete all bindings of ACLs belonging to a type in an
+ # interface, if only the ACL type is provided
+ if not acls:
+ for acl_name, direction in have_obj[acl_type].items():
+ acls_to_delete.append({'name': acl_name, 'direction': direction})
+ else:
+ for acl_name, direction in acls.items():
+ if have_obj[acl_type].get(acl_name) and direction == have_obj[acl_type][acl_name]:
+ acls_to_delete.append({'name': acl_name, 'direction': direction})
+
+ if acls_to_delete:
+ access_groups_to_del.append({'type': acl_type, 'acls': acls_to_delete})
+
+ if access_groups_to_del:
+ commands.append({'name': intf_name, 'access_groups': access_groups_to_del})
+
+ if commands:
+ requests = self.get_interfaces_acl_unbind_requests(commands)
+ commands = update_states(commands, 'deleted')
+
+ return commands, requests
+
+ def get_interfaces_acl_bind_requests(self, commands):
+ """Get requests to bind specified ACLs for all interfaces
+ specified the commands
+ """
+ requests = []
+
+ for command in commands:
+ intf_name = command['name']
+ url = self.acl_interfaces_path.format(intf_name=intf_name)
+ for access_group in command['access_groups']:
+ for acl in access_group['acls']:
+ if acl['direction'] == 'in':
+ payload = {
+ 'openconfig-acl:config': {
+ 'id': intf_name
+ },
+ 'openconfig-acl:interface-ref': {
+ 'config': {
+ 'interface': intf_name.split('.')[0]
+ }
+ },
+ 'openconfig-acl:ingress-acl-sets': {
+ 'ingress-acl-set': [
+ {
+ 'set-name': acl['name'],
+ 'type': acl_type_to_payload_map[access_group['type']],
+ 'config': {
+ 'set-name': acl['name'],
+ 'type': acl_type_to_payload_map[access_group['type']]
+ }
+ }
+ ]
+ }
+ }
+ else:
+ payload = {
+ 'openconfig-acl:config': {
+ 'id': intf_name
+ },
+ 'openconfig-acl:interface-ref': {
+ 'config': {
+ 'interface': intf_name.split('.')[0]
+ }
+ },
+ 'openconfig-acl:egress-acl-sets': {
+ 'egress-acl-set': [
+ {
+ 'set-name': acl['name'],
+ 'type': acl_type_to_payload_map[access_group['type']],
+ 'config': {
+ 'set-name': acl['name'],
+ 'type': acl_type_to_payload_map[access_group['type']]
+ }
+ }
+ ]
+ }
+ }
+
+ # Update the payload for subinterfaces
+ if '.' in intf_name:
+ payload['openconfig-acl:interface-ref']['config']['subinterface'] = int(intf_name.split('.')[1])
+
+ requests.append({'path': url, 'method': POST, 'data': payload})
+
+ return requests
+
+ def get_interfaces_acl_unbind_requests(self, commands):
+ """Get requests to unbind specified ACLs for all interfaces
+ specified in the commands
+ """
+ requests = []
+
+ for command in commands:
+ intf_name = command['name']
+ # Delete all acl bindings in an interface, if only the
+ # interface name is provided
+ if not command.get('access_groups'):
+ url = self.acl_interfaces_path.format(intf_name=intf_name)
+ requests.append({'path': url, 'method': DELETE})
+ else:
+ for access_group in command['access_groups']:
+ for acl in access_group['acls']:
+ if acl['direction'] == 'in':
+ url = self.ingress_acl_set_path.format(intf_name=intf_name, acl_name=acl['name'],
+ acl_type=acl_type_to_payload_map[access_group['type']])
+ requests.append({'path': url, 'method': DELETE})
+ else:
+ url = self.egress_acl_set_path.format(intf_name=intf_name, acl_name=acl['name'],
+ acl_type=acl_type_to_payload_map[access_group['type']])
+ requests.append({'path': url, 'method': DELETE})
+
+ return requests
+
+ def validate_and_normalize_config(self, config_list):
+ """Validate and normalize the given config"""
+ # Remove empties and validate the config with argument spec
+ config_list = [remove_empties(config) for config in config_list]
+ validate_config(self._module.argument_spec, {'config': config_list})
+ normalize_interface_name(config_list, self._module)
+
+ state = self._module.params['state']
+ # When state is deleted, empty access_groups and acls are
+ # supported and therefore no futher changes are required.
+ if state == 'deleted':
+ return config_list
+
+ updated_config_list = []
+ for config in config_list:
+ if not config.get('access_groups'):
+ # When state is replaced, if only the interface name is
+ # specified for deleting all ACL bindings in it do not
+ # remove that config.
+ if state == 'replaced':
+ updated_config_list.append(config)
+ else:
+ access_group_list = []
+ for access_group in config['access_groups']:
+ if access_group.get('acls'):
+ access_group_list.append(access_group)
+
+ if access_group_list:
+ updated_config_list.append({'name': config['name'], 'access_groups': access_group_list})
+
+ return updated_config_list
+
+ @staticmethod
+ def get_interface_names(config_list):
+ """Get a set of interface names available in the given
+ config_list dict
+ """
+ interface_names = set()
+ for config in config_list:
+ interface_names.add(config['name'])
+
+ return interface_names
+
+ @staticmethod
+ def _convert_config_list_to_dict(config_list):
+ config_dict = {}
+
+ for config in config_list:
+ config_dict[config['name']] = {}
+ if config.get('access_groups'):
+ for access_group in config['access_groups']:
+ config_dict[config['name']][access_group['type']] = {}
+ if access_group.get('acls'):
+ for acl in access_group['acls']:
+ config_dict[config['name']][access_group['type']][acl['name']] = acl['direction']
+
+ return config_dict
+
+ def sort_config(self, configs):
+ # natsort provides better result.
+ # The use of natsort causes sanity error due to it is not available in
+ # python version currently used.
+ # new_config = natsorted(new_config, key=lambda x: x['name'])
+ # For time-being, use simple "sort"
+ configs.sort(key=lambda x: x['name'])
+
+ for conf in configs:
+ ags = conf.get('access_groups', [])
+ if ags:
+ ags.sort(key=lambda x: x['type'])
+ for ag in ags:
+ if ag.get('acls', []):
+ ag['acls'].sort(key=lambda x: x['name'])
+
+ def post_process_generated_config(self, configs):
+ for conf in configs[:]:
+ ags = conf.get('access_groups', [])
+ if ags:
+ for ag in ags[:]:
+ if not ag.get('acls', []):
+ ags.remove(ag)
+
+ if not conf.get('access_groups', []):
+ configs.remove(conf)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bfd/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bfd/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bfd/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bfd/bfd.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bfd/bfd.py
new file mode 100644
index 000000000..484c4203d
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bfd/bfd.py
@@ -0,0 +1,734 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_bfd class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_diff,
+ get_replaced_config,
+ send_requests,
+ remove_empties,
+ update_states
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+
+from copy import deepcopy
+
+
+BFD_PATH = '/data/openconfig-bfd:bfd'
+PATCH = 'patch'
+DELETE = 'delete'
+TEST_KEYS = [
+ {'profiles': {'profile_name': ''}},
+ {'single_hops': {'remote_address': '', 'vrf': '', 'interface': '', 'local_address': ''}},
+ {'multi_hops': {'remote_address': '', 'vrf': '', 'local_address': ''}}
+]
+
+
+class Bfd(ConfigBase):
+ """
+ The sonic_bfd class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'bfd',
+ ]
+
+ def __init__(self, module):
+ super(Bfd, self).__init__(module)
+
+ def get_bfd_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ bfd_facts = facts['ansible_network_resources'].get('bfd')
+ if not bfd_facts:
+ return {}
+ return bfd_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = []
+ commands = []
+
+ existing_bfd_facts = self.get_bfd_facts()
+ commands, requests = self.set_config(existing_bfd_facts)
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_bfd_facts = self.get_bfd_facts()
+
+ result['before'] = existing_bfd_facts
+ if result['changed']:
+ result['after'] = changed_bfd_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_bfd_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ have = existing_bfd_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ state = self._module.params['state']
+ diff = get_diff(want, have, TEST_KEYS)
+
+ if state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'merged':
+ commands, requests = self._state_merged(diff)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have, diff)
+ return commands, requests
+
+ def _state_replaced(self, want, have, diff):
+ """ The command generator when state is replaced
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ replaced_config = get_replaced_config(want, have, TEST_KEYS)
+
+ if replaced_config:
+ self.sort_lists_in_config(replaced_config)
+ self.sort_lists_in_config(have)
+ is_delete_all = (replaced_config == have)
+ requests = self.get_delete_bfd_requests(replaced_config, have, is_delete_all)
+ send_requests(self._module, requests)
+
+ commands = want
+ else:
+ commands = diff
+
+ requests = []
+
+ if commands:
+ requests = self.get_modify_bfd_request(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "replaced")
+ else:
+ commands = []
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ self.sort_lists_in_config(want)
+ self.sort_lists_in_config(have)
+
+ if have and have != want:
+ is_delete_all = True
+ requests = self.get_delete_bfd_requests(have, None, is_delete_all)
+ send_requests(self._module, requests)
+ have = []
+
+ commands = []
+ requests = []
+
+ if not have and want:
+ commands = want
+ requests = self.get_modify_bfd_request(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "overridden")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_merged(self, diff):
+ """ The command generator when state is merged
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = diff
+ requests = self.get_modify_bfd_request(commands)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+ :param want: the objects from which the configuration should be removed
+ :param obj_in_have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ is_delete_all = False
+ want = remove_empties(want)
+ if not want:
+ commands = deepcopy(have)
+ is_delete_all = True
+ else:
+ commands = deepcopy(want)
+
+ self.remove_default_entries(commands)
+ requests = self.get_delete_bfd_requests(commands, have, is_delete_all)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+ return commands, requests
+
+ def get_modify_bfd_request(self, commands):
+ request = None
+
+ profiles = commands.get('profiles', None)
+ single_hops = commands.get('single_hops', None)
+ multi_hops = commands.get('multi_hops', None)
+ bfd_dict = {}
+ bfd_profile_dict = {}
+ bfd_shop_dict = {}
+ bfd_mhop_dict = {}
+
+ if profiles:
+ profile_list = []
+ for profile in profiles:
+ profile_dict = {}
+ config_dict = {}
+ profile_name = profile.get('profile_name', None)
+ enabled = profile.get('enabled', None)
+ transmit_interval = profile.get('transmit_interval', None)
+ receive_interval = profile.get('receive_interval', None)
+ detect_multiplier = profile.get('detect_multiplier', None)
+ passive_mode = profile.get('passive_mode', None)
+ min_ttl = profile.get('min_ttl', None)
+ echo_interval = profile.get('echo_interval', None)
+ echo_mode = profile.get('echo_mode', None)
+
+ if profile_name:
+ profile_dict['profile-name'] = profile_name
+ config_dict['profile-name'] = profile_name
+ if enabled is not None:
+ config_dict['enabled'] = enabled
+ if transmit_interval:
+ config_dict['desired-minimum-tx-interval'] = transmit_interval
+ if receive_interval:
+ config_dict['required-minimum-receive'] = receive_interval
+ if detect_multiplier:
+ config_dict['detection-multiplier'] = detect_multiplier
+ if passive_mode is not None:
+ config_dict['passive-mode'] = passive_mode
+ if min_ttl:
+ config_dict['minimum-ttl'] = min_ttl
+ if echo_interval:
+ config_dict['desired-minimum-echo-receive'] = echo_interval
+ if echo_mode is not None:
+ config_dict['echo-active'] = echo_mode
+ if config_dict:
+ profile_dict['config'] = config_dict
+ profile_list.append(profile_dict)
+ if profile_list:
+ bfd_profile_dict['profile'] = profile_list
+
+ if single_hops:
+ single_hop_list = []
+ for hop in single_hops:
+ hop_dict = {}
+ config_dict = {}
+ remote_address = hop.get('remote_address', None)
+ vrf = hop.get('vrf', None)
+ interface = hop.get('interface', None)
+ local_address = hop.get('local_address', None)
+ enabled = hop.get('enabled', None)
+ transmit_interval = hop.get('transmit_interval', None)
+ receive_interval = hop.get('receive_interval', None)
+ detect_multiplier = hop.get('detect_multiplier', None)
+ passive_mode = hop.get('passive_mode', None)
+ echo_interval = hop.get('echo_interval', None)
+ echo_mode = hop.get('echo_mode', None)
+ profile_name = hop.get('profile_name', None)
+
+ if remote_address:
+ hop_dict['remote-address'] = remote_address
+ config_dict['remote-address'] = remote_address
+ if vrf:
+ hop_dict['vrf'] = vrf
+ config_dict['vrf'] = vrf
+ if interface:
+ hop_dict['interface'] = interface
+ config_dict['interface'] = interface
+ if local_address:
+ hop_dict['local-address'] = local_address
+ config_dict['local-address'] = local_address
+ if enabled is not None:
+ config_dict['enabled'] = enabled
+ if transmit_interval:
+ config_dict['desired-minimum-tx-interval'] = transmit_interval
+ if receive_interval:
+ config_dict['required-minimum-receive'] = receive_interval
+ if detect_multiplier:
+ config_dict['detection-multiplier'] = detect_multiplier
+ if passive_mode is not None:
+ config_dict['passive-mode'] = passive_mode
+ if echo_interval:
+ config_dict['desired-minimum-echo-receive'] = echo_interval
+ if echo_mode is not None:
+ config_dict['echo-active'] = echo_mode
+ if profile_name:
+ config_dict['profile-name'] = profile_name
+ if config_dict:
+ hop_dict['config'] = config_dict
+ single_hop_list.append(hop_dict)
+ if single_hop_list:
+ bfd_shop_dict['single-hop'] = single_hop_list
+
+ if multi_hops:
+ multi_hop_list = []
+ for hop in multi_hops:
+ hop_dict = {}
+ config_dict = {}
+ remote_address = hop.get('remote_address', None)
+ vrf = hop.get('vrf', None)
+ local_address = hop.get('local_address', None)
+ enabled = hop.get('enabled', None)
+ transmit_interval = hop.get('transmit_interval', None)
+ receive_interval = hop.get('receive_interval', None)
+ detect_multiplier = hop.get('detect_multiplier', None)
+ passive_mode = hop.get('passive_mode', None)
+ min_ttl = hop.get('min_ttl', None)
+ profile_name = hop.get('profile_name', None)
+
+ if remote_address:
+ hop_dict['remote-address'] = remote_address
+ config_dict['remote-address'] = remote_address
+ if vrf:
+ hop_dict['vrf'] = vrf
+ config_dict['vrf'] = vrf
+ if local_address:
+ hop_dict['local-address'] = local_address
+ config_dict['local-address'] = local_address
+ if enabled is not None:
+ config_dict['enabled'] = enabled
+ if transmit_interval:
+ config_dict['desired-minimum-tx-interval'] = transmit_interval
+ if receive_interval:
+ config_dict['required-minimum-receive'] = receive_interval
+ if detect_multiplier:
+ config_dict['detection-multiplier'] = detect_multiplier
+ if passive_mode is not None:
+ config_dict['passive-mode'] = passive_mode
+ if min_ttl:
+ config_dict['minimum-ttl'] = min_ttl
+ if profile_name:
+ config_dict['profile-name'] = profile_name
+ if config_dict:
+ config_dict['interface'] = 'null'
+ hop_dict['interface'] = 'null'
+ hop_dict['config'] = config_dict
+ multi_hop_list.append(hop_dict)
+ if multi_hop_list:
+ bfd_mhop_dict['multi-hop'] = multi_hop_list
+
+ if bfd_profile_dict:
+ bfd_dict['openconfig-bfd-ext:bfd-profile'] = bfd_profile_dict
+ if bfd_shop_dict:
+ bfd_dict['openconfig-bfd-ext:bfd-shop-sessions'] = bfd_shop_dict
+ if bfd_mhop_dict:
+ bfd_dict['openconfig-bfd-ext:bfd-mhop-sessions'] = bfd_mhop_dict
+ if bfd_dict:
+ payload = {'openconfig-bfd:bfd': bfd_dict}
+ request = {'path': BFD_PATH, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_delete_bfd_requests(self, commands, have, is_delete_all):
+ requests = []
+
+ if not commands:
+ return requests
+
+ if is_delete_all:
+ requests.extend(self.get_delete_all_bfd_cfg_requests(commands))
+ else:
+ requests.extend(self.get_delete_bfd_profile_requests(commands, have))
+ requests.extend(self.get_delete_bfd_shop_requests(commands, have))
+ requests.extend(self.get_delete_bfd_mhop_requests(commands, have))
+
+ return requests
+
+ def get_delete_bfd_profile_requests(self, commands, have):
+ requests = []
+
+ profiles = commands.get('profiles', None)
+ if profiles:
+ for profile in profiles:
+ profile_name = profile.get('profile_name', None)
+ enabled = profile.get('enabled', None)
+ transmit_interval = profile.get('transmit_interval', None)
+ receive_interval = profile.get('receive_interval', None)
+ detect_multiplier = profile.get('detect_multiplier', None)
+ passive_mode = profile.get('passive_mode', None)
+ min_ttl = profile.get('min_ttl', None)
+ echo_interval = profile.get('echo_interval', None)
+ echo_mode = profile.get('echo_mode', None)
+
+ cfg_profiles = have.get('profiles', None)
+ if cfg_profiles:
+ for cfg_profile in cfg_profiles:
+ cfg_profile_name = cfg_profile.get('profile_name', None)
+ cfg_enabled = cfg_profile.get('enabled', None)
+ cfg_transmit_interval = cfg_profile.get('transmit_interval', None)
+ cfg_receive_interval = cfg_profile.get('receive_interval', None)
+ cfg_detect_multiplier = cfg_profile.get('detect_multiplier', None)
+ cfg_passive_mode = cfg_profile.get('passive_mode', None)
+ cfg_min_ttl = cfg_profile.get('min_ttl', None)
+ cfg_echo_interval = cfg_profile.get('echo_interval', None)
+ cfg_echo_mode = cfg_profile.get('echo_mode', None)
+
+ if profile_name == cfg_profile_name:
+ if enabled is not None and enabled == cfg_enabled:
+ requests.append(self.get_delete_profile_attr_request(profile_name, 'enabled'))
+ if transmit_interval and transmit_interval == cfg_transmit_interval:
+ requests.append(self.get_delete_profile_attr_request(profile_name, 'desired-minimum-tx-interval'))
+ if receive_interval and receive_interval == cfg_receive_interval:
+ requests.append(self.get_delete_profile_attr_request(profile_name, 'required-minimum-receive'))
+ if detect_multiplier and detect_multiplier == cfg_detect_multiplier:
+ requests.append(self.get_delete_profile_attr_request(profile_name, 'detection-multiplier'))
+ if passive_mode is not None and passive_mode == cfg_passive_mode:
+ requests.append(self.get_delete_profile_attr_request(profile_name, 'passive-mode'))
+ if min_ttl and min_ttl == cfg_min_ttl:
+ requests.append(self.get_delete_profile_attr_request(profile_name, 'minimum-ttl'))
+ if echo_interval and echo_interval == cfg_echo_interval:
+ requests.append(self.get_delete_profile_attr_request(profile_name, 'desired-minimum-echo-receive'))
+ if echo_mode is not None and echo_mode == cfg_echo_mode:
+ requests.append(self.get_delete_profile_attr_request(profile_name, 'echo-active'))
+ if (enabled is None and not transmit_interval and not receive_interval and not detect_multiplier and passive_mode is None
+ and not min_ttl and not echo_interval and echo_mode is None):
+ requests.append(self.get_delete_profile_request(profile_name))
+
+ return requests
+
+ def get_delete_bfd_shop_requests(self, commands, have):
+ requests = []
+
+ single_hops = commands.get('single_hops', None)
+ if single_hops:
+ for hop in single_hops:
+ remote_address = hop.get('remote_address', None)
+ vrf = hop.get('vrf', None)
+ interface = hop.get('interface', None)
+ local_address = hop.get('local_address', None)
+ enabled = hop.get('enabled', None)
+ transmit_interval = hop.get('transmit_interval', None)
+ receive_interval = hop.get('receive_interval', None)
+ detect_multiplier = hop.get('detect_multiplier', None)
+ passive_mode = hop.get('passive_mode', None)
+ echo_interval = hop.get('echo_interval', None)
+ echo_mode = hop.get('echo_mode', None)
+ profile_name = hop.get('profile_name', None)
+
+ cfg_single_hops = have.get('single_hops', None)
+ if cfg_single_hops:
+ for cfg_hop in cfg_single_hops:
+ cfg_remote_address = cfg_hop.get('remote_address', None)
+ cfg_vrf = cfg_hop.get('vrf', None)
+ cfg_interface = cfg_hop.get('interface', None)
+ cfg_local_address = cfg_hop.get('local_address', None)
+ cfg_enabled = cfg_hop.get('enabled', None)
+ cfg_transmit_interval = cfg_hop.get('transmit_interval', None)
+ cfg_receive_interval = cfg_hop.get('receive_interval', None)
+ cfg_detect_multiplier = cfg_hop.get('detect_multiplier', None)
+ cfg_passive_mode = cfg_hop.get('passive_mode', None)
+ cfg_echo_interval = cfg_hop.get('echo_interval', None)
+ cfg_echo_mode = cfg_hop.get('echo_mode', None)
+ cfg_profile_name = cfg_hop.get('profile_name', None)
+
+ if remote_address == cfg_remote_address and vrf == cfg_vrf and interface == cfg_interface and local_address == cfg_local_address:
+ if enabled is not None and enabled == cfg_enabled:
+ requests.append(self.get_delete_shop_attr_request(remote_address, interface, vrf, local_address, 'enabled'))
+ if transmit_interval and transmit_interval == cfg_transmit_interval:
+ requests.append(self.get_delete_shop_attr_request(remote_address, interface, vrf, local_address,
+ 'desired-minimum-tx-interval'))
+ if receive_interval and receive_interval == cfg_receive_interval:
+ requests.append(self.get_delete_shop_attr_request(remote_address, interface, vrf, local_address, 'required-minimum-receive'))
+ if detect_multiplier and detect_multiplier == cfg_detect_multiplier:
+ requests.append(self.get_delete_shop_attr_request(remote_address, interface, vrf, local_address, 'detection-multiplier'))
+ if passive_mode is not None and passive_mode == cfg_passive_mode:
+ requests.append(self.get_delete_shop_attr_request(remote_address, interface, vrf, local_address, 'passive-mode'))
+ if echo_interval and echo_interval == cfg_echo_interval:
+ requests.append(self.get_delete_shop_attr_request(remote_address, interface, vrf, local_address,
+ 'desired-minimum-echo-receive'))
+ if echo_mode is not None and echo_mode == cfg_echo_mode:
+ requests.append(self.get_delete_shop_attr_request(remote_address, interface, vrf, local_address, 'echo-active'))
+ if profile_name and profile_name == cfg_profile_name:
+ requests.append(self.get_delete_shop_attr_request(remote_address, interface, vrf, local_address, 'profile-name'))
+ if (enabled is None and not transmit_interval and not receive_interval and not detect_multiplier and passive_mode is None
+ and not echo_interval and echo_mode is None and not profile_name):
+ requests.append(self.get_delete_shop_request(remote_address, interface, vrf, local_address))
+
+ return requests
+
+ def get_delete_bfd_mhop_requests(self, commands, have):
+ requests = []
+
+ multi_hops = commands.get('multi_hops', None)
+ if multi_hops:
+ for hop in multi_hops:
+ remote_address = hop.get('remote_address', None)
+ vrf = hop.get('vrf', None)
+ local_address = hop.get('local_address', None)
+ enabled = hop.get('enabled', None)
+ transmit_interval = hop.get('transmit_interval', None)
+ receive_interval = hop.get('receive_interval', None)
+ detect_multiplier = hop.get('detect_multiplier', None)
+ passive_mode = hop.get('passive_mode', None)
+ min_ttl = hop.get('min_ttl', None)
+ profile_name = hop.get('profile_name', None)
+
+ cfg_multi_hops = have.get('multi_hops', None)
+ if cfg_multi_hops:
+ for cfg_hop in cfg_multi_hops:
+ cfg_remote_address = cfg_hop.get('remote_address', None)
+ cfg_vrf = cfg_hop.get('vrf', None)
+ cfg_local_address = cfg_hop.get('local_address', None)
+ cfg_enabled = cfg_hop.get('enabled', None)
+ cfg_transmit_interval = cfg_hop.get('transmit_interval', None)
+ cfg_receive_interval = cfg_hop.get('receive_interval', None)
+ cfg_detect_multiplier = cfg_hop.get('detect_multiplier', None)
+ cfg_passive_mode = cfg_hop.get('passive_mode', None)
+ cfg_min_ttl = cfg_hop.get('min_ttl', None)
+ cfg_profile_name = cfg_hop.get('profile_name', None)
+
+ if remote_address == cfg_remote_address and vrf == cfg_vrf and local_address == cfg_local_address:
+ if enabled is not None and enabled == cfg_enabled:
+ requests.append(self.get_delete_mhop_attr_request(remote_address, vrf, local_address, 'enabled'))
+ if transmit_interval and transmit_interval == cfg_transmit_interval:
+ requests.append(self.get_delete_mhop_attr_request(remote_address, vrf, local_address, 'desired-minimum-tx-interval'))
+ if receive_interval and receive_interval == cfg_receive_interval:
+ requests.append(self.get_delete_mhop_attr_request(remote_address, vrf, local_address, 'required-minimum-receive'))
+ if detect_multiplier and detect_multiplier == cfg_detect_multiplier:
+ requests.append(self.get_delete_mhop_attr_request(remote_address, vrf, local_address, 'detection-multiplier'))
+ if passive_mode is not None and passive_mode == cfg_passive_mode:
+ requests.append(self.get_delete_mhop_attr_request(remote_address, vrf, local_address, 'passive-mode'))
+ if min_ttl and min_ttl == cfg_min_ttl:
+ requests.append(self.get_delete_mhop_attr_request(remote_address, vrf, local_address, 'minimum-ttl'))
+ if profile_name and profile_name == cfg_profile_name:
+ requests.append(self.get_delete_mhop_attr_request(remote_address, vrf, local_address, 'profile-name'))
+ if (enabled is None and not transmit_interval and not receive_interval and not detect_multiplier and passive_mode is None
+ and not min_ttl and not profile_name):
+ requests.append(self.get_delete_mhop_request(remote_address, vrf, local_address))
+
+ return requests
+
+ def get_delete_all_bfd_cfg_requests(self, commands):
+ requests = []
+ profiles = commands.get('profiles', None)
+ single_hops = commands.get('single_hops', None)
+ multi_hops = commands.get('multi_hops', None)
+
+ if profiles:
+ url = '%s/openconfig-bfd-ext:bfd-profile/profile' % (BFD_PATH)
+ requests.append({'path': url, 'method': DELETE})
+ if single_hops:
+ url = '%s/openconfig-bfd-ext:bfd-shop-sessions/single-hop' % (BFD_PATH)
+ requests.append({'path': url, 'method': DELETE})
+ if multi_hops:
+ url = '%s/openconfig-bfd-ext:bfd-mhop-sessions/multi-hop' % (BFD_PATH)
+ requests.append({'path': url, 'method': DELETE})
+
+ return requests
+
+ def get_delete_profile_request(self, profile_name):
+ url = '%s/openconfig-bfd-ext:bfd-profile/profile=%s' % (BFD_PATH, profile_name)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_profile_attr_request(self, profile_name, attr):
+ url = '%s/openconfig-bfd-ext:bfd-profile/profile=%s/config/%s' % (BFD_PATH, profile_name, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_shop_request(self, remote_address, interface, vrf, local_address):
+ url = '%s/openconfig-bfd-ext:bfd-shop-sessions/single-hop=%s,%s,%s,%s' % (BFD_PATH, remote_address, interface, vrf, local_address)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_shop_attr_request(self, remote_address, interface, vrf, local_address, attr):
+ url = '%s/openconfig-bfd-ext:bfd-shop-sessions/single-hop=%s,%s,%s,%s/config/%s' % (BFD_PATH, remote_address, interface, vrf, local_address, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_mhop_request(self, remote_address, vrf, local_address):
+ url = '%s/openconfig-bfd-ext:bfd-mhop-sessions/multi-hop=%s,null,%s,%s' % (BFD_PATH, remote_address, vrf, local_address)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_mhop_attr_request(self, remote_address, vrf, local_address, attr):
+ url = '%s/openconfig-bfd-ext:bfd-mhop-sessions/multi-hop=%s,null,%s,%s/config/%s' % (BFD_PATH, remote_address, vrf, local_address, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_profile_name(self, profile_name):
+ return profile_name.get('profile_name')
+
+ def sort_lists_in_config(self, config):
+ if 'profiles' in config and config['profiles'] is not None:
+ config['profiles'].sort(key=self.get_profile_name)
+ if 'single_hops' in config and config['single_hops'] is not None:
+ config['single_hops'].sort(key=lambda x: (x['remote_address'], x['interface'], x['vrf'], x['local_address']))
+ if 'multi_hops' in config and config['multi_hops'] is not None:
+ config['multi_hops'].sort(key=lambda x: (x['remote_address'], x['vrf'], x['local_address']))
+
+ def remove_default_entries(self, data):
+
+ profiles = data.get('profiles', None)
+ single_hops = data.get('single_hops', None)
+ multi_hops = data.get('multi_hops', None)
+
+ if profiles:
+ for profile in profiles:
+ enabled = profile.get('enabled', None)
+ transmit_interval = profile.get('transmit_interval', None)
+ receive_interval = profile.get('receive_interval', None)
+ detect_multiplier = profile.get('detect_multiplier', None)
+ passive_mode = profile.get('passive_mode', None)
+ min_ttl = profile.get('min_ttl', None)
+ echo_interval = profile.get('echo_interval', None)
+ echo_mode = profile.get('echo_mode', None)
+
+ if enabled:
+ profile.pop('enabled')
+ if transmit_interval == 300:
+ profile.pop('transmit_interval')
+ if receive_interval == 300:
+ profile.pop('receive_interval')
+ if detect_multiplier == 3:
+ profile.pop('detect_multiplier')
+ if passive_mode is False:
+ profile.pop('passive_mode')
+ if min_ttl == 254:
+ profile.pop('min_ttl')
+ if echo_interval == 300:
+ profile.pop('echo_interval')
+ if echo_mode is False:
+ profile.pop('echo_mode')
+
+ if single_hops:
+ for hop in single_hops:
+ enabled = hop.get('enabled', None)
+ transmit_interval = hop.get('transmit_interval', None)
+ receive_interval = hop.get('receive_interval', None)
+ detect_multiplier = hop.get('detect_multiplier', None)
+ passive_mode = hop.get('passive_mode', None)
+ echo_interval = hop.get('echo_interval', None)
+ echo_mode = hop.get('echo_mode', None)
+
+ if enabled:
+ hop.pop('enabled')
+ if transmit_interval == 300:
+ hop.pop('transmit_interval')
+ if receive_interval == 300:
+ hop.pop('receive_interval')
+ if detect_multiplier == 3:
+ hop.pop('detect_multiplier')
+ if passive_mode is False:
+ hop.pop('passive_mode')
+ if echo_interval == 300:
+ hop.pop('echo_interval')
+ if echo_mode is False:
+ hop.pop('echo_mode')
+
+ if multi_hops:
+ for hop in multi_hops:
+ enabled = hop.get('enabled', None)
+ transmit_interval = hop.get('transmit_interval', None)
+ receive_interval = hop.get('receive_interval', None)
+ detect_multiplier = hop.get('detect_multiplier', None)
+ passive_mode = hop.get('passive_mode', None)
+ min_ttl = hop.get('min_ttl', None)
+
+ if enabled:
+ hop.pop('enabled')
+ if transmit_interval == 300:
+ hop.pop('transmit_interval')
+ if receive_interval == 300:
+ hop.pop('receive_interval')
+ if detect_multiplier == 3:
+ hop.pop('detect_multiplier')
+ if passive_mode is False:
+ hop.pop('passive_mode')
+ if min_ttl == 254:
+ hop.pop('min_ttl')
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp/bgp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp/bgp.py
index fd4d5c57e..69c7ca455 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp/bgp.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp/bgp.py
@@ -13,18 +13,12 @@ created
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-try:
- from urllib import quote
-except ImportError:
- from urllib.parse import quote
-
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_empties,
to_list,
- search_obj_in_list,
- remove_empties
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
@@ -32,10 +26,8 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
edit_config
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
- dict_to_set,
update_states,
get_diff,
- remove_empties_from_list
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import to_request
from ansible.module_utils.connection import ConnectionError
@@ -120,6 +112,11 @@ class Bgp(ConfigBase):
to the desired configuration
"""
want = self._module.params['config']
+ if want:
+ want = [remove_empties(conf) for conf in want]
+ else:
+ want = []
+
have = existing_bgp_facts
resp = self.set_state(want, have)
return to_list(resp)
@@ -137,19 +134,85 @@ class Bgp(ConfigBase):
requests = []
state = self._module.params['state']
- diff = get_diff(want, have, TEST_KEYS)
-
if state == 'overridden':
- commands, requests = self._state_overridden(want, have, diff)
+ commands, requests = self._state_overridden(want, have)
elif state == 'deleted':
- commands, requests = self._state_deleted(want, have, diff)
+ commands, requests = self._state_deleted(want, have)
elif state == 'merged':
- commands, requests = self._state_merged(want, have, diff)
+ commands, requests = self._state_merged(want, have)
elif state == 'replaced':
- commands, requests = self._state_replaced(want, have, diff)
+ commands, requests = self._state_replaced(want, have)
return commands, requests
- def _state_merged(self, want, have, diff):
+ def _state_replaced(self, want, have):
+ """ The command generator when state is replaced
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ del_commands, del_requests = self.get_delete_commands_requests_for_replaced_overridden(want, have, 'replaced')
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+ requests = del_requests
+
+ add_commands = get_diff(want, have, TEST_KEYS)
+ if add_commands:
+ for command in add_commands:
+ as_val = command['bgp_as']
+ vrf_name = command['vrf_name']
+
+ # max_med -> on_startup options are modified or deleted at once.
+ # Diff might not reflect the correct commands if only one of
+ # them is modified. So, update the command with want value.
+ if command.get('max_med'):
+ for cfg in want:
+ if cfg['vrf_name'] == vrf_name and cfg['bgp_as'] == as_val:
+ command['max_med'] = cfg['max_med']
+ break
+
+ commands.extend(update_states(add_commands, 'replaced'))
+ requests.extend(self.get_modify_bgp_requests(add_commands, have))
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ del_commands, del_requests = self.get_delete_commands_requests_for_replaced_overridden(want, have, 'overridden')
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+ requests = del_requests
+
+ add_commands = get_diff(want, have, TEST_KEYS)
+ if add_commands:
+ for command in add_commands:
+ as_val = command['bgp_as']
+ vrf_name = command['vrf_name']
+
+ # max_med -> on_startup options are modified or deleted at once.
+ # Diff will not reflect the correct commands if only one of
+ # them is modified. So, update the command with want value.
+ if command.get('max_med'):
+ for cfg in want:
+ if cfg['vrf_name'] == vrf_name and cfg['bgp_as'] == as_val:
+ command['max_med'] = cfg['max_med']
+ break
+
+ commands.extend(update_states(add_commands, 'overridden'))
+ requests.extend(self.get_modify_bgp_requests(add_commands, have))
+
+ return commands, requests
+
+ def _state_merged(self, want, have):
""" The command generator when state is merged
:param want: the additive configuration as a dictionary
@@ -158,7 +221,7 @@ class Bgp(ConfigBase):
:returns: the commands necessary to merge the provided into
the current configuration
"""
- commands = diff
+ commands = get_diff(want, have, TEST_KEYS)
requests = self.get_modify_bgp_requests(commands, have)
if commands and len(requests) > 0:
commands = update_states(commands, "merged")
@@ -167,7 +230,7 @@ class Bgp(ConfigBase):
return commands, requests
- def _state_deleted(self, want, have, diff):
+ def _state_deleted(self, want, have):
""" The command generator when state is deleted
:param want: the objects from which the configuration should be removed
@@ -269,6 +332,7 @@ class Bgp(ConfigBase):
requests = []
router_id = command.get('router_id', None)
+ rt_delay = command.get('rt_delay', None)
timers = command.get('timers', None)
holdtime = None
keepalive = None
@@ -282,6 +346,10 @@ class Bgp(ConfigBase):
url = '%s=%s/%s/global/config/router-id' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
requests.append({"path": url, "method": DELETE})
+ if rt_delay and match.get('rt_delay', None):
+ url = '%s=%s/%s/global/config/route-map-process-delay' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ requests.append({"path": url, "method": DELETE})
+
if holdtime and match['timers'].get('holdtime', None) != 180:
url = '%s=%s/%s/global/config/hold-time' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
requests.append({"path": url, "method": DELETE})
@@ -320,7 +388,7 @@ class Bgp(ConfigBase):
if not match:
continue
# if there is specific parameters to delete then delete those alone
- if cmd.get('router_id', None) or cmd.get('log_neighbor_changes', None) or cmd.get('bestpath', None):
+ if cmd.get('router_id', None) or cmd.get('log_neighbor_changes', None) or cmd.get('bestpath', None) or cmd.get('rt_delay', None):
requests.extend(self.get_delete_specific_bgp_param_request(cmd, match))
else:
# delete entire bgp
@@ -471,7 +539,7 @@ class Bgp(ConfigBase):
payload = {}
if holdtime is not None:
- payload['hold-time'] = str(holdtime)
+ payload['hold-time'] = holdtime
if payload:
url = '%s=%s/%s/global/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, self.holdtime_path)
@@ -485,7 +553,7 @@ class Bgp(ConfigBase):
payload = {}
if keepalive_interval is not None:
- payload['keepalive-interval'] = str(keepalive_interval)
+ payload['keepalive-interval'] = keepalive_interval
if payload:
url = '%s=%s/%s/global/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, self.keepalive_path)
@@ -514,7 +582,7 @@ class Bgp(ConfigBase):
return request
- def get_modify_global_config_request(self, vrf_name, router_id, as_val):
+ def get_modify_global_config_request(self, vrf_name, router_id, as_val, rt_delay):
request = None
method = PATCH
payload = {}
@@ -524,6 +592,8 @@ class Bgp(ConfigBase):
cfg['router-id'] = router_id
if as_val:
cfg['as'] = float(as_val)
+ if rt_delay:
+ cfg['route-map-process-delay'] = rt_delay
if cfg:
payload['openconfig-network-instance:config'] = cfg
@@ -547,6 +617,7 @@ class Bgp(ConfigBase):
max_med = None
holdtime = None
keepalive_interval = None
+ rt_delay = None
if 'bgp_as' in conf:
as_val = conf['bgp_as']
@@ -558,6 +629,8 @@ class Bgp(ConfigBase):
bestpath = conf['bestpath']
if 'max_med' in conf:
max_med = conf['max_med']
+ if 'rt_delay' in conf:
+ rt_delay = conf['rt_delay']
if 'timers' in conf and conf['timers']:
if 'holdtime' in conf['timers']:
holdtime = conf['timers']['holdtime']
@@ -569,7 +642,7 @@ class Bgp(ConfigBase):
if new_bgp_req:
requests.append(new_bgp_req)
- global_req = self.get_modify_global_config_request(vrf_name, router_id, as_val)
+ global_req = self.get_modify_global_config_request(vrf_name, router_id, as_val, rt_delay)
if global_req:
requests.append(global_req)
@@ -596,3 +669,112 @@ class Bgp(ConfigBase):
requests.extend(max_med_reqs)
return requests
+
+ def get_delete_commands_requests_for_replaced_overridden(self, want, have, state):
+ """Returns the commands and requests necessary to remove applicable
+ current configurations when state is replaced or overridden
+ """
+ commands = []
+ requests = []
+ if not have:
+ return commands, requests
+
+ for conf in have:
+ as_val = conf['bgp_as']
+ vrf_name = conf['vrf_name']
+
+ match_cfg = next((cfg for cfg in want if cfg['vrf_name'] == vrf_name and cfg['bgp_as'] == as_val), None)
+ # Delete entire BGP if not specified in overridden
+ if not match_cfg:
+ if state == 'overridden':
+ commands.append(conf)
+ requests.append(self.get_delete_single_bgp_request(vrf_name))
+ continue
+
+ # Delete config in BGP AS that are replaced/overridden
+ # - Modified attributes are not deleted, since they will be
+ # updated by merge.
+ # - log_neighbor_changes is enabled by default, therefore
+ # it will be enabled if not specified and currently
+ # disabled for an existing BGP AS.
+ command = {}
+
+ if conf.get('router_id') and not match_cfg.get('router_id'):
+ command['router_id'] = conf['router_id']
+
+ if conf.get('rt_delay') and match_cfg.get('rt_delay') is None:
+ command['rt_delay'] = conf['rt_delay']
+
+ if not conf.get('log_neighbor_changes') and match_cfg.get('log_neighbor_changes') is None:
+ command['log_neighbor_changes'] = False
+ requests.append(self.get_modify_log_change_request(vrf_name, True))
+
+ # max_med -> on_startup options are deleted at once.
+ # Update the commands appropriately.
+ if conf.get('max_med') and (not match_cfg.get('max_med') or conf['max_med']['on_startup'] != match_cfg['max_med']['on_startup']):
+ command['max_med'] = conf['max_med']
+
+ if conf.get('timers'):
+ timer_command = {}
+ timers = conf['timers']
+ match_timers = match_cfg.get('timers', {})
+ if timers.get('holdtime') is not None and match_timers.get('holdtime') is None and timers['holdtime'] != 180:
+ timer_command['holdtime'] = timers['holdtime']
+ if timers.get('keepalive_interval') is not None and match_timers.get('keepalive_interval') is None and timers['keepalive_interval'] != 60:
+ timer_command['keepalive_interval'] = timers['keepalive_interval']
+
+ if timer_command:
+ command['timers'] = timer_command
+
+ if conf.get('bestpath'):
+ bestpath_command = {}
+ bestpath = conf['bestpath']
+ match_bestpath = match_cfg.get('bestpath', {})
+ if bestpath.get('as_path'):
+ as_path_command = {}
+ as_path = bestpath['as_path']
+ match_as_path = match_bestpath.get('as_path', {})
+ for option in ('confed', 'ignore', 'multipath_relax', 'multipath_relax_as_set'):
+ if as_path.get(option) and match_as_path.get(option) is None:
+ as_path_command[option] = True
+
+ if as_path_command:
+ bestpath_command['as_path'] = as_path_command
+
+ if bestpath.get('compare_routerid') and match_bestpath.get('compare_routerid') is None:
+ bestpath_command['compare_routerid'] = True
+
+ if bestpath.get('med'):
+ med_command = {}
+ med = bestpath['med']
+ match_med = match_bestpath.get('med', {})
+ for option in ('confed', 'missing_as_worst', 'always_compare_med'):
+ if med.get(option) and match_med.get(option) is None:
+ med_command[option] = True
+
+ if med_command:
+ bestpath_command['med'] = med_command
+
+ if bestpath_command:
+ command['bestpath'] = bestpath_command
+
+ if command:
+ command['bgp_as'] = as_val
+ command['vrf_name'] = vrf_name
+ commands.append(command)
+ requests.extend(self.get_delete_specific_bgp_param_request(command, command))
+
+ if requests:
+ # reorder the requests to get default vrfs at end of the requests. so deletion will get success
+ default_vrf_reqs = []
+ other_vrf_reqs = []
+ for req in requests:
+ if '=default/' in req['path']:
+ default_vrf_reqs.append(req)
+ else:
+ other_vrf_reqs.append(req)
+ requests.clear()
+ requests.extend(other_vrf_reqs)
+ requests.extend(default_vrf_reqs)
+
+ return commands, requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_af/bgp_af.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_af/bgp_af.py
index 2a5c4cfca..05d74fff2 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_af/bgp_af.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_af/bgp_af.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2019 Red Hat
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -14,17 +14,16 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
try:
- from urllib import quote
+ from urllib import quote_plus
except ImportError:
- from urllib.parse import quote
+ from urllib.parse import quote_plus
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
- ConfigBase,
+ ConfigBase
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_empties,
to_list,
- search_obj_in_list,
- remove_empties
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
@@ -32,14 +31,11 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
edit_config
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
- dict_to_set,
update_states,
- get_diff,
- remove_empties_from_list,
+ get_diff
)
-from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import to_request
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import (
- validate_bgps,
+ validate_bgps
)
from ansible.module_utils.connection import ConnectionError
@@ -49,7 +45,8 @@ TEST_KEYS = [
{'config': {'vrf_name': '', 'bgp_as': ''}},
{'afis': {'afi': '', 'safi': ''}},
{'redistribute': {'protocol': ''}},
- {'route_advertise_list': {'advertise_afi': ''}}
+ {'route_advertise_list': {'advertise_afi': ''}},
+ {'vnis': {'vni_number': ''}}
]
@@ -71,9 +68,31 @@ class Bgp_af(ConfigBase):
protocol_bgp_path = 'protocols/protocol=BGP,bgp/bgp'
l2vpn_evpn_config_path = 'l2vpn-evpn/openconfig-bgp-evpn-ext:config'
l2vpn_evpn_route_advertise_path = 'l2vpn-evpn/openconfig-bgp-evpn-ext:route-advertise'
+ l2vpn_evpn_vnis_path = 'l2vpn-evpn/openconfig-bgp-evpn-ext:vnis'
afi_safi_path = 'global/afi-safis/afi-safi'
table_connection_path = 'table-connections/table-connection'
+ advertise_attrs_map = {
+ 'advertise_pip': 'advertise-pip',
+ 'advertise_pip_ip': 'advertise-pip-ip',
+ 'advertise_pip_peer_ip': 'advertise-pip-peer-ip',
+ 'advertise_svi_ip': 'advertise-svi-ip',
+ 'advertise_default_gw': 'advertise-default-gw',
+ 'advertise_all_vni': 'advertise-all-vni',
+ 'rd': 'route-distinguisher',
+ 'rt_in': 'import-rts',
+ 'rt_out': 'export-rts'
+ }
+ non_list_advertise_attrs = (
+ 'advertise_pip',
+ 'advertise_pip_ip',
+ 'advertise_pip_peer_ip',
+ 'advertise_svi_ip',
+ 'advertise_default_gw',
+ 'advertise_all_vni',
+ 'rd'
+ )
+
def __init__(self, module):
super(Bgp_af, self).__init__(module)
@@ -125,7 +144,15 @@ class Bgp_af(ConfigBase):
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
+ state = self._module.params['state']
want = self._module.params['config']
+ if want:
+ # In state deleted, specific empty parameters are supported
+ if state != 'deleted':
+ want = [remove_empties(conf) for conf in want]
+ else:
+ want = []
+
have = existing_bgp_af_facts
resp = self.set_state(want, have)
return to_list(resp)
@@ -143,19 +170,64 @@ class Bgp_af(ConfigBase):
requests = []
state = self._module.params['state']
- diff = get_diff(want, have, TEST_KEYS)
-
if state == 'overridden':
- commands, requests = self._state_overridden(want, have, diff)
+ commands, requests = self._state_overridden(want, have)
elif state == 'deleted':
- commands, requests = self._state_deleted(want, have, diff)
+ commands, requests = self._state_deleted(want, have)
elif state == 'merged':
- commands, requests = self._state_merged(want, have, diff)
+ commands, requests = self._state_merged(want, have)
elif state == 'replaced':
- commands, requests = self._state_replaced(want, have, diff)
+ commands, requests = self._state_replaced(want, have)
+
return commands, requests
- def _state_merged(self, want, have, diff):
+ def _state_replaced(self, want, have):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ validate_bgps(self._module, want, have)
+
+ del_commands, del_requests = self.get_delete_commands_requests_for_replaced_overridden(want, have, 'replaced')
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+ requests = del_requests
+
+ add_commands = get_diff(want, have, TEST_KEYS)
+ if add_commands:
+ commands.extend(update_states(add_commands, 'replaced'))
+ requests.extend(self.get_modify_bgp_af_requests(add_commands, have))
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ validate_bgps(self._module, want, have)
+
+ del_commands, del_requests = self.get_delete_commands_requests_for_replaced_overridden(want, have, 'overridden')
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+ requests = del_requests
+
+ add_commands = get_diff(want, have, TEST_KEYS)
+ if add_commands:
+ commands.extend(update_states(add_commands, 'overridden'))
+ requests.extend(self.get_modify_bgp_af_requests(add_commands, have))
+
+ return commands, requests
+
+ def _state_merged(self, want, have):
""" The command generator when state is merged
:param want: the additive configuration as a dictionary
@@ -164,7 +236,7 @@ class Bgp_af(ConfigBase):
:returns: the commands necessary to merge the provided into
the current configuration
"""
- commands = diff
+ commands = get_diff(want, have, TEST_KEYS)
validate_bgps(self._module, commands, have)
requests = self.get_modify_bgp_af_requests(commands, have)
if commands and len(requests) > 0:
@@ -173,7 +245,7 @@ class Bgp_af(ConfigBase):
commands = []
return commands, requests
- def _state_deleted(self, want, have, diff):
+ def _state_deleted(self, want, have):
""" The command generator when state is deleted
:param want: the objects from which the configuration should be removed
@@ -191,7 +263,6 @@ class Bgp_af(ConfigBase):
commands = want
requests = self.get_delete_bgp_af_requests(commands, have, is_delete_all)
- requests.extend(self.get_delete_route_advertise_requests(commands, have, is_delete_all))
if commands and len(requests) > 0:
commands = update_states(commands, "deleted")
@@ -208,7 +279,7 @@ class Bgp_af(ConfigBase):
return ({"path": url, "method": PATCH, "data": pay_load})
- def get_modify_advertise_request(self, vrf_name, conf_afi, conf_safi, conf_addr_fam):
+ def get_modify_evpn_adv_cfg_request(self, vrf_name, conf_afi, conf_safi, conf_addr_fam):
request = None
conf_adv_pip = conf_addr_fam.get('advertise_pip', None)
conf_adv_pip_ip = conf_addr_fam.get('advertise_pip_ip', None)
@@ -216,26 +287,30 @@ class Bgp_af(ConfigBase):
conf_adv_svi_ip = conf_addr_fam.get('advertise_svi_ip', None)
conf_adv_all_vni = conf_addr_fam.get('advertise_all_vni', None)
conf_adv_default_gw = conf_addr_fam.get('advertise_default_gw', None)
+ conf_rd = conf_addr_fam.get('rd', None)
+ conf_rt_in = conf_addr_fam.get('rt_in', [])
+ conf_rt_out = conf_addr_fam.get('rt_out', [])
afi_safi = ("%s_%s" % (conf_afi, conf_safi)).upper()
evpn_cfg = {}
- if conf_adv_pip:
+ if conf_adv_pip is not None:
evpn_cfg['advertise-pip'] = conf_adv_pip
-
if conf_adv_pip_ip:
evpn_cfg['advertise-pip-ip'] = conf_adv_pip_ip
-
if conf_adv_pip_peer_ip:
evpn_cfg['advertise-pip-peer-ip'] = conf_adv_pip_peer_ip
-
- if conf_adv_svi_ip:
+ if conf_adv_svi_ip is not None:
evpn_cfg['advertise-svi-ip'] = conf_adv_svi_ip
-
- if conf_adv_all_vni:
+ if conf_adv_all_vni is not None:
evpn_cfg['advertise-all-vni'] = conf_adv_all_vni
-
- if conf_adv_default_gw:
+ if conf_adv_default_gw is not None:
evpn_cfg['advertise-default-gw'] = conf_adv_default_gw
+ if conf_rd:
+ evpn_cfg['route-distinguisher'] = conf_rd
+ if conf_rt_in:
+ evpn_cfg['import-rts'] = conf_rt_in
+ if conf_rt_out:
+ evpn_cfg['export-rts'] = conf_rt_out
if evpn_cfg:
url = '%s=%s/%s/global' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
@@ -247,6 +322,52 @@ class Bgp_af(ConfigBase):
return request
+ def get_modify_evpn_vnis_request(self, vrf_name, conf_afi, conf_safi, conf_addr_fam):
+ request = None
+ conf_vnis = conf_addr_fam.get('vnis', [])
+ afi_safi = ("%s_%s" % (conf_afi, conf_safi)).upper()
+ vnis_dict = {}
+ vni_list = []
+
+ if conf_vnis:
+ for vni in conf_vnis:
+ vni_dict = {}
+ cfg = {}
+ vni_number = vni.get('vni_number', None)
+ adv_default_gw = vni.get('advertise_default_gw', None)
+ adv_svi_ip = vni.get('advertise_svi_ip', None)
+ rd = vni.get('rd', None)
+ rt_in = vni.get('rt_in', [])
+ rt_out = vni.get('rt_out', [])
+
+ if vni_number:
+ cfg['vni-number'] = vni_number
+ if adv_default_gw is not None:
+ cfg['advertise-default-gw'] = adv_default_gw
+ if adv_svi_ip is not None:
+ cfg['advertise-svi-ip'] = adv_svi_ip
+ if rd:
+ cfg['route-distinguisher'] = rd
+ if rt_in:
+ cfg['import-rts'] = rt_in
+ if rt_out:
+ cfg['export-rts'] = rt_out
+ if cfg:
+ vni_dict['config'] = cfg
+ vni_dict['vni-number'] = vni_number
+ vni_list.append(vni_dict)
+
+ if vni_list:
+ vnis_dict['vni'] = vni_list
+ url = '%s=%s/%s/global' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ afi_safi_load = {'afi-safi-name': ("openconfig-bgp-types:%s" % (afi_safi))}
+ afi_safi_load['l2vpn-evpn'] = {'openconfig-bgp-evpn-ext:vnis': vnis_dict}
+ afi_safis_load = {'afi-safis': {'afi-safi': [afi_safi_load]}}
+ pay_load = {'openconfig-network-instance:global': afi_safis_load}
+ request = {"path": url, "method": PATCH, "data": pay_load}
+
+ return request
+
def get_modify_route_advertise_list_request(self, vrf_name, conf_afi, conf_safi, conf_addr_fam):
request = []
route_advertise = []
@@ -259,7 +380,7 @@ class Bgp_af(ConfigBase):
if advertise_afi:
advertise_afi_safi = '%s_UNICAST' % advertise_afi.upper()
url = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
- url += '/%s=%s/%s' % (self.afi_safi_path, afi_safi, self.l2vpn_evpn_route_advertise_path)
+ url += '/%s=%s/%s/route-advertise-list' % (self.afi_safi_path, afi_safi, self.l2vpn_evpn_route_advertise_path)
cfg = None
if route_map:
route_map_list = [route_map]
@@ -267,7 +388,7 @@ class Bgp_af(ConfigBase):
else:
cfg = {'advertise-afi-safi': advertise_afi_safi}
route_advertise.append({'advertise-afi-safi': advertise_afi_safi, 'config': cfg})
- pay_load = {'openconfig-bgp-evpn-ext:route-advertise': {'route-advertise-list': route_advertise}}
+ pay_load = {'openconfig-bgp-evpn-ext:route-advertise-list': route_advertise}
request = {"path": url, "method": PATCH, "data": pay_load}
return request
@@ -373,9 +494,15 @@ class Bgp_af(ConfigBase):
if request:
requests.append(request)
elif conf_afi == "l2vpn" and conf_safi == 'evpn':
- adv_req = self.get_modify_advertise_request(vrf_name, conf_afi, conf_safi, conf_addr_fam)
- if adv_req:
- requests.append(adv_req)
+ cfg_req = self.get_modify_evpn_adv_cfg_request(vrf_name, conf_afi, conf_safi, conf_addr_fam)
+ vni_req = self.get_modify_evpn_vnis_request(vrf_name, conf_afi, conf_safi, conf_addr_fam)
+ rt_adv_req = self.get_modify_route_advertise_list_request(vrf_name, conf_afi, conf_safi, conf_addr_fam)
+ if cfg_req:
+ requests.append(cfg_req)
+ if vni_req:
+ requests.append(vni_req)
+ if rt_adv_req:
+ requests.append(rt_adv_req)
return requests
def get_modify_all_af_requests(self, conf_addr_fams, vrf_name):
@@ -418,16 +545,19 @@ class Bgp_af(ConfigBase):
if conf_afi == 'ipv4' and conf_safi == 'unicast':
conf_dampening = conf_addr_fam.get('dampening', None)
- if conf_dampening:
+ if conf_dampening is not None:
request = self.get_modify_dampening_request(vrf_name, conf_afi, conf_safi, conf_dampening)
if request:
requests.append(request)
if conf_afi == "l2vpn" and conf_safi == "evpn":
- adv_req = self.get_modify_advertise_request(vrf_name, conf_afi, conf_safi, conf_addr_fam)
+ cfg_req = self.get_modify_evpn_adv_cfg_request(vrf_name, conf_afi, conf_safi, conf_addr_fam)
+ vni_req = self.get_modify_evpn_vnis_request(vrf_name, conf_afi, conf_safi, conf_addr_fam)
rt_adv_req = self.get_modify_route_advertise_list_request(vrf_name, conf_afi, conf_safi, conf_addr_fam)
- if adv_req:
- requests.append(adv_req)
+ if cfg_req:
+ requests.append(cfg_req)
+ if vni_req:
+ requests.append(vni_req)
if rt_adv_req:
requests.append(rt_adv_req)
@@ -451,13 +581,14 @@ class Bgp_af(ConfigBase):
have_redis_arr = mat_addr_fam.get('redistribute', [])
have_redis = None
have_route_map = None
- # Check the route_map, if existing route_map is different from required route_map, delete the existing route map
- if conf_route_map and have_redis_arr:
+ if have_redis_arr:
have_redis = next((redis_cfg for redis_cfg in have_redis_arr if conf_redis['protocol'] == redis_cfg['protocol']), None)
- if have_redis:
- have_route_map = have_redis.get('route_map', None)
- if have_route_map and have_route_map != conf_route_map:
- requests.append(self.get_delete_route_map_request(vrf_name, conf_afi, have_redis, have_route_map))
+
+ # Check the route_map, if existing route_map is different from required route_map, delete the existing route map
+ if conf_route_map and have_redis:
+ have_route_map = have_redis.get('route_map', None)
+ if have_route_map and have_route_map != conf_route_map:
+ requests.append(self.get_delete_redistribute_route_map_request(vrf_name, conf_afi, have_redis, have_route_map))
modify_redis = {}
if conf_metric is not None:
@@ -465,7 +596,7 @@ class Bgp_af(ConfigBase):
if conf_route_map:
modify_redis['route_map'] = conf_route_map
- if modify_redis:
+ if modify_redis or have_redis is None:
modify_redis['protocol'] = conf_redis['protocol']
modify_redis_arr.append(modify_redis)
@@ -531,50 +662,100 @@ class Bgp_af(ConfigBase):
return ({'path': url, 'method': DELETE})
- def get_delete_route_advertise_requests(self, commands, have, is_delete_all):
+ def get_delete_all_vnis_request(self, vrf_name, conf_afi, conf_safi, conf_vnis):
+ requests = []
+ for vni in conf_vnis:
+ requests.append(self.get_delete_vni_request(vrf_name, conf_afi, conf_safi, vni['vni_number']))
+
+ return requests
+
+ def get_delete_vni_request(self, vrf_name, conf_afi, conf_safi, vni_number):
+ afi_safi = ('%s_%s' % (conf_afi, conf_safi)).upper()
+ url = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ url += '/%s=%s/%s/vni=%s' % (self.afi_safi_path, afi_safi, self.l2vpn_evpn_vnis_path, vni_number)
+
+ return ({'path': url, 'method': DELETE})
+
+ def get_delete_vni_cfg_attr_request(self, vrf_name, conf_afi, conf_safi, vni_number, attr):
+ afi_safi = ('%s_%s' % (conf_afi, conf_safi)).upper()
+ url = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ url += '/%s=%s/%s/vni=%s' % (self.afi_safi_path, afi_safi, self.l2vpn_evpn_vnis_path, vni_number)
+ url += '/config/%s' % attr
+
+ return ({'path': url, 'method': DELETE})
+
+ def get_delete_rt(self, conf_rt, mat_rt):
+ del_rt_list = []
+ for rt in conf_rt:
+ if mat_rt and rt in mat_rt:
+ del_rt_list.append(rt)
+ encoded_del_rt_list = quote_plus(','.join(del_rt_list))
+
+ return encoded_del_rt_list
+
+ def get_delete_route_advertise_requests(self, vrf_name, conf_afi, conf_safi, conf_route_adv_list, is_delete_all, mat_route_adv_list):
requests = []
- if not is_delete_all:
- for cmd in commands:
- vrf_name = cmd['vrf_name']
- addr_fams = cmd.get('address_family', None)
- if addr_fams:
- addr_fams = addr_fams.get('afis', [])
- if not addr_fams:
- return requests
- for addr_fam in addr_fams:
- afi = addr_fam.get('afi', None)
- safi = addr_fam.get('safi', None)
- route_advertise_list = addr_fam.get('route_advertise_list', [])
- if route_advertise_list:
- for rt_adv in route_advertise_list:
- advertise_afi = rt_adv.get('advertise_afi', None)
- route_map = rt_adv.get('route_map', None)
- # Check if the commands to be deleted are configured
- for conf in have:
- conf_vrf_name = conf['vrf_name']
- conf_addr_fams = conf.get('address_family', None)
- if conf_addr_fams:
- conf_addr_fams = conf_addr_fams.get('afis', [])
- for conf_addr_fam in conf_addr_fams:
- conf_afi = conf_addr_fam.get('afi', None)
- conf_safi = conf_addr_fam.get('safi', None)
- conf_route_advertise_list = conf_addr_fam.get('route_advertise_list', [])
- if conf_route_advertise_list:
- for conf_rt_adv in conf_route_advertise_list:
- conf_advertise_afi = conf_rt_adv.get('advertise_afi', None)
- conf_route_map = conf_rt_adv.get('route_map', None)
- # Deletion at route-advertise level
- if (not advertise_afi and vrf_name == conf_vrf_name and afi == conf_afi and safi == conf_safi):
- requests.append(self.get_delete_route_advertise_request(vrf_name, afi, safi))
- # Deletion at advertise-afi-safi level
- if (advertise_afi and not route_map and vrf_name == conf_vrf_name and afi == conf_afi and safi ==
- conf_safi and advertise_afi == conf_advertise_afi):
- requests.append(self.get_delete_route_advertise_list_request(vrf_name, afi, safi, advertise_afi))
- # Deletion at route-map level
- if (route_map and vrf_name == conf_vrf_name and afi == conf_afi and safi == conf_safi
- and advertise_afi == conf_advertise_afi and route_map == conf_route_map):
- requests.append(self.get_delete_route_advertise_route_map_request(vrf_name, afi, safi,
- advertise_afi, route_map))
+ if is_delete_all:
+ requests.append(self.get_delete_route_advertise_request(vrf_name, conf_afi, conf_safi))
+ else:
+ for conf_rt_adv in conf_route_adv_list:
+ conf_advertise_afi = conf_rt_adv.get('advertise_afi', None)
+ conf_route_map = conf_rt_adv.get('route_map', None)
+ # Check if the commands to be deleted are configured
+ for mat_rt_adv in mat_route_adv_list:
+ mat_advertise_afi = mat_rt_adv.get('advertise_afi', None)
+ mat_route_map = mat_rt_adv.get('route_map', None)
+ # Deletion at advertise-afi-safi level
+ if (not conf_route_map and conf_advertise_afi == mat_advertise_afi):
+ requests.append(self.get_delete_route_advertise_list_request(vrf_name, conf_afi, conf_safi, conf_advertise_afi))
+ # Deletion at route-map level
+ if (conf_route_map and conf_advertise_afi == mat_advertise_afi and conf_route_map == mat_route_map):
+ requests.append(self.get_delete_route_advertise_route_map_request(vrf_name, conf_afi, conf_safi, conf_advertise_afi, conf_route_map))
+
+ return requests
+
+ def get_delete_vnis_requests(self, vrf_name, conf_afi, conf_safi, conf_vnis, is_delete_all, mat_vnis):
+ requests = []
+ if is_delete_all:
+ requests.extend(self.get_delete_all_vnis_request(vrf_name, conf_afi, conf_safi, conf_vnis))
+ else:
+ for conf_vni in conf_vnis:
+ conf_vni_number = conf_vni.get('vni_number', None)
+ conf_adv_default_gw = conf_vni.get('advertise_default_gw', None)
+ conf_adv_svi_ip = conf_vni.get('advertise_svi_ip', None)
+ conf_rd = conf_vni.get('rd', None)
+ conf_rt_in = conf_vni.get('rt_in', None)
+ conf_rt_out = conf_vni.get('rt_out', None)
+ # Check if the commands to be deleted are configured
+ for mat_vni in mat_vnis:
+ mat_vni_number = mat_vni.get('vni_number', None)
+ mat_adv_default_gw = mat_vni.get('advertise_default_gw', None)
+ mat_adv_svi_ip = mat_vni.get('advertise_svi_ip', None)
+ mat_rd = mat_vni.get('rd', None)
+ mat_rt_in = mat_vni.get('rt_in', None)
+ mat_rt_out = mat_vni.get('rt_out', None)
+ # Deletion at vni-number level
+ if (conf_vni_number and conf_vni_number == mat_vni_number and not conf_adv_default_gw and not conf_adv_svi_ip and not conf_rd and not
+ conf_rt_in and not conf_rt_out):
+ requests.append(self.get_delete_vni_request(vrf_name, conf_afi, conf_safi, conf_vni_number))
+ # Deletion at config/attribute level
+ if conf_vni_number == mat_vni_number:
+ if conf_adv_default_gw is not None and conf_adv_default_gw == mat_adv_default_gw:
+ requests.append(self.get_delete_vni_cfg_attr_request(vrf_name, conf_afi, conf_safi, conf_vni_number, 'advertise-default-gw'))
+ if conf_adv_svi_ip is not None and conf_adv_svi_ip == mat_adv_svi_ip:
+ requests.append(self.get_delete_vni_cfg_attr_request(vrf_name, conf_afi, conf_safi, conf_vni_number, 'advertise-svi-ip'))
+ if conf_rd and conf_rd == mat_rd:
+ requests.append(self.get_delete_vni_cfg_attr_request(vrf_name, conf_afi, conf_safi, conf_vni_number, 'route-distinguisher'))
+ if conf_rt_in:
+ del_rt_list = self.get_delete_rt(conf_rt_in, mat_rt_in)
+ if del_rt_list:
+ requests.append(self.get_delete_vni_cfg_attr_request(vrf_name, conf_afi, conf_safi, conf_vni_number, 'import-rts=%s' %
+ del_rt_list))
+ if conf_rt_out:
+ del_rt_list = self.get_delete_rt(conf_rt_out, mat_rt_out)
+ if del_rt_list:
+ requests.append(self.get_delete_vni_cfg_attr_request(vrf_name, conf_afi, conf_safi, conf_vni_number, 'export-rts=%s' %
+ del_rt_list))
return requests
@@ -588,11 +769,10 @@ class Bgp_af(ConfigBase):
def get_delete_address_family_request(self, vrf_name, conf_afi, conf_safi):
request = None
- if conf_afi != "l2vpn":
- afi_safi = ("%s_%s" % (conf_afi, conf_safi)).upper()
- url = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
- url += '/%s=openconfig-bgp-types:%s' % (self.afi_safi_path, afi_safi)
- request = {"path": url, "method": DELETE}
+ afi_safi = ("%s_%s" % (conf_afi, conf_safi)).upper()
+ url = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ url += '/%s=openconfig-bgp-types:%s' % (self.afi_safi_path, afi_safi)
+ request = {"path": url, "method": DELETE}
return request
@@ -630,27 +810,42 @@ class Bgp_af(ConfigBase):
conf_max_path = conf_addr_fam.get('max_path', None)
conf_dampening = conf_addr_fam.get('dampening', None)
conf_network = conf_addr_fam.get('network', [])
+ conf_route_adv_list = conf_addr_fam.get('route_advertise_list', [])
+ conf_rd = conf_addr_fam.get('rd', None)
+ conf_rt_in = conf_addr_fam.get('rt_in', [])
+ conf_rt_out = conf_addr_fam.get('rt_out', [])
+ conf_vnis = conf_addr_fam.get('vnis', [])
if is_delete_all:
- if conf_adv_pip:
- requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip'))
if conf_adv_pip_ip:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip-ip'))
if conf_adv_pip_peer_ip:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip-peer-ip'))
- if conf_adv_svi_ip:
+ if conf_adv_pip is not None:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip'))
+ if conf_adv_svi_ip is not None:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-svi-ip'))
- if conf_adv_all_vni:
+ if conf_adv_all_vni is not None:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-all-vni'))
if conf_dampening:
requests.append(self.get_delete_dampening_request(vrf_name, conf_afi, conf_safi))
if conf_network:
requests.extend(self.get_delete_network_request(vrf_name, conf_afi, conf_safi, conf_network, is_delete_all, None))
- if conf_adv_default_gw:
+ if conf_adv_default_gw is not None:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-default-gw'))
+ if conf_route_adv_list:
+ requests.extend(self.get_delete_route_advertise_requests(vrf_name, conf_afi, conf_safi, conf_route_adv_list, is_delete_all, None))
+ if conf_rd:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'route-distinguisher'))
+ if conf_rt_in:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'import-rts'))
+ if conf_rt_out:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'export-rts'))
if conf_redis_arr:
requests.extend(self.get_delete_redistribute_requests(vrf_name, conf_afi, conf_safi, conf_redis_arr, is_delete_all, None))
if conf_max_path:
requests.extend(self.get_delete_max_path_requests(vrf_name, conf_afi, conf_safi, conf_max_path, is_delete_all, None))
+ if conf_vnis:
+ requests.extend(self.get_delete_vnis_requests(vrf_name, conf_afi, conf_safi, conf_vnis, is_delete_all, None))
addr_family_del_req = self.get_delete_address_family_request(vrf_name, conf_afi, conf_safi)
if addr_family_del_req:
requests.append(addr_family_del_req)
@@ -674,54 +869,87 @@ class Bgp_af(ConfigBase):
mat_max_path = match_addr_fam.get('max_path', None)
mat_dampening = match_addr_fam.get('dampening', None)
mat_network = match_addr_fam.get('network', [])
-
- if (conf_adv_pip is None and conf_adv_pip_ip is None and conf_adv_pip_peer_ip is None and conf_adv_svi_ip is None
- and conf_adv_all_vni is None and not conf_redis_arr and conf_adv_default_gw is None
- and not conf_max_path and conf_dampening is None and not conf_network):
- if mat_advt_pip:
- requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip'))
+ mat_route_adv_list = match_addr_fam.get('route_advertise_list', None)
+ mat_rd = match_addr_fam.get('rd', None)
+ mat_rt_in = match_addr_fam.get('rt_in', [])
+ mat_rt_out = match_addr_fam.get('rt_out', [])
+ mat_vnis = match_addr_fam.get('vnis', [])
+
+ if (conf_adv_pip is None and not conf_adv_pip_ip and not conf_adv_pip_peer_ip and conf_adv_svi_ip is None
+ and conf_adv_all_vni is None and not conf_redis_arr and conf_adv_default_gw is None and not conf_max_path and conf_dampening is
+ None and not conf_network and not conf_route_adv_list and not conf_rd and not conf_rt_in and not conf_rt_out and not conf_vnis):
if mat_advt_pip_ip:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip-ip'))
if mat_advt_pip_peer_ip:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip-peer-ip'))
- if mat_advt_svi_ip:
+ if mat_advt_pip is not None:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip'))
+ if mat_advt_svi_ip is not None:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-svi-ip'))
if mat_advt_all_vni is not None:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-all-vni'))
- if mat_dampening is not None:
+ if mat_dampening:
requests.append(self.get_delete_dampening_request(vrf_name, conf_afi, conf_safi))
- if mat_advt_defaut_gw:
+ if mat_advt_defaut_gw is not None:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-default-gw'))
+ if mat_route_adv_list:
+ requests.extend(self.get_delete_route_advertise_requests(vrf_name, conf_afi, conf_safi, mat_route_adv_list, is_delete_all,
+ mat_route_adv_list))
+ if mat_rd:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'route-distinguisher'))
+ if mat_rt_in:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'import-rts'))
+ if mat_rt_out:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'export-rts'))
if mat_redis_arr:
requests.extend(self.get_delete_redistribute_requests(vrf_name, conf_afi, conf_safi, mat_redis_arr, False, mat_redis_arr))
if mat_max_path:
requests.extend(self.get_delete_max_path_requests(vrf_name, conf_afi, conf_safi, mat_max_path, is_delete_all, mat_max_path))
if mat_network:
requests.extend(self.get_delete_network_request(vrf_name, conf_afi, conf_safi, mat_network, False, mat_network))
+ if mat_vnis:
+ requests.extend(self.get_delete_vnis_requests(vrf_name, conf_afi, conf_safi, mat_vnis, is_delete_all, mat_vnis))
addr_family_del_req = self.get_delete_address_family_request(vrf_name, conf_afi, conf_safi)
if addr_family_del_req:
requests.append(addr_family_del_req)
else:
- if conf_adv_pip and mat_advt_pip:
- requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip'))
- if conf_adv_pip_ip and mat_advt_pip_ip:
+ if conf_adv_pip_ip and conf_adv_pip_ip == mat_advt_pip_ip:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip-ip'))
- if conf_adv_pip_peer_ip and mat_advt_pip_peer_ip:
+ if conf_adv_pip_peer_ip and conf_adv_pip_peer_ip == mat_advt_pip_peer_ip:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip-peer-ip'))
- if conf_adv_svi_ip and mat_advt_svi_ip:
+ if conf_adv_pip is not None and conf_adv_pip == mat_advt_pip:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-pip'))
+ if conf_adv_svi_ip is not None and conf_adv_svi_ip == mat_advt_svi_ip:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-svi-ip'))
- if conf_adv_all_vni and mat_advt_all_vni:
+ if conf_adv_all_vni is not None and conf_adv_all_vni == mat_advt_all_vni:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-all-vni'))
- if conf_dampening and mat_dampening:
+ if conf_dampening and conf_dampening == mat_dampening:
requests.append(self.get_delete_dampening_request(vrf_name, conf_afi, conf_safi))
- if conf_adv_default_gw and mat_advt_defaut_gw:
+ if conf_adv_default_gw is not None and conf_adv_default_gw == mat_advt_defaut_gw:
requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-default-gw'))
+ if conf_route_adv_list and mat_route_adv_list:
+ requests.extend(self.get_delete_route_advertise_requests(vrf_name, conf_afi, conf_safi, conf_route_adv_list, is_delete_all,
+ mat_route_adv_list))
+ if conf_rd and conf_rd == mat_rd:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'route-distinguisher'))
+ if conf_rt_in:
+ del_rt_list = self.get_delete_rt(conf_rt_in, mat_rt_in)
+ if del_rt_list:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'import-rts=%s' %
+ del_rt_list))
+ if conf_rt_out:
+ del_rt_list = self.get_delete_rt(conf_rt_out, mat_rt_out)
+ if del_rt_list:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'export-rts=%s' %
+ del_rt_list))
if conf_redis_arr and mat_redis_arr:
requests.extend(self.get_delete_redistribute_requests(vrf_name, conf_afi, conf_safi, conf_redis_arr, False, mat_redis_arr))
if conf_max_path and mat_max_path:
requests.extend(self.get_delete_max_path_requests(vrf_name, conf_afi, conf_safi, conf_max_path, is_delete_all, mat_max_path))
if conf_network and mat_network:
requests.extend(self.get_delete_network_request(vrf_name, conf_afi, conf_safi, conf_network, False, mat_network))
+ if conf_vnis and mat_vnis:
+ requests.extend(self.get_delete_vnis_requests(vrf_name, conf_afi, conf_safi, conf_vnis, is_delete_all, mat_vnis))
break
return requests
@@ -761,14 +989,14 @@ class Bgp_af(ConfigBase):
mat_ebgp = mat_max_path.get('ebgp', None)
mat_ibgp = mat_max_path.get('ibgp', None)
- if (conf_ebgp and mat_ebgp) or is_delete_all:
- requests.append({'path': url + 'ebgp', 'method': DELETE})
- if (conf_ibgp and mat_ibgp) or is_delete_all:
- requests.append({'path': url + 'ibgp', 'method': DELETE})
+ if (conf_ebgp and mat_ebgp and mat_ebgp != 1) or (is_delete_all and conf_ebgp != 1):
+ requests.append({'path': url + 'ebgp/config/maximum-paths', 'method': DELETE})
+ if (conf_ibgp and mat_ibgp and mat_ibgp != 1) or (is_delete_all and conf_ibgp != 1):
+ requests.append({'path': url + 'ibgp/config/maximum-paths', 'method': DELETE})
return requests
- def get_delete_route_map_request(self, vrf_name, conf_afi, conf_redis, conf_route_map):
+ def get_delete_redistribute_route_map_request(self, vrf_name, conf_afi, conf_redis, conf_route_map):
addr_family = "openconfig-types:%s" % (conf_afi.upper())
conf_protocol = conf_redis['protocol'].upper()
if conf_protocol == 'CONNECTED':
@@ -779,6 +1007,17 @@ class Bgp_af(ConfigBase):
url += '%s,%s,%s/config/import-policy=%s' % (src_protocol, dst_protocol, addr_family, conf_route_map)
return ({'path': url, 'method': DELETE})
+ def get_delete_redistribute_metric_request(self, vrf_name, conf_afi, conf_redis):
+ addr_family = "openconfig-types:%s" % (conf_afi.upper())
+ conf_protocol = conf_redis['protocol'].upper()
+ if conf_protocol == 'CONNECTED':
+ conf_protocol = "DIRECTLY_CONNECTED"
+ src_protocol = "openconfig-policy-types:%s" % (conf_protocol)
+ dst_protocol = "openconfig-policy-types:BGP"
+ url = '%s=%s/%s=' % (self.network_instance_path, vrf_name, self.table_connection_path)
+ url += '%s,%s,%s/config/metric' % (src_protocol, dst_protocol, addr_family)
+ return {'path': url, 'method': DELETE}
+
def get_delete_redistribute_requests(self, vrf_name, conf_afi, conf_safi, conf_redis_arr, is_delete_all, mat_redis_arr):
requests = []
for conf_redis in conf_redis_arr:
@@ -846,3 +1085,190 @@ class Bgp_af(ConfigBase):
match_cfg = next((have_cfg for have_cfg in have if have_cfg['vrf_name'] == vrf_name and have_cfg['bgp_as'] == as_val), None)
requests.extend(self.get_delete_single_bgp_af_request(cmd, is_delete_all, match_cfg))
return requests
+
+ def get_delete_commands_requests_for_replaced_overridden(self, want, have, state):
+ """Returns the commands and requests necessary to remove applicable
+ current configurations when state is replaced or overridden
+ """
+ commands = []
+ requests = []
+ if not have:
+ return commands, requests
+
+ for conf in have:
+ as_val = conf['bgp_as']
+ vrf_name = conf['vrf_name']
+ if conf.get('address_family') and conf['address_family'].get('afis'):
+ afi_list = conf['address_family']['afis']
+ else:
+ continue
+
+ match_cfg = next((cfg for cfg in want if cfg['vrf_name'] == vrf_name and cfg['bgp_as'] == as_val), None)
+ if not match_cfg:
+ # Delete all address-families in BGPs that are not
+ # specified in overridden
+ if state == 'overridden':
+ commands.append(conf)
+ requests.extend(self.get_delete_single_bgp_af_request(conf, True))
+ continue
+
+ match_afi_list = []
+ if match_cfg.get('address_family') and match_cfg['address_family'].get('afis'):
+ match_afi_list = match_cfg['address_family']['afis']
+
+ # Delete AF configs in BGPs that are replaced/overridden
+ afi_command_list = []
+ for afi_conf in afi_list:
+ afi_command = {}
+ afi = afi_conf['afi']
+ safi = afi_conf['safi']
+
+ match_afi_cfg = next((afi_cfg for afi_cfg in match_afi_list if afi_cfg['afi'] == afi and afi_cfg['safi'] == safi), None)
+ # Delete address-families that are not specified
+ if not match_afi_cfg:
+ afi_command_list.append(afi_conf)
+ requests.extend(self.get_delete_single_bgp_af_request({'bgp_as': as_val, 'vrf_name': vrf_name, 'address_family': {'afis': [afi_conf]}},
+ True))
+ continue
+
+ if afi == 'ipv4' and safi == 'unicast':
+ if afi_conf.get('dampening') and match_afi_cfg.get('dampening') is None:
+ afi_command['dampening'] = afi_conf['dampening']
+ requests.append(self.get_delete_dampening_request(vrf_name, afi, safi))
+
+ if afi == 'l2vpn' and safi == 'evpn':
+ for option in self.non_list_advertise_attrs:
+ if afi_conf.get(option) is not None and match_afi_cfg.get(option) is None:
+ afi_command[option] = afi_conf[option]
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, afi, safi, self.advertise_attrs_map[option]))
+
+ for option in ('rt_in', 'rt_out'):
+ if afi_conf.get(option):
+ del_rt = self._get_diff_list(afi_conf[option], match_afi_cfg.get(option, []))
+ if del_rt:
+ afi_command[option] = del_rt
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, afi, safi,
+ '{0}={1}'.format(self.advertise_attrs_map[option],
+ quote_plus(','.join(del_rt)))))
+
+ if afi_conf.get('route_advertise_list'):
+ route_adv_list = []
+ match_route_adv_list = match_afi_cfg.get('route_advertise_list', [])
+ for route_adv in afi_conf['route_advertise_list']:
+ advertise_afi = route_adv['advertise_afi']
+ route_map = route_adv.get('route_map')
+ match_route_adv = next((adv_cfg for adv_cfg in match_route_adv_list if adv_cfg['advertise_afi'] == advertise_afi), None)
+ if not match_route_adv:
+ route_adv_list.append(route_adv)
+ requests.append(self.get_delete_route_advertise_list_request(vrf_name, afi, safi, advertise_afi))
+ # Delete existing route-map before configuring
+ # new route-map.
+ elif route_map and route_map != match_route_adv.get('route_map'):
+ route_adv_list.append(route_adv)
+ requests.append(self.get_delete_route_advertise_route_map_request(vrf_name, afi, safi, advertise_afi, route_map))
+
+ if route_adv_list:
+ afi_command['route_advertise_list'] = route_adv_list
+
+ if afi_conf.get('vnis'):
+ vni_command_list = []
+ match_vni_list = match_afi_cfg.get('vnis', [])
+ for vni_conf in afi_conf['vnis']:
+ vni_number = vni_conf['vni_number']
+ match_vni = next((vni_cfg for vni_cfg in match_vni_list if vni_cfg['vni_number'] == vni_number), None)
+ # Delete entire VNIs that are not specified
+ if not match_vni:
+ vni_command_list.append(vni_conf)
+ requests.append(self.get_delete_vni_request(vrf_name, afi, safi, vni_number))
+ else:
+ vni_command = {}
+ for option in ('advertise_default_gw', 'advertise_svi_ip', 'rd'):
+ if vni_conf.get(option) is not None and match_vni.get(option) is None:
+ vni_command[option] = vni_conf[option]
+ requests.append(self.get_delete_vni_cfg_attr_request(vrf_name, afi, safi, vni_number,
+ self.advertise_attrs_map[option]))
+
+ for option in ('rt_in', 'rt_out'):
+ if vni_conf.get(option):
+ del_rt = self._get_diff_list(vni_conf[option], match_vni.get(option, []))
+ if del_rt:
+ vni_command[option] = del_rt
+ requests.append(self.get_delete_vni_cfg_attr_request(vrf_name, afi, safi, vni_number,
+ '{0}={1}'.format(self.advertise_attrs_map[option],
+ quote_plus(','.join(del_rt)))))
+
+ if vni_command:
+ vni_command['vni_number'] = vni_number
+ vni_command_list.append(vni_command)
+
+ if vni_command_list:
+ afi_command['vnis'] = vni_command_list
+
+ elif afi in ['ipv4', 'ipv6'] and safi == 'unicast':
+ if afi_conf.get('network'):
+ del_network = self._get_diff_list(afi_conf['network'], match_afi_cfg.get('network', []))
+ if del_network:
+ afi_command['network'] = del_network
+ requests.extend(self.get_delete_network_request(vrf_name, afi, safi, del_network, True, None))
+
+ if afi_conf.get('redistribute'):
+ match_redis_list = match_afi_cfg.get('redistribute')
+ if not match_redis_list:
+ afi_command['redistribute'] = afi_conf['redistribute']
+ requests.extend(self.get_delete_redistribute_requests(vrf_name, afi, safi, afi_conf['redistribute'], True, None))
+ else:
+ redis_command_list = []
+ for redis_conf in afi_conf['redistribute']:
+ protocol = redis_conf['protocol']
+ match_redis = next((redis_cfg for redis_cfg in match_redis_list if redis_cfg['protocol'] == protocol), None)
+ # Delete complete protocol redistribute
+ # configuration if not specified
+ if not match_redis:
+ redis_command_list.append(redis_conf)
+ requests.extend(self.get_delete_redistribute_requests(vrf_name, afi, safi, [redis_conf], True, None))
+ # Delete metric, route_map for specified
+ # protocol if they are not specified.
+ else:
+ redis_command = {}
+ if redis_conf.get('metric') is not None and match_redis.get('metric') is None:
+ redis_command['metric'] = redis_conf['metric']
+ requests.append(self.get_delete_redistribute_metric_request(vrf_name, afi, redis_conf))
+ if redis_conf.get('route_map') is not None and match_redis.get('route_map') is None:
+ redis_command['route_map'] = redis_conf['route_map']
+ requests.append(self.get_delete_redistribute_route_map_request(vrf_name, afi, redis_conf, redis_command['route_map']))
+
+ if redis_command:
+ redis_command['protocol'] = protocol
+ redis_command_list.append(redis_command)
+
+ if redis_command_list:
+ afi_command['redistribute'] = redis_command_list
+
+ if afi_conf.get('max_path'):
+ max_path_command = {}
+ match_max_path = match_afi_cfg.get('max_path', {})
+ if afi_conf['max_path'].get('ibgp') and afi_conf['max_path']['ibgp'] != 1 and match_max_path.get('ibgp') is None:
+ max_path_command['ibgp'] = afi_conf['max_path']['ibgp']
+ if afi_conf['max_path'].get('ebgp') and afi_conf['max_path']['ebgp'] != 1 and match_max_path.get('ebgp') is None:
+ max_path_command['ebgp'] = afi_conf['max_path']['ebgp']
+
+ if max_path_command:
+ afi_command['max_path'] = max_path_command
+ requests.extend(self.get_delete_max_path_requests(vrf_name, afi, safi, afi_command['max_path'], False, afi_command['max_path']))
+
+ if afi_command:
+ afi_command['afi'] = afi
+ afi_command['safi'] = safi
+ afi_command_list.append(afi_command)
+
+ if afi_command_list:
+ commands.append({'bgp_as': as_val, 'vrf_name': vrf_name, 'address_family': {'afis': afi_command_list}})
+
+ return commands, requests
+
+ @staticmethod
+ def _get_diff_list(base_list, compare_with_list):
+ if not compare_with_list:
+ return base_list
+
+ return [item for item in base_list if item not in compare_with_list]
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_as_paths/bgp_as_paths.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_as_paths/bgp_as_paths.py
index dc2b023b1..d57cf36e2 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_as_paths/bgp_as_paths.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_as_paths/bgp_as_paths.py
@@ -17,13 +17,13 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.c
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
to_list,
+ search_obj_in_list
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
update_states,
get_diff,
)
-from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import to_request
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
to_request,
edit_config
@@ -120,50 +120,142 @@ class Bgp_as_paths(ConfigBase):
commands = []
requests = []
state = self._module.params['state']
- for i in want:
- if i.get('members'):
- temp = []
- for j in i['members']:
- temp.append(j.replace('\\\\', '\\'))
- i['members'] = temp
- diff = get_diff(want, have)
- for i in want:
- if i.get('members'):
- temp = []
- for j in i['members']:
- temp.append(j.replace('\\', '\\\\'))
- i['members'] = temp
if state == 'overridden':
- commands, requests = self._state_overridden(want, have, diff)
+ commands, requests = self._state_overridden(want, have)
elif state == 'deleted':
- commands, requests = self._state_deleted(want, have, diff)
+ commands, requests = self._state_deleted(want, have)
elif state == 'merged':
+ diff = get_diff(want, have)
commands, requests = self._state_merged(want, have, diff)
elif state == 'replaced':
- commands, requests = self._state_replaced(want, have, diff)
+ commands, requests = self._state_replaced(want, have)
return commands, requests
- @staticmethod
- def _state_replaced(**kwargs):
+ def _state_replaced(self, want, have):
""" The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
+ add_commands = []
+ del_commands = []
commands = []
- return commands
+ requests = []
+
+ for cmd in want:
+ # Set action to deny if not specfied for as-path-list
+ if cmd.get('permit') is None:
+ cmd['permit'] = False
+
+ match = search_obj_in_list(cmd['name'], have, 'name')
+ # Replace existing as-path-list
+ if match:
+ # Delete entire as-path-list if no members are specified
+ if not cmd.get('members'):
+ del_commands.append(match)
+ requests.append(self.get_delete_single_as_path_request(cmd['name']))
+ else:
+ if cmd['permit'] != match['permit']:
+ # If action is changed, delete the entire as-path list
+ # and add the given configuration
+ del_commands.append(match)
+ requests.append(self.get_delete_single_as_path_request(cmd['name']))
+ add_commands.append(cmd)
+ requests.append(self.get_new_add_request(cmd))
+ else:
+ want_members_set = set(cmd['members'])
+ have_members_set = set(match['members'])
+ members_to_delete = list(have_members_set.difference(want_members_set))
+ members_to_add = list(want_members_set.difference(have_members_set))
+ if members_to_delete:
+ del_commands.append({'name': cmd['name'], 'permit': cmd['permit'], 'members': members_to_delete})
+ if len(members_to_delete) == len(match['members']):
+ requests.append(self.get_delete_single_as_path_request(cmd['name']))
+ else:
+ requests.append(self.get_delete_single_as_path_member_request(cmd['name'], members_to_delete))
+
+ if members_to_add:
+ add_commands.append({'name': cmd['name'], 'permit': cmd['permit'], 'members': members_to_add})
+ requests.append(self.get_new_add_request({'name': cmd['name'], 'permit': cmd['permit'], 'members': members_to_add}))
+ else:
+ if cmd.get('members'):
+ add_commands.append(cmd)
+ requests.append(self.get_new_add_request(cmd))
+
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+
+ if add_commands:
+ commands.extend(update_states(add_commands, 'replaced'))
+
+ return commands, requests
- @staticmethod
- def _state_overridden(**kwargs):
+ def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
+ add_commands = []
+ del_commands = []
commands = []
- return commands
+ requests = []
+
+ # Delete as-path-lists that are not specified
+ for cfg in have:
+ if not search_obj_in_list(cfg['name'], want, 'name'):
+ del_commands.append(cfg)
+ requests.append(self.get_delete_single_as_path_request(cfg['name']))
+
+ for cmd in want:
+ # Set action to deny if not specfied for as-path-list
+ if cmd.get('permit') is None:
+ cmd['permit'] = False
+
+ match = search_obj_in_list(cmd['name'], have, 'name')
+ # Override existing as-path-list
+ if match:
+ # Delete entire as-path-list if no members are specified
+ if not cmd.get('members'):
+ del_commands.append(match)
+ requests.append(self.get_delete_single_as_path_request(cmd['name']))
+ else:
+ if cmd['permit'] != match['permit']:
+ # If action is changed, delete the entire as-path list
+ # and add the given configuration
+ del_commands.append(match)
+ requests.append(self.get_delete_single_as_path_request(cmd['name']))
+ add_commands.append(cmd)
+ requests.append(self.get_new_add_request(cmd))
+ else:
+ want_members_set = set(cmd['members'])
+ have_members_set = set(match['members'])
+ members_to_delete = list(have_members_set.difference(want_members_set))
+ members_to_add = list(want_members_set.difference(have_members_set))
+ if members_to_delete:
+ del_commands.append({'name': cmd['name'], 'permit': cmd['permit'], 'members': members_to_delete})
+ if len(members_to_delete) == len(match['members']):
+ requests.append(self.get_delete_single_as_path_request(cmd['name']))
+ else:
+ requests.append(self.get_delete_single_as_path_member_request(cmd['name'], members_to_delete))
+
+ if members_to_add:
+ add_commands.append({'name': cmd['name'], 'permit': cmd['permit'], 'members': members_to_add})
+ requests.append(self.get_new_add_request({'name': cmd['name'], 'permit': cmd['permit'], 'members': members_to_add}))
+ else:
+ if cmd.get('members'):
+ add_commands.append(cmd)
+ requests.append(self.get_new_add_request(cmd))
+
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+
+ if add_commands:
+ commands.extend(update_states(add_commands, 'overridden'))
+
+ return commands, requests
def _state_merged(self, want, have, diff):
""" The command generator when state is merged
@@ -173,6 +265,19 @@ class Bgp_as_paths(ConfigBase):
the current configuration
"""
commands = diff
+ for cmd in commands:
+ match = next((item for item in have if item['name'] == cmd['name']), None)
+ if match:
+ # Use existing action if not specified
+ if cmd.get('permit') is None:
+ cmd['permit'] = match['permit']
+ elif cmd['permit'] != match['permit']:
+ action = 'permit' if match['permit'] else 'deny'
+ self._module.fail_json(msg='Cannot override existing action {0} of {1}'.format(action, cmd['name']))
+ # Set action to deny if not specfied for a new as-path-list
+ elif cmd.get('permit') is None:
+ cmd['permit'] = False
+
requests = self.get_modify_as_path_list_requests(commands, have)
if commands and len(requests) > 0:
commands = update_states(commands, "merged")
@@ -181,7 +286,7 @@ class Bgp_as_paths(ConfigBase):
return commands, requests
- def _state_deleted(self, want, have, diff):
+ def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
@@ -239,7 +344,7 @@ class Bgp_as_paths(ConfigBase):
requests.append(request)
return requests
- def get_delete_single_as_path_member_requests(self, name, members):
+ def get_delete_single_as_path_member_request(self, name, members):
url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:"
url = url + "bgp-defined-sets/as-path-sets/as-path-set={name}/config/{members_param}"
method = "DELETE"
@@ -248,15 +353,8 @@ class Bgp_as_paths(ConfigBase):
request = {"path": url.format(name=name, members_param=members_str), "method": method}
return request
- def get_delete_single_as_path_requests(self, name):
- url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/as-path-sets/as-path-set={}"
- method = "DELETE"
- request = {"path": url.format(name), "method": method}
- return request
-
- def get_delete_single_as_path_action_requests(self, name):
+ def get_delete_single_as_path_request(self, name):
url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/as-path-sets/as-path-set={}"
- url = url + "/openconfig-bgp-policy-ext:action"
method = "DELETE"
request = {"path": url.format(name), "method": method}
return request
@@ -270,25 +368,18 @@ class Bgp_as_paths(ConfigBase):
name = cmd['name']
members = cmd['members']
permit = cmd['permit']
- if members:
- diff_members = []
- for item in have:
- if item['name'] == name:
- for member_want in cmd['members']:
- if item['members']:
- if str(member_want) in item['members']:
- diff_members.append(member_want)
- if diff_members:
- requests.append(self.get_delete_single_as_path_member_requests(name, diff_members))
-
- elif permit:
- for item in have:
- if item['name'] == name:
- requests.append(self.get_delete_single_as_path_action_requests(name))
- else:
- for item in have:
- if item['name'] == name:
- requests.append(self.get_delete_single_as_path_requests(name))
+ match = next((item for item in have if item['name'] == cmd['name']), None)
+ if match:
+ if members:
+ if match.get('members'):
+ del_members = set(match['members']).intersection(set(members))
+ if del_members:
+ if len(del_members) == len(match['members']):
+ requests.append(self.get_delete_single_as_path_request(name))
+ else:
+ requests.append(self.get_delete_single_as_path_member_request(name, del_members))
+ else:
+ requests.append(self.get_delete_single_as_path_request(name))
return requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_communities/bgp_communities.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_communities/bgp_communities.py
index 670fb26d3..82ed70a3f 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_communities/bgp_communities.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_communities/bgp_communities.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -62,6 +62,13 @@ class Bgp_communities(ConfigBase):
'bgp_communities',
]
+ standard_communities_map = {
+ 'no_peer': 'NOPEER',
+ 'no_export': 'NO_EXPORT',
+ 'no_advertise': 'NO_ADVERTISE',
+ 'local_as': 'NO_EXPORT_SUBCONFED'
+ }
+
def __init__(self, module):
super(Bgp_communities, self).__init__(module)
@@ -89,6 +96,7 @@ class Bgp_communities(ConfigBase):
existing_bgp_communities_facts = self.get_bgp_communities_facts()
commands, requests = self.set_config(existing_bgp_communities_facts)
+
if commands and len(requests) > 0:
if not self._module.check_mode:
try:
@@ -116,6 +124,13 @@ class Bgp_communities(ConfigBase):
to the desired configuration
"""
want = self._module.params['config']
+ if want:
+ for conf in want:
+ if conf.get("match", None):
+ conf["match"] = conf["match"].upper()
+ if conf.get("members", {}) and conf['members'].get("regex", []):
+ conf['members']['regex'].sort()
+
have = existing_bgp_communities_facts
resp = self.set_state(want, have)
return to_list(resp)
@@ -138,17 +153,16 @@ class Bgp_communities(ConfigBase):
# fp.write('comm: have: ' + str(have) + '\n')
# fp.write('comm: diff: ' + str(diff) + '\n')
if state == 'overridden':
- commands, requests = self._state_overridden(want, have, diff)
+ commands, requests = self._state_overridden(want, have)
elif state == 'deleted':
- commands, requests = self._state_deleted(want, have, diff)
+ commands, requests = self._state_deleted(want, have)
elif state == 'merged':
commands, requests = self._state_merged(want, have, diff)
elif state == 'replaced':
- commands, requests = self._state_replaced(want, have, diff)
+ commands, requests = self._state_replaced(want, have)
return commands, requests
- @staticmethod
- def _state_replaced(**kwargs):
+ def _state_replaced(self, want, have):
""" The command generator when state is replaced
:rtype: A list
@@ -156,10 +170,13 @@ class Bgp_communities(ConfigBase):
to the desired configuration
"""
commands = []
- return commands
+ requests = []
+
+ commands, requests = self.get_replaced_overridden_config(want, have, "replaced")
- @staticmethod
- def _state_overridden(**kwargs):
+ return commands, requests
+
+ def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
@@ -167,7 +184,11 @@ class Bgp_communities(ConfigBase):
to the desired configuration
"""
commands = []
- return commands
+ requests = []
+
+ commands, requests = self.get_replaced_overridden_config(want, have, "overridden")
+
+ return commands, requests
def _state_merged(self, want, have, diff):
""" The command generator when state is merged
@@ -177,7 +198,7 @@ class Bgp_communities(ConfigBase):
the current configuration
"""
commands = diff
- requests = self.get_modify_bgp_community_requests(commands, have)
+ requests = self.get_modify_bgp_community_requests(commands, have, "merged")
if commands and len(requests) > 0:
commands = update_states(commands, "merged")
else:
@@ -185,7 +206,7 @@ class Bgp_communities(ConfigBase):
return commands, requests
- def _state_deleted(self, want, have, diff):
+ def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
@@ -217,28 +238,18 @@ class Bgp_communities(ConfigBase):
return commands, requests
- def get_delete_single_bgp_community_member_requests(self, name, type, members):
+ def get_delete_single_bgp_community_member_requests(self, name, members):
requests = []
for member in members:
url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:"
url = url + "bgp-defined-sets/community-sets/community-set={name}/config/{members_param}"
method = "DELETE"
- memberstr = member
- if type == 'expanded':
- memberstr = 'REGEX:' + member
- members_params = {'community-member': memberstr}
+ members_params = {'community-member': member}
members_str = urlencode(members_params)
request = {"path": url.format(name=name, members_param=members_str), "method": method}
requests.append(request)
return requests
- def get_delete_all_members_bgp_community_requests(self, name):
- url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:"
- url = url + "bgp-defined-sets/community-sets/community-set={}/config/community-member"
- method = "DELETE"
- request = {"path": url.format(name), "method": method}
- return request
-
def get_delete_single_bgp_community_requests(self, name):
url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/community-sets/community-set={}"
method = "DELETE"
@@ -255,70 +266,90 @@ class Bgp_communities(ConfigBase):
return requests
def get_delete_bgp_communities(self, commands, have, is_delete_all):
- # with open('/root/ansible_log.log', 'a+') as fp:
- # fp.write('bgp_commmunities: delete requests ************** \n')
requests = []
if is_delete_all:
requests = self.get_delete_all_bgp_communities(commands)
else:
for cmd in commands:
name = cmd['name']
- type = cmd['type']
- members = cmd['members']
- if members:
- if members['regex']:
- diff_members = []
- for item in have:
- if item['name'] == name and item['members']:
- for member_want in members['regex']:
- if str(member_want) in item['members']['regex']:
- diff_members.append(member_want)
- if diff_members:
- requests.extend(self.get_delete_single_bgp_community_member_requests(name, type, diff_members))
- else:
- for item in have:
- if item['name'] == name:
- if item['members']:
- requests.append(self.get_delete_all_members_bgp_community_requests(name))
- else:
- for item in have:
- if item['name'] == name:
+ members = cmd.get('members', None)
+ cmd_type = cmd['type']
+ diff_members = []
+
+ for item in have:
+ if item['name'] == name:
+ if 'permit' not in cmd or cmd['permit'] is None:
+ cmd['permit'] = item['permit']
+
+ if cmd == item:
requests.append(self.get_delete_single_bgp_community_requests(name))
+ break
+
+ if cmd_type == "standard":
+ for attr in self.standard_communities_map:
+ if cmd.get(attr, None) and item[attr] and cmd[attr] == item[attr]:
+ diff_members.append(self.standard_communities_map[attr])
+
+ if members:
+ if members.get('regex', []):
+ for member_want in members['regex']:
+ if item.get('members', None) and item['members'].get('regex', []):
+ if str(member_want) in item['members']['regex']:
+ diff_members.append("REGEX:" + str(member_want))
+ else:
+ requests.append(self.get_delete_single_bgp_community_requests(name))
+
+ else:
+ if cmd_type == "standard":
+ no_attr = True
+ for attr in self.standard_communities_map:
+ if cmd.get(attr, None):
+ no_attr = False
+ break
+ if no_attr:
+ requests.append(self.get_delete_single_bgp_community_requests(name))
+ else:
+ requests.append(self.get_delete_single_bgp_community_requests(name))
+ break
+
+ if diff_members:
+ requests.extend(self.get_delete_single_bgp_community_member_requests(name, diff_members))
- # with open('/root/ansible_log.log', 'a+') as fp:
- # fp.write('bgp_commmunities: delete requests' + str(requests) + '\n')
return requests
def get_new_add_request(self, conf):
url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/community-sets"
method = "PATCH"
- # members = conf['members']
- # members_str = ', '.join(members)
- # members_list = list()
- # for member in members.split(','):
- # members_list.append(str(member))
+ community_members = []
+ community_action = ""
if 'match' not in conf:
conf['match'] = "ANY"
- # with open('/root/ansible_log.log', 'a+') as fp:
- # fp.write('bgp_communities: conf' + str(conf) + '\n')
- if 'local_as' in conf and conf['local_as']:
- conf['members']['regex'].append("NO_EXPORT_SUBCONFED")
- if 'no_peer' in conf and conf['no_peer']:
- conf['members']['regex'].append("NOPEER")
- if 'no_export' in conf and conf['no_export']:
- conf['members']['regex'].append("NO_EXPORT")
- if 'no_advertise' in conf and conf['no_advertise']:
- conf['members']['regex'].append("NO_ADVERTISE")
- input_data = {'name': conf['name'], 'members_list': conf['members']['regex'], 'match': conf['match']}
- if conf['type'] == 'expanded':
- input_data['regex'] = "REGEX:"
- else:
- input_data['regex'] = ""
+
+ if conf['type'] == 'standard':
+ for attr in self.standard_communities_map:
+ if attr in conf and conf[attr]:
+ community_members.append(self.standard_communities_map[attr])
+ if 'members' in conf and conf['members'] and conf['members'].get('regex', []):
+ for i in conf['members']['regex']:
+ community_members.extend([str(i)])
+ if not community_members:
+ self._module.fail_json(msg='Cannot create standard community-list {0} without community attributes'.format(conf['name']))
+
+ elif conf['type'] == 'expanded':
+ if 'members' in conf and conf['members'] and conf['members'].get('regex', []):
+ for i in conf['members']['regex']:
+ community_members.extend(["REGEX:" + str(i)])
+ if not community_members:
+ self._module.fail_json(msg='Cannot create expanded community-list {0} without community attributes'.format(conf['name']))
+
if conf['permit']:
- input_data['permit'] = "PERMIT"
+ community_action = "PERMIT"
else:
- input_data['permit'] = "DENY"
+ community_action = "DENY"
+
+ input_data = {'name': conf['name'], 'members_list': community_members, 'match': conf['match'].upper(), 'permit': community_action}
+
payload_template = """
{
"openconfig-bgp-policy:community-sets": {
@@ -328,7 +359,7 @@ class Bgp_communities(ConfigBase):
"config": {
"community-set-name": "{{name}}",
"community-member": [
- {% for member in members_list %}"{{regex}}{{member}}"{%- if not loop.last -%},{% endif %}{%endfor%}
+ {% for member in members_list %}"{{member}}"{%- if not loop.last -%},{% endif %}{%endfor%}
],
"openconfig-bgp-policy-ext:action": "{{permit}}",
"match-set-options": "{{match}}"
@@ -342,27 +373,118 @@ class Bgp_communities(ConfigBase):
intended_payload = t.render(input_data)
ret_payload = json.loads(intended_payload)
request = {"path": url, "method": method, "data": ret_payload}
- # with open('/root/ansible_log.log', 'a+') as fp:
- # fp.write('bgp_communities: request' + str(request) + '\n')
+
return request
- def get_modify_bgp_community_requests(self, commands, have):
+ def get_modify_bgp_community_requests(self, commands, have, cur_state):
requests = []
if not commands:
return requests
for conf in commands:
- for item in have:
- if item['name'] == conf['name']:
- if 'type' not in conf:
- conf['type'] = item['type']
- if 'permit' not in conf:
- conf['permit'] = item['permit']
- if 'match' not in conf:
- conf['match'] = item['match']
- if 'members' not in conf:
- conf['members'] = item['members']
+ if cur_state == "merged":
+ for item in have:
+ if item['name'] == conf['name']:
+ if 'type' not in conf:
+ conf['type'] = item['type']
+ if 'permit' not in conf or conf['permit'] is None:
+ conf['permit'] = item['permit']
+ if 'match' not in conf:
+ conf['match'] = item['match']
+ if conf['type'] == "standard":
+ for attr in self.standard_communities_map:
+ if attr not in conf and attr in item:
+ conf[attr] = item[attr]
+ else:
+ if 'members' not in conf:
+ if item.get('members', {}) and item['members'].get('regex', []):
+ conf['members'] = {'regex': item['members']['regex']}
+ else:
+ conf['members'] = item['members']
+ break
+
new_req = self.get_new_add_request(conf)
if new_req:
requests.append(new_req)
return requests
+
+ def get_replaced_overridden_config(self, want, have, cur_state):
+ commands, requests = [], []
+
+ commands_del, requests_del = [], []
+ commands_add, requests_add = [], []
+
+ for conf in want:
+ name = conf['name']
+ in_have = False
+ for have_conf in have:
+ if have_conf['name'] == name:
+ in_have = True
+ if have_conf['type'] != conf['type']:
+ # If both community list are of same name but different types
+ commands_del.append(have_conf)
+ commands_add.append(conf)
+ else:
+ is_change = False
+
+ if have_conf['permit'] != conf['permit']:
+ is_change = True
+
+ if have_conf['match'] != conf['match']:
+ is_change = is_delete = True
+
+ if conf["type"] == "standard":
+ no_attr = True
+ for attr in self.standard_communities_map:
+ if not conf.get(attr, None):
+ if have_conf.get(attr, None):
+ is_change = True
+ else:
+ no_attr = False
+ if not have_conf.get(attr, None):
+ is_change = True
+
+ if no_attr:
+ # Since standard type needs atleast one attribute to exist
+ self._module.fail_json(msg='Cannot create standard community-list {0} without community attributes'.format(conf['name']))
+ else:
+ members = conf.get('members', {})
+ if members and members.get('regex', []):
+ if have_conf.get('members', {}) and have_conf['members'].get('regex', []):
+ if set(have_conf['members']['regex']).symmetric_difference(set(members['regex'])):
+ is_change = True
+ else:
+ # If there are no members in any community list of want, then
+ # that particular community list request to be ignored since
+ # expanded type needs community-member to exist
+ self._module.fail_json(msg='Cannot create expanded community-list {0} without community attributes'.format(conf['name']))
+
+ if is_change:
+ commands_add.append(conf)
+ commands_del.append(have_conf)
+ break
+
+ if not in_have:
+ commands_add.append(conf)
+
+ if cur_state == "overridden":
+ for have_conf in have:
+ in_want = next((conf for conf in want if conf['name'] == have_conf['name']), None)
+ if not in_want:
+ commands_del.append(have_conf)
+
+ if commands_del:
+ requests_del = self.get_delete_bgp_communities(commands_del, have, False)
+
+ if len(requests_del) > 0:
+ commands.extend(update_states(commands_del, "deleted"))
+ requests.extend(requests_del)
+
+ if commands_add:
+ requests_add = self.get_modify_bgp_community_requests(commands_add, have, cur_state)
+
+ if len(requests_add) > 0:
+ commands.extend(update_states(commands_add, cur_state))
+ requests.extend(requests_add)
+
+ return commands, requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_ext_communities/bgp_ext_communities.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_ext_communities/bgp_ext_communities.py
index 751f88e48..8cd9953e6 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_ext_communities/bgp_ext_communities.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_ext_communities/bgp_ext_communities.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -60,6 +60,11 @@ class Bgp_ext_communities(ConfigBase):
'bgp_ext_communities',
]
+ standard_communities_map = {
+ "route_origin": "route-origin",
+ "route_target": "route-target"
+ }
+
def __init__(self, module):
super(Bgp_ext_communities, self).__init__(module)
@@ -87,6 +92,7 @@ class Bgp_ext_communities(ConfigBase):
existing_bgp_ext_communities_facts = self.get_bgp_ext_communities_facts()
commands, requests = self.set_config(existing_bgp_ext_communities_facts)
+
if commands and len(requests) > 0:
if not self._module.check_mode:
try:
@@ -114,6 +120,21 @@ class Bgp_ext_communities(ConfigBase):
to the desired configuration
"""
want = self._module.params['config']
+ if want:
+ for conf in want:
+ cmd_type = conf.get("type", None)
+ if cmd_type and conf.get("match", None):
+ conf['match'] = conf['match'].lower()
+ if cmd_type and conf.get("members", {}):
+ if cmd_type == "expanded":
+ if conf['members'].get("regex", []):
+ conf['members']['regex'].sort()
+ else:
+ if conf['members'].get("route_origin", []):
+ conf['members']['route_origin'].sort()
+ if conf['members'].get("route_target", []):
+ conf['members']['route_target'].sort()
+
have = existing_bgp_ext_communities_facts
resp = self.set_state(want, have)
return to_list(resp)
@@ -133,17 +154,16 @@ class Bgp_ext_communities(ConfigBase):
new_want = self.validate_type(want)
diff = get_diff(new_want, have)
if state == 'overridden':
- commands, requests = self._state_overridden(want, have, diff)
+ commands, requests = self._state_overridden(want, have)
elif state == 'deleted':
- commands, requests = self._state_deleted(want, have, diff)
+ commands, requests = self._state_deleted(want, have)
elif state == 'merged':
commands, requests = self._state_merged(want, have, diff)
elif state == 'replaced':
- commands, requests = self._state_replaced(want, have, diff)
+ commands, requests = self._state_replaced(want, have)
return commands, requests
- @staticmethod
- def _state_replaced(**kwargs):
+ def _state_replaced(self, want, have):
""" The command generator when state is replaced
:rtype: A list
@@ -151,10 +171,13 @@ class Bgp_ext_communities(ConfigBase):
to the desired configuration
"""
commands = []
- return commands
+ requests = []
+
+ commands, requests = self.get_replaced_overridden_config(want, have, "replaced")
- @staticmethod
- def _state_overridden(**kwargs):
+ return commands, requests
+
+ def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
@@ -162,7 +185,11 @@ class Bgp_ext_communities(ConfigBase):
to the desired configuration
"""
commands = []
- return commands
+ requests = []
+
+ commands, requests = self.get_replaced_overridden_config(want, have, "overridden")
+
+ return commands, requests
def _state_merged(self, want, have, diff):
""" The command generator when state is merged
@@ -172,7 +199,7 @@ class Bgp_ext_communities(ConfigBase):
the current configuration
"""
commands = diff
- requests = self.get_modify_bgp_ext_community_requests(commands, have)
+ requests = self.get_modify_bgp_ext_community_requests(commands, have, "merged")
if commands and len(requests) > 0:
commands = update_states(commands, "merged")
else:
@@ -180,7 +207,7 @@ class Bgp_ext_communities(ConfigBase):
return commands, requests
- def _state_deleted(self, want, have, diff):
+ def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
@@ -204,7 +231,7 @@ class Bgp_ext_communities(ConfigBase):
return commands, requests
- def get_delete_single_bgp_ext_community_member_requests(self, name, type, members):
+ def get_delete_single_bgp_ext_community_member_requests(self, name, members):
requests = []
for member in members:
url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:"
@@ -216,13 +243,6 @@ class Bgp_ext_communities(ConfigBase):
requests.append(request)
return requests
- def get_delete_all_members_bgp_ext_community_requests(self, name):
- url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:"
- url = url + "bgp-defined-sets/ext-community-sets/ext-community-set={}/config/ext-community-member"
- method = "DELETE"
- request = {"path": url.format(name), "method": method}
- return request
-
def get_delete_single_bgp_ext_community_requests(self, name):
url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/ext-community-sets/ext-community-set={}"
method = "DELETE"
@@ -245,71 +265,79 @@ class Bgp_ext_communities(ConfigBase):
else:
for cmd in commands:
name = cmd['name']
- type = cmd['type']
- members = cmd['members']
- if members:
- if members['regex'] or members['route_origin'] or members['route_target']:
- diff_members = []
- for item in have:
- if item['name'] == name and item['members']:
- if members['regex']:
+ cmd_type = cmd['type']
+ members = cmd.get('members', None)
+ diff_members = []
+
+ for item in have:
+ if item["name"] == name:
+ if 'permit' not in cmd or cmd['permit'] is None:
+ cmd['permit'] = item['permit']
+ if cmd == item:
+ requests.append(self.get_delete_single_bgp_ext_community_requests(name))
+ break
+
+ if members:
+ if cmd_type == "expanded":
+ if members.get('regex', []):
for member_want in members['regex']:
- if str(member_want) in item['members']['regex']:
- diff_members.append('REGEX:' + str(member_want))
- if members['route_origin']:
- for member_want in members['route_origin']:
- if str(member_want) in item['members']['route_origin']:
- diff_members.append("route-origin:" + str(member_want))
- if members['route_target']:
- for member_want in members['route_target']:
- if str(member_want) in item['members']['route_target']:
- diff_members.append("route-target:" + str(member_want))
- if diff_members:
- requests.extend(self.get_delete_single_bgp_ext_community_member_requests(name, type, diff_members))
- else:
- for item in have:
- if item['name'] == name:
- if item['members']:
- requests.append(self.get_delete_all_members_bgp_ext_community_requests(name))
- else:
- for item in have:
- if item['name'] == name:
+ if item.get("members", None) and item['members'].get('regex', []):
+ if str(member_want) in item['members']['regex']:
+ diff_members.append("REGEX:" + str(member_want))
+ else:
+ requests.append(self.get_delete_single_bgp_ext_community_requests(name))
+ else:
+ no_members = True
+ for attr in self.standard_communities_map:
+ if members.get(attr, []):
+ no_members = False
+ for member_want in members[attr]:
+ if item.get("members", None) and item['members'].get(attr, []):
+ if str(member_want) in item['members'][attr]:
+ diff_members.append(self.standard_communities_map[attr] + ":" + str(member_want))
+ if no_members:
+ requests.append(self.get_delete_single_bgp_ext_community_requests(name))
+ else:
requests.append(self.get_delete_single_bgp_ext_community_requests(name))
+ break
+
+ if diff_members:
+ requests.extend(self.get_delete_single_bgp_ext_community_member_requests(name, diff_members))
+
return requests
def get_new_add_request(self, conf):
url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/ext-community-sets"
method = "PATCH"
- members = conf.get('members', None)
+ community_members = []
+ community_action = ""
+
if 'match' not in conf:
conf['match'] = "ANY"
- else:
- conf['match'] = conf['match'].upper()
- input_data = {'name': conf['name'], 'match': conf['match']}
-
- input_data['members_list'] = list()
- if members:
- regex = members.get('regex', None)
- if regex:
- input_data['members_list'].extend(["REGEX:" + cfg for cfg in regex])
- else:
- route_target = members.get('route_target', None)
- if route_target:
- input_data['members_list'].extend(["route-target:" + cfg for cfg in route_target])
- route_origin = members.get('route_origin', None)
- if route_origin:
- input_data['members_list'].extend(["route-origin:" + cfg for cfg in route_origin])
if conf['type'] == 'expanded':
- input_data['regex'] = "REGEX:"
- else:
- input_data['regex'] = ""
+ if 'members' in conf and conf['members'] and conf['members'].get('regex', []):
+ for i in conf['members']['regex']:
+ community_members.extend(["REGEX:" + str(i)])
+ elif conf['type'] == 'standard':
+ for attr in self.standard_communities_map:
+ if 'members' in conf and conf['members'] and conf['members'].get(attr, []):
+ for i in conf['members'][attr]:
+ community_members.extend([self.standard_communities_map[attr] + ":" + str(i)])
+
+ if not community_members:
+ self._module.fail_json(msg='Cannot create {0} community-list {1} without community attributes'.format(conf['type'], conf['name']))
+ return {}
+
if conf['permit']:
- input_data['permit'] = "PERMIT"
+ community_action = "PERMIT"
else:
- input_data['permit'] = "DENY"
+ community_action = "DENY"
+
+ input_data = {'name': conf['name'], 'members_list': community_members, 'match': conf['match'].upper(), 'permit': community_action}
+
payload_template = """
{
"openconfig-bgp-policy:ext-community-sets": {
@@ -335,23 +363,37 @@ class Bgp_ext_communities(ConfigBase):
request = {"path": url, "method": method, "data": ret_payload}
return request
- def get_modify_bgp_ext_community_requests(self, commands, have):
+ def get_modify_bgp_ext_community_requests(self, commands, have, cur_state):
requests = []
if not commands:
return requests
for conf in commands:
- for item in have:
- if item['name'] == conf['name']:
- if 'type' not in conf:
- conf['type'] = item['type']
- if 'permit' not in conf:
- conf['permit'] = item['permit']
- if 'match' not in conf:
- conf['match'] = item['match']
- if 'members' not in conf:
- conf['members'] = item['members']
- break
+ if cur_state == "merged":
+ for item in have:
+ if item['name'] == conf['name']:
+ if 'type' not in conf:
+ conf['type'] = item['type']
+ if 'permit' not in conf or conf['permit'] is None:
+ conf['permit'] = item['permit']
+ if 'match' not in conf:
+ conf['match'] = item['match']
+ if 'members' not in conf:
+ if conf['type'] == "expanded":
+ if item.get('members', {}) and item['members'].get('regex', []):
+ conf['members'] = {'regex': item['members']['regex']}
+ else:
+ conf['members'] = item['members']
+ else:
+ no_members = True
+ for attr in self.standard_communities_map:
+ if item.get('members', {}) and item['members'].get(attr, []):
+ no_members = False
+ conf['members'] = {attr: item['members'][attr]}
+ if no_members:
+ conf['members'] = item['members']
+ break
+
new_req = self.get_new_add_request(conf)
if new_req:
requests.append(new_req)
@@ -369,3 +411,84 @@ class Bgp_ext_communities(ConfigBase):
new_want.append(cfg)
return new_want
+
+ def get_replaced_overridden_config(self, want, have, cur_state):
+ commands, requests = [], []
+
+ commands_del, requests_del = [], []
+ commands_add, requests_add = [], []
+
+ for conf in want:
+ name = conf['name']
+ in_have = False
+ for have_conf in have:
+ if have_conf['name'] == name:
+ in_have = True
+ if have_conf['type'] != conf['type']:
+ # If both extended community list are of same name but different types
+ commands_del.append(have_conf)
+ commands_add.append(conf)
+ else:
+ is_change = False
+
+ if have_conf['permit'] != conf['permit']:
+ is_change = True
+
+ if have_conf['match'] != conf['match']:
+ is_change = True
+
+ if conf["type"] == "expanded":
+ members = conf.get('members', {})
+ if members and conf['members'].get('regex', []):
+ if have_conf.get('members', {}) and have_conf['members'].get('regex', []):
+ if set(have_conf['members']['regex']).symmetric_difference(set(members['regex'])):
+ is_change = True
+ else:
+ # If there are no members in any expanded ext community list of want, then
+ # abort the playbook with an error message explaining why the specified command is not valid
+ self._module.fail_json(msg='Cannot create expanded extended community-list '
+ '{0} without community attributes'.format(conf['name']))
+ else:
+ members = conf.get('members', {})
+ no_members = True
+ for attr in self.standard_communities_map:
+ if members and conf['members'].get(attr, []):
+ no_members = False
+ if have_conf.get('members', {}) and have_conf['members'].get(attr, []):
+ if set(have_conf['members'][attr]).symmetric_difference(set(members[attr])):
+ is_change = True
+
+ if no_members:
+ # If there are no members in any standard ext community list of want, then
+ # abort the playbook with an error message explaining why the specified command is not valid
+ self._module.fail_json(msg='Cannot create standard extended community-list '
+ '{0} without community attributes'.format(conf['name']))
+
+ if is_change:
+ commands_add.append(conf)
+ commands_del.append(have_conf)
+ break
+ if not in_have:
+ commands_add.append(conf)
+
+ if cur_state == "overridden":
+ for have_conf in have:
+ in_want = next((conf for conf in want if conf['name'] == have_conf['name']), None)
+ if not in_want:
+ commands_del.append(have_conf)
+
+ if commands_del:
+ requests_del = self.get_delete_bgp_ext_communities(commands_del, have, False)
+
+ if len(requests_del) > 0:
+ commands.extend(update_states(commands_del, "deleted"))
+ requests.extend(requests_del)
+
+ if commands_add:
+ requests_add = self.get_modify_bgp_ext_community_requests(commands_add, have, cur_state)
+
+ if len(requests_add) > 0:
+ commands.extend(update_states(commands_add, cur_state))
+ requests.extend(requests_add)
+
+ return commands, requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors/bgp_neighbors.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors/bgp_neighbors.py
index 31bbec78d..9c0920832 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors/bgp_neighbors.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors/bgp_neighbors.py
@@ -27,6 +27,7 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
update_states,
get_diff,
+ remove_matching_defaults
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import (
validate_bgps,
@@ -37,6 +38,8 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import to_request
from ansible.module_utils.connection import ConnectionError
+from copy import deepcopy
+
PATCH = 'patch'
DELETE = 'delete'
@@ -47,6 +50,95 @@ TEST_KEYS = [
{'afis': {'afi': '', 'safi': ''}},
]
+default_entries = [
+ [
+ {'name': 'peer_group'},
+ {'name': 'timers'},
+ {'name': 'keepalive', 'default': 60}
+ ],
+ [
+ {'name': 'peer_group'},
+ {'name': 'timers'},
+ {'name': 'holdtime', 'default': 180}
+ ],
+ [
+ {'name': 'peer_group'},
+ {'name': 'timers'},
+ {'name': 'connect_retry', 'default': 30}
+ ],
+ [
+ {'name': 'peer_group'},
+ {'name': 'advertisement_interval', 'default': 30}
+ ],
+ [
+ {'name': 'peer_group'},
+ {'name': 'auth_pwd'},
+ {'name': 'encrypted', 'default': False}
+ ],
+ [
+ {'name': 'peer_group'},
+ {'name': 'ebgp_multihop'},
+ {'name': 'enabled', 'default': False}
+ ],
+ [
+ {'name': 'peer_group'},
+ {'name': 'passive', 'default': False}
+ ],
+ [
+ {'name': 'peer_group'},
+ {'name': 'address_family'},
+ {'name': 'afis'},
+ {'name': 'ip_afi'},
+ {'name': 'send_default_route', 'default': False}
+ ],
+ [
+ {'name': 'peer_group'},
+ {'name': 'address_family'},
+ {'name': 'afis'},
+ {'name': 'activate', 'default': False}
+ ],
+ [
+ {'name': 'peer_group'},
+ {'name': 'address_family'},
+ {'name': 'afis'},
+ {'name': 'prefix_limit'},
+ {'name': 'prevent_teardown', 'default': False}
+ ],
+ [
+ {'name': 'neighbors'},
+ {'name': 'timers'},
+ {'name': 'keepalive', 'default': 60}
+ ],
+ [
+ {'name': 'neighbors'},
+ {'name': 'timers'},
+ {'name': 'holdtime', 'default': 180}
+ ],
+ [
+ {'name': 'neighbors'},
+ {'name': 'timers'},
+ {'name': 'connect_retry', 'default': 30}
+ ],
+ [
+ {'name': 'neighbors'},
+ {'name': 'advertisement_interval', 'default': 30}
+ ],
+ [
+ {'name': 'neighbors'},
+ {'name': 'auth_pwd'},
+ {'name': 'encrypted', 'default': False}
+ ],
+ [
+ {'name': 'neighbors'},
+ {'name': 'ebgp_multihop'},
+ {'name': 'enabled', 'default': False}
+ ],
+ [
+ {'name': 'neighbors'},
+ {'name': 'passive', 'default': False}
+ ],
+]
+
class Bgp_neighbors(ConfigBase):
"""
@@ -180,7 +272,9 @@ class Bgp_neighbors(ConfigBase):
commands = have
new_have = have
else:
- new_have = self.remove_default_entries(have)
+ new_have = deepcopy(have)
+ for default_entry in default_entries:
+ remove_matching_defaults(new_have, default_entry)
d_diff = get_diff(want, new_have, TEST_KEYS, is_skeleton=True)
delete_diff = get_diff(want, d_diff, TEST_KEYS, is_skeleton=True)
commands = delete_diff
@@ -192,141 +286,6 @@ class Bgp_neighbors(ConfigBase):
commands = []
return commands, requests
- def remove_default_entries(self, data):
- new_data = []
- if not data:
- return new_data
- for conf in data:
- new_conf = {}
- as_val = conf['bgp_as']
- vrf_name = conf['vrf_name']
- new_conf['bgp_as'] = as_val
- new_conf['vrf_name'] = vrf_name
- peergroup = conf.get('peer_group', None)
- new_peergroups = []
- if peergroup is not None:
- for pg in peergroup:
- new_pg = {}
- pg_val = pg.get('name', None)
- new_pg['name'] = pg_val
- remote_as = pg.get('remote_as', None)
- new_remote = {}
- if remote_as:
- peer_as = remote_as.get('peer_as', None)
- peer_type = remote_as.get('peer_type', None)
- if peer_as is not None:
- new_remote['peer_as'] = peer_as
- if peer_type is not None:
- new_remote['peer_type'] = peer_type
- if new_remote:
- new_pg['remote_as'] = new_remote
- timers = pg.get('timers', None)
- new_timers = {}
- if timers:
- keepalive = timers.get('keepalive', None)
- holdtime = timers.get('holdtime', None)
- connect_retry = timers.get('connect_retry', None)
- if keepalive is not None and keepalive != 60:
- new_timers['keepalive'] = keepalive
- if holdtime is not None and holdtime != 180:
- new_timers['holdtime'] = holdtime
- if connect_retry is not None and connect_retry != 30:
- new_timers['connect_retry'] = connect_retry
- if new_timers:
- new_pg['timers'] = new_timers
- advertisement_interval = pg.get('advertisement_interval', None)
- if advertisement_interval is not None and advertisement_interval != 30:
- new_pg['advertisement_interval'] = advertisement_interval
- bfd = pg.get('bfd', None)
- if bfd is not None:
- new_pg['bfd'] = bfd
- capability = pg.get('capability', None)
- if capability is not None:
- new_pg['capability'] = capability
- afi = []
- address_family = pg.get('address_family', None)
- if address_family:
- if address_family.get('afis', None):
- for each in address_family['afis']:
- if each:
- tmp = {}
- if each.get('afi', None) is not None:
- tmp['afi'] = each['afi']
- if each.get('safi', None) is not None:
- tmp['safi'] = each['safi']
- if each.get('activate', None) is not None and each['activate'] is not False:
- tmp['activate'] = each['activate']
- if each.get('allowas_in', None) is not None:
- tmp['allowas_in'] = each['allowas_in']
- if each.get('ip_afi', None) is not None:
- tmp['ip_afi'] = each['ip_afi']
- if each.get('prefix_limit', None) is not None:
- tmp['prefix_limit'] = each['prefix_limit']
- if each.get('prefix_list_in', None) is not None:
- tmp['prefix_list_in'] = each['prefix_list_in']
- if each.get('prefix_list_out', None) is not None:
- tmp['prefix_list_out'] = each['prefix_list_out']
- afi.append(tmp)
- if afi and len(afi) > 0:
- afis = {}
- afis.update({'afis': afi})
- new_pg['address_family'] = afis
- if new_pg:
- new_peergroups.append(new_pg)
- if new_peergroups:
- new_conf['peer_group'] = new_peergroups
- neighbors = conf.get('neighbors', None)
- new_neighbors = []
- if neighbors is not None:
- for neighbor in neighbors:
- new_neighbor = {}
- neighbor_val = neighbor.get('neighbor', None)
- new_neighbor['neighbor'] = neighbor_val
- remote_as = neighbor.get('remote_as', None)
- new_remote = {}
- if remote_as:
- peer_as = remote_as.get('peer_as', None)
- peer_type = remote_as.get('peer_type', None)
- if peer_as is not None:
- new_remote['peer_as'] = peer_as
- if peer_type is not None:
- new_remote['peer_type'] = peer_type
- if new_remote:
- new_neighbor['remote_as'] = new_remote
- peer_group = neighbor.get('peer_group', None)
- if peer_group:
- new_neighbor['peer_group'] = peer_group
- timers = neighbor.get('timers', None)
- new_timers = {}
- if timers:
- keepalive = timers.get('keepalive', None)
- holdtime = timers.get('holdtime', None)
- connect_retry = timers.get('connect_retry', None)
- if keepalive is not None and keepalive != 60:
- new_timers['keepalive'] = keepalive
- if holdtime is not None and holdtime != 180:
- new_timers['holdtime'] = holdtime
- if connect_retry is not None and connect_retry != 30:
- new_timers['connect_retry'] = connect_retry
- if new_timers:
- new_neighbor['timers'] = new_timers
- advertisement_interval = neighbor.get('advertisement_interval', None)
- if advertisement_interval is not None and advertisement_interval != 30:
- new_neighbor['advertisement_interval'] = advertisement_interval
- bfd = neighbor.get('bfd', None)
- if bfd is not None:
- new_neighbor['bfd'] = bfd
- capability = neighbor.get('capability', None)
- if capability is not None:
- new_neighbor['capability'] = capability
- if new_neighbor:
- new_neighbors.append(new_neighbor)
- if new_neighbors:
- new_conf['neighbors'] = new_neighbors
- if new_conf:
- new_data.append(new_conf)
- return new_data
-
def build_bgp_peer_groups_payload(self, cmd, have, bgp_as, vrf_name):
requests = []
bgp_peer_group_list = []
@@ -444,7 +403,7 @@ class Bgp_neighbors(ConfigBase):
if each.get('prefix_limit', None) is not None:
pfx_lmt_cfg = get_prefix_limit_payload(each['prefix_limit'])
if pfx_lmt_cfg and afi_safi == 'L2VPN_EVPN':
- samp.update({'l2vpn-evpn': {'prefix-limit': {'config': pfx_lmt_cfg}}})
+ self._module.fail_json('Prefix limit configuration not supported for l2vpn evpn')
else:
if each.get('ip_afi', None) is not None:
afi_safi_cfg = get_ip_afi_cfg_payload(each['ip_afi'])
@@ -696,13 +655,35 @@ class Bgp_neighbors(ConfigBase):
advertisement_interval = each.get('advertisement_interval', None)
bfd = each.get('bfd', None)
capability = each.get('capability', None)
+ auth_pwd = each.get('auth_pwd', None)
+ pg_description = each.get('pg_description', None)
+ disable_connected_check = each.get('disable_connected_check', None)
+ dont_negotiate_capability = each.get('dont_negotiate_capability', None)
+ ebgp_multihop = each.get('ebgp_multihop', None)
+ enforce_first_as = each.get('enforce_first_as', None)
+ enforce_multihop = each.get('enforce_multihop', None)
+ local_address = each.get('local_address', None)
+ local_as = each.get('local_as', None)
+ override_capability = each.get('override_capability', None)
+ passive = each.get('passive', None)
+ shutdown_msg = each.get('shutdown_msg', None)
+ solo = each.get('solo', None)
+ strict_capability_match = each.get('strict_capability_match', None)
+ ttl_security = each.get('ttl_security', None)
address_family = each.get('address_family', None)
- if name and not remote_as and not timers and not advertisement_interval and not bfd and not capability and not address_family:
+ if (name and not remote_as and not timers and not advertisement_interval and not bfd and not capability and not auth_pwd and not
+ pg_description and disable_connected_check is None and dont_negotiate_capability is None and not ebgp_multihop and
+ enforce_first_as is None and enforce_multihop is None and not local_address and not local_as and override_capability
+ is None and passive is None and not shutdown_msg and solo is None and strict_capability_match is None and not ttl_security and
+ not address_family):
want_pg_match = None
if want_peer_group:
want_pg_match = next((cfg for cfg in want_peer_group if cfg['name'] == name), None)
if want_pg_match:
- keys = ['remote_as', 'timers', 'advertisement_interval', 'bfd', 'capability', 'address_family']
+ keys = ['remote_as', 'timers', 'advertisement_interval', 'bfd', 'capability', 'auth_pwd', 'pg_description',
+ 'disable_connected_check', 'dont_negotiate_capability', 'ebgp_multihop', 'enforce_first_as', 'enforce_multihop',
+ 'local_address', 'local_as', 'override_capability', 'passive', 'shutdown_msg', 'solo', 'strict_capability_match',
+ 'ttl_security', 'address_family']
if not any(want_pg_match.get(key, None) for key in keys):
requests.append(self.get_delete_vrf_specific_peergroup_request(vrf_name, name))
else:
@@ -808,7 +789,7 @@ class Bgp_neighbors(ConfigBase):
delete_path = delete_static_path + '/ebgp-multihop/config/enabled'
requests.append({'path': delete_path, 'method': DELETE})
if cmd['ebgp_multihop'].get('multihop_ttl', None) is not None:
- delete_path = delete_static_path + '/ebgp-multihop/config/multihop_ttl'
+ delete_path = delete_static_path + '/ebgp-multihop/config/multihop-ttl'
requests.append({'path': delete_path, 'method': DELETE})
if cmd.get('address_family', None) is not None:
if cmd['address_family'].get('afis', None) is None:
@@ -857,9 +838,6 @@ class Bgp_neighbors(ConfigBase):
requests.extend(self.delete_ip_afi_requests(ip_afi, afi_safi_name, 'ipv6-unicast', delete_static_path))
if prefix_limit:
requests.extend(self.delete_prefix_limit_requests(prefix_limit, afi_safi_name, 'ipv6-unicast', delete_static_path))
- elif afi_safi == 'L2VPN_EVPN':
- if prefix_limit:
- requests.extend(self.delete_prefix_limit_requests(prefix_limit, afi_safi_name, 'l2vpn-evpn', delete_static_path))
return requests
@@ -909,12 +887,36 @@ class Bgp_neighbors(ConfigBase):
advertisement_interval = each.get('advertisement_interval', None)
bfd = each.get('bfd', None)
capability = each.get('capability', None)
- if neighbor and not remote_as and not peer_group and not timers and not advertisement_interval and not bfd and not capability:
+ auth_pwd = each.get('auth_pwd', None)
+ nbr_description = each.get('nbr_description', None)
+ disable_connected_check = each.get('disable_connected_check', None)
+ dont_negotiate_capability = each.get('dont_negotiate_capability', None)
+ ebgp_multihop = each.get('ebgp_multihop', None)
+ enforce_first_as = each.get('enforce_first_as', None)
+ enforce_multihop = each.get('enforce_multihop', None)
+ local_address = each.get('local_address', None)
+ local_as = each.get('local_as', None)
+ override_capability = each.get('override_capability', None)
+ passive = each.get('passive', None)
+ port = each.get('port', None)
+ shutdown_msg = each.get('shutdown_msg', None)
+ solo = each.get('solo', None)
+ strict_capability_match = each.get('strict_capability_match', None)
+ ttl_security = each.get('ttl_security', None)
+ v6only = each.get('v6only', None)
+ if (neighbor and not remote_as and not peer_group and not timers and not advertisement_interval and not bfd and not capability and not
+ auth_pwd and not nbr_description and disable_connected_check is None and dont_negotiate_capability is None and not
+ ebgp_multihop and enforce_first_as is None and enforce_multihop is None and not local_address and not local_as and
+ override_capability is None and passive is None and not port and not shutdown_msg and solo is None and strict_capability_match
+ is None and not ttl_security and v6only is None):
want_nei_match = None
if want_neighbors:
want_nei_match = next(cfg for cfg in want_neighbors if cfg['neighbor'] == neighbor)
if want_nei_match:
- keys = ['remote_as', 'peer_group', 'timers', 'advertisement_interval', 'bfd', 'capability']
+ keys = ['remote_as', 'peer_group', 'timers', 'advertisement_interval', 'bfd', 'capability', 'auth_pwd', 'nbr_description',
+ 'disable_connected_check', 'dont_negotiate_capability', 'ebgp_multihop', 'enforce_first_as', 'enforce_multihop',
+ 'local_address', 'local_as', 'override_capability', 'passive', 'port', 'shutdown_msg', 'solo',
+ 'strict_capability_match', 'ttl_security', 'v6only']
if not any(want_nei_match.get(key, None) for key in keys):
requests.append(self.delete_neighbor_whole_request(vrf_name, neighbor))
else:
@@ -1034,7 +1036,7 @@ class Bgp_neighbors(ConfigBase):
delete_path = delete_static_path + '/ebgp-multihop/config/enabled'
requests.append({'path': delete_path, 'method': DELETE})
if cmd['ebgp_multihop'].get('multihop_ttl', None) is not None:
- delete_path = delete_static_path + '/ebgp-multihop/config/multihop_ttl'
+ delete_path = delete_static_path + '/ebgp-multihop/config/multihop-ttl'
requests.append({'path': delete_path, 'method': DELETE})
return requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors_af/bgp_neighbors_af.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors_af/bgp_neighbors_af.py
index 15f46f966..196a6eea9 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors_af/bgp_neighbors_af.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors_af/bgp_neighbors_af.py
@@ -13,11 +13,6 @@ created
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-try:
- from urllib import quote
-except ImportError:
- from urllib.parse import quote
-
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -288,7 +283,7 @@ class Bgp_neighbors_af(ConfigBase):
if conf_prefix_limit:
pfx_lmt_cfg = get_prefix_limit_payload(conf_prefix_limit)
if pfx_lmt_cfg and afi_safi_val == 'L2VPN_EVPN':
- afi_safi['l2vpn-evpn'] = {'prefix-limit': {'config': pfx_lmt_cfg}}
+ self._module.fail_json('Prefix limit configuration not supported for l2vpn evpn')
else:
if conf_ip_afi:
ip_afi_cfg = get_ip_afi_cfg_payload(conf_ip_afi)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/copp/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/copp/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/copp/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/copp/copp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/copp/copp.py
new file mode 100644
index 000000000..cec802e67
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/copp/copp.py
@@ -0,0 +1,393 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_copp class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_diff,
+ get_replaced_config,
+ send_requests,
+ remove_empties,
+ update_states,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+
+
+COPP_GROUPS_PATH = '/data/openconfig-copp-ext:copp/copp-groups'
+PATCH = 'patch'
+DELETE = 'delete'
+TEST_KEYS = [
+ {'copp_groups': {'copp_name': ''}}
+]
+reserved_copp_names = [
+ 'copp-system-lacp',
+ 'copp-system-udld',
+ 'copp-system-stp',
+ 'copp-system-bfd',
+ 'copp-system-ptp',
+ 'copp-system-lldp',
+ 'copp-system-vrrp',
+ 'copp-system-iccp',
+ 'copp-system-ospf',
+ 'copp-system-bgp',
+ 'copp-system-pim',
+ 'copp-system-igmp',
+ 'copp-system-suppress',
+ 'copp-system-arp',
+ 'copp-system-dhcp',
+ 'copp-system-icmp',
+ 'copp-system-ip2me',
+ 'copp-system-subnet',
+ 'copp-system-nat',
+ 'copp-system-mtu',
+ 'copp-system-sflow',
+ 'copp-system-default',
+ 'copp-system-ttl',
+ 'default'
+]
+
+
+class Copp(ConfigBase):
+ """
+ The sonic_copp class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'copp',
+ ]
+
+ def __init__(self, module):
+ super(Copp, self).__init__(module)
+
+ def get_copp_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ copp_facts = facts['ansible_network_resources'].get('copp')
+ if not copp_facts:
+ return []
+ return copp_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = []
+ commands = []
+
+ existing_copp_facts = self.get_copp_facts()
+ commands, requests = self.set_config(existing_copp_facts)
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_copp_facts = self.get_copp_facts()
+
+ result['before'] = existing_copp_facts
+ if result['changed']:
+ result['after'] = changed_copp_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_copp_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ have = existing_copp_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ state = self._module.params['state']
+
+ diff = get_diff(want, have, TEST_KEYS)
+
+ if state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'merged':
+ commands, requests = self._state_merged(diff)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have, diff)
+ return commands, requests
+
+ def _state_replaced(self, want, have, diff):
+ """ The command generator when state is replaced
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ replaced_config = get_replaced_config(want, have, TEST_KEYS)
+
+ if replaced_config:
+ is_delete_all = True
+ requests = self.get_delete_copp_requests(replaced_config, None, is_delete_all)
+ send_requests(self._module, requests)
+
+ commands = want
+ else:
+ commands = diff
+
+ requests = []
+
+ if commands:
+ requests = self.get_modify_copp_groups_request(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "replaced")
+ else:
+ commands = []
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ self.sort_lists_in_config(want)
+ self.sort_lists_in_config(have)
+
+ have = self.filter_copp_groups(have)
+ if have and have != want:
+ is_delete_all = True
+ requests = self.get_delete_copp_requests(have, None, is_delete_all)
+ send_requests(self._module, requests)
+ have = []
+
+ commands = []
+ requests = []
+
+ if not have and want:
+ commands = want
+ requests = self.get_modify_copp_groups_request(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "overridden")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_merged(self, diff):
+ """ The command generator when state is merged
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = diff
+ requests = self.get_modify_copp_groups_request(commands)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ is_delete_all = False
+ # if want is none, then delete ALL
+ want = remove_empties(want)
+ if not want:
+ commands = have
+ is_delete_all = True
+ else:
+ commands = want
+ commands = self.filter_copp_groups(commands)
+ requests = self.get_delete_copp_requests(commands, have, is_delete_all)
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def get_modify_copp_groups_request(self, commands):
+ request = None
+
+ copp_groups = commands.get('copp_groups', None)
+ if copp_groups:
+ group_list = []
+ for group in copp_groups:
+ config_dict = {}
+ group_dict = {}
+ copp_name = group.get('copp_name', None)
+ trap_priority = group.get('trap_priority', None)
+ trap_action = group.get('trap_action', None)
+ queue = group.get('queue', None)
+ cir = group.get('cir', None)
+ cbs = group.get('cbs', None)
+
+ if copp_name:
+ config_dict['name'] = copp_name
+ group_dict['name'] = copp_name
+ if trap_priority:
+ config_dict['trap-priority'] = trap_priority
+ if trap_action:
+ config_dict['trap-action'] = trap_action
+ if queue:
+ config_dict['queue'] = queue
+ if cir:
+ config_dict['cir'] = cir
+ if cbs:
+ config_dict['cbs'] = cbs
+ if config_dict:
+ group_dict['config'] = config_dict
+ group_list.append(group_dict)
+
+ if group_list:
+ copp_groups_dict = {'copp-group': group_list}
+ payload = {'openconfig-copp-ext:copp-groups': copp_groups_dict}
+ request = {'path': COPP_GROUPS_PATH, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_delete_copp_requests(self, commands, have, is_delete_all):
+ requests = []
+
+ if is_delete_all:
+ copp_groups = commands.get('copp_groups', None)
+ if copp_groups:
+ for group in copp_groups:
+ copp_name = group.get('copp_name', None)
+ requests.append(self.get_delete_single_copp_group_request(copp_name))
+ else:
+ copp_groups = commands.get('copp_groups', None)
+ if copp_groups:
+ for group in copp_groups:
+ copp_name = group.get('copp_name', None)
+ trap_priority = group.get('trap_priority', None)
+ trap_action = group.get('trap_action', None)
+ queue = group.get('queue', None)
+ cir = group.get('cir', None)
+ cbs = group.get('cbs', None)
+
+ if have:
+ cfg_copp_groups = have.get('copp_groups', None)
+ if cfg_copp_groups:
+ for cfg_group in cfg_copp_groups:
+ cfg_copp_name = cfg_group.get('copp_name', None)
+ cfg_trap_priority = cfg_group.get('trap_priority', None)
+ cfg_trap_action = cfg_group.get('trap_action', None)
+ cfg_queue = cfg_group.get('queue', None)
+ cfg_cir = cfg_group.get('cir', None)
+ cfg_cbs = cfg_group.get('cbs', None)
+
+ if copp_name == cfg_copp_name:
+ if trap_priority and trap_priority == cfg_trap_priority:
+ requests.append(self.get_delete_copp_groups_attr_request(copp_name, 'trap-priority'))
+ if trap_action and trap_action == cfg_trap_action:
+ err_msg = "Deletion of trap-action attribute is not supported."
+ self._module.fail_json(msg=err_msg, code=405)
+ requests.append(self.get_delete_copp_groups_attr_request(copp_name, 'trap-action'))
+ if queue and queue == cfg_queue:
+ requests.append(self.get_delete_copp_groups_attr_request(copp_name, 'queue'))
+ if cir and cir == cfg_cir:
+ requests.append(self.get_delete_copp_groups_attr_request(copp_name, 'cir'))
+ if cbs and cbs == cfg_cbs:
+ requests.append(self.get_delete_copp_groups_attr_request(copp_name, 'cbs'))
+ if not trap_priority and not trap_action and not queue and not cir and not cbs:
+ requests.append(self.get_delete_single_copp_group_request(copp_name))
+
+ return requests
+
+ def get_delete_copp_groups_attr_request(self, copp_name, attr):
+ url = '%s/copp-group=%s/config/%s' % (COPP_GROUPS_PATH, copp_name, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_single_copp_group_request(self, copp_name):
+ url = '%s/copp-group=%s' % (COPP_GROUPS_PATH, copp_name)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def filter_copp_groups(self, commands):
+ cfg_dict = {}
+
+ if commands:
+ copp_groups = commands.get('copp_groups', None)
+ if copp_groups:
+ copp_groups_list = []
+ for group in copp_groups:
+ copp_name = group.get('copp_name', None)
+ if copp_name not in reserved_copp_names:
+ copp_groups_list.append(group)
+ if copp_groups_list:
+ cfg_dict['copp_groups'] = copp_groups_list
+
+ return cfg_dict
+
+ def get_copp_groups_key(self, copp_groups_key):
+ return copp_groups_key.get('copp_name')
+
+ def sort_lists_in_config(self, config):
+ if 'copp_groups' in config and config['copp_groups'] is not None:
+ config['copp_groups'].sort(key=self.get_copp_groups_key)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_relay/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_relay/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_relay/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_relay/dhcp_relay.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_relay/dhcp_relay.py
new file mode 100644
index 000000000..64d50fb5b
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_relay/dhcp_relay.py
@@ -0,0 +1,695 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_dhcp_relay class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_diff,
+ update_states,
+ normalize_interface_name,
+ get_normalize_interface_name,
+ remove_empties_from_list
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils.connection import ConnectionError
+
+PATCH = 'patch'
+DELETE = 'delete'
+
+DEFAULT_CIRCUIT_ID = '%p'
+DEFAULT_MAX_HOP_COUNT = 10
+DEFAULT_POLICY_ACTION = 'discard'
+
+BOOL_TO_SELECT_VALUE = {
+ True: 'ENABLE',
+ False: 'DISABLE'
+}
+
+
+class Dhcp_relay(ConfigBase):
+ """
+ The sonic_dhcp_relay class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'dhcp_relay',
+ ]
+
+ dhcp_relay_intf_path = 'data/openconfig-relay-agent:relay-agent/dhcp/interfaces/interface={intf_name}'
+ dhcp_relay_intf_config_path = {
+ 'circuit_id': dhcp_relay_intf_path + '/agent-information-option/config/circuit-id',
+ 'link_select': dhcp_relay_intf_path + '/agent-information-option/config/openconfig-relay-agent-ext:link-select',
+ 'max_hop_count': dhcp_relay_intf_path + '/config/openconfig-relay-agent-ext:max-hop-count',
+ 'policy_action': dhcp_relay_intf_path + '/config/openconfig-relay-agent-ext:policy-action',
+ 'server_address': dhcp_relay_intf_path + '/config/helper-address={server_address}',
+ 'server_addresses_all': dhcp_relay_intf_path + '/config/helper-address',
+ 'source_interface': dhcp_relay_intf_path + '/config/openconfig-relay-agent-ext:src-intf',
+ 'vrf_name': dhcp_relay_intf_path + '/config/openconfig-relay-agent-ext:vrf',
+ 'vrf_select': dhcp_relay_intf_path + '/agent-information-option/config/openconfig-relay-agent-ext:vrf-select'
+ }
+
+ dhcpv6_relay_intf_path = 'data/openconfig-relay-agent:relay-agent/dhcpv6/interfaces/interface={intf_name}'
+ dhcpv6_relay_intf_config_path = {
+ 'max_hop_count': dhcpv6_relay_intf_path + '/config/openconfig-relay-agent-ext:max-hop-count',
+ 'server_address': dhcpv6_relay_intf_path + '/config/helper-address={server_address}',
+ 'server_addresses_all': dhcpv6_relay_intf_path + '/config/helper-address',
+ 'source_interface': dhcpv6_relay_intf_path + '/config/openconfig-relay-agent-ext:src-intf',
+ 'vrf_name': dhcpv6_relay_intf_path + '/config/openconfig-relay-agent-ext:vrf',
+ 'vrf_select': dhcpv6_relay_intf_path + '/options/config/openconfig-relay-agent-ext:vrf-select'
+ }
+
+ def __init__(self, module):
+ super(Dhcp_relay, self).__init__(module)
+
+ def get_dhcp_relay_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ dhcp_relay_facts = facts['ansible_network_resources'].get('dhcp_relay')
+ if not dhcp_relay_facts:
+ return []
+ return dhcp_relay_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = []
+
+ existing_dhcp_relay_facts = self.get_dhcp_relay_facts()
+ commands, requests = self.set_config(existing_dhcp_relay_facts)
+ if commands:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+
+ changed_dhcp_relay_facts = self.get_dhcp_relay_facts()
+
+ result['before'] = existing_dhcp_relay_facts
+ if result['changed']:
+ result['after'] = changed_dhcp_relay_facts
+
+ result['commands'] = commands
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_dhcp_relay_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params['state']
+ want = self._module.params['config']
+ if want:
+ # In state deleted, specific empty parameters are supported
+ if state != 'deleted':
+ want = remove_empties_from_list(want)
+
+ normalize_interface_name(want, self._module)
+ for config in want:
+ if config.get('ipv4') and config['ipv4'].get('source_interface'):
+ config['ipv4']['source_interface'] = get_normalize_interface_name(config['ipv4']['source_interface'], self._module)
+ if config.get('ipv6') and config['ipv6'].get('source_interface'):
+ config['ipv6']['source_interface'] = get_normalize_interface_name(config['ipv6']['source_interface'], self._module)
+ else:
+ want = []
+
+ have = existing_dhcp_relay_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params['state']
+ if state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'merged':
+ commands, requests = self._state_merged(want, have)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have)
+ elif state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+ return commands, requests
+
+ def _state_merged(self, want, have):
+ """ The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = get_diff(want, have)
+ requests = self.get_modify_dhcp_dhcpv6_relay_requests(commands)
+ if commands and len(requests) > 0:
+ commands = update_states(commands, 'merged')
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ requests = []
+ if not want:
+ commands = have
+ requests.extend(self.get_delete_dhcp_dhcpv6_relay_completely_requests(commands))
+ else:
+ commands = want
+ requests.extend(self.get_delete_dhcp_dhcpv6_relay_requests(commands, have))
+
+ if len(requests) == 0:
+ commands = []
+
+ if commands:
+ commands = update_states(commands, "deleted")
+
+ return commands, requests
+
+ def _state_replaced(self, want, have):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ del_commands, del_requests = self.get_delete_commands_requests_for_replaced_overridden(want, have, 'replaced')
+ if del_commands:
+ new_have = get_diff(have, del_commands)
+ commands = update_states(del_commands, 'deleted')
+ requests = del_requests
+ else:
+ new_have = have
+
+ add_commands = get_diff(want, new_have)
+ if add_commands:
+ commands.extend(update_states(add_commands, 'replaced'))
+ requests.extend(self.get_modify_dhcp_dhcpv6_relay_requests(add_commands))
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ del_commands, del_requests = self.get_delete_commands_requests_for_replaced_overridden(want, have, 'overridden')
+ if del_commands:
+ new_have = get_diff(have, del_commands)
+ commands = update_states(del_commands, 'deleted')
+ requests = del_requests
+ else:
+ new_have = have
+
+ add_commands = get_diff(want, new_have)
+ if add_commands:
+ commands.extend(update_states(add_commands, 'overridden'))
+ requests.extend(self.get_modify_dhcp_dhcpv6_relay_requests(add_commands))
+
+ return commands, requests
+
+ def get_modify_dhcp_dhcpv6_relay_requests(self, commands):
+ """Get requests to modify DHCP and DHCPv6 relay configurations
+ for all interfaces specified by the commands
+ """
+ requests = []
+
+ for command in commands:
+ if command.get('ipv4'):
+ requests.extend(self.get_modify_specific_dhcp_relay_param_requests(command))
+ if command.get('ipv6'):
+ requests.extend(self.get_modify_specific_dhcpv6_relay_param_requests(command))
+
+ return requests
+
+ def get_modify_specific_dhcp_relay_param_requests(self, command):
+ """Get requests to modify specific DHCP relay configurations
+ based on the command specified for the interface
+ """
+ requests = []
+
+ name = command['name']
+ ipv4 = command.get('ipv4')
+ if not ipv4:
+ return requests
+
+ # Specifying appropriate order for merge to succeed
+ server_addresses = self.get_server_addresses(ipv4.get('server_addresses'))
+ if server_addresses:
+ payload = {'openconfig-relay-agent:helper-address': list(server_addresses)}
+ url = self.dhcp_relay_intf_config_path['server_addresses_all'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if ipv4.get('vrf_name'):
+ payload = {'openconfig-relay-agent-ext:vrf': ipv4['vrf_name']}
+ url = self.dhcp_relay_intf_config_path['vrf_name'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if ipv4.get('source_interface'):
+ payload = {'openconfig-relay-agent-ext:src-intf': ipv4['source_interface']}
+ url = self.dhcp_relay_intf_config_path['source_interface'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if ipv4.get('link_select') is not None:
+ link_select = BOOL_TO_SELECT_VALUE[ipv4['link_select']]
+ payload = {'openconfig-relay-agent-ext:link-select': link_select}
+ url = self.dhcp_relay_intf_config_path['link_select'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if ipv4.get('max_hop_count'):
+ payload = {'openconfig-relay-agent-ext:max-hop-count': ipv4['max_hop_count']}
+ url = self.dhcp_relay_intf_config_path['max_hop_count'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if ipv4.get('vrf_select') is not None:
+ vrf_select = BOOL_TO_SELECT_VALUE[ipv4['vrf_select']]
+ payload = {'openconfig-relay-agent-ext:vrf-select': vrf_select}
+ url = self.dhcp_relay_intf_config_path['vrf_select'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if ipv4.get('policy_action'):
+ payload = {'openconfig-relay-agent-ext:policy-action': ipv4['policy_action'].upper()}
+ url = self.dhcp_relay_intf_config_path['policy_action'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if ipv4.get('circuit_id'):
+ payload = {'openconfig-relay-agent:circuit-id': ipv4['circuit_id']}
+ url = self.dhcp_relay_intf_config_path['circuit_id'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ return requests
+
+ def get_modify_specific_dhcpv6_relay_param_requests(self, command):
+ """Get requests to modify specific DHCPv6 relay configurations
+ based on the command specified for the interface
+ """
+ requests = []
+
+ name = command['name']
+ ipv6 = command.get('ipv6')
+ if not ipv6:
+ return requests
+
+ # Specifying appropriate order for merge to succeed
+ server_addresses = self.get_server_addresses(ipv6.get('server_addresses'))
+ if server_addresses:
+ payload = {'openconfig-relay-agent:helper-address': list(server_addresses)}
+ url = self.dhcpv6_relay_intf_config_path['server_addresses_all'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if ipv6.get('vrf_name'):
+ payload = {'openconfig-relay-agent-ext:vrf': ipv6['vrf_name']}
+ url = self.dhcpv6_relay_intf_config_path['vrf_name'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if ipv6.get('source_interface'):
+ payload = {'openconfig-relay-agent-ext:src-intf': ipv6['source_interface']}
+ url = self.dhcpv6_relay_intf_config_path['source_interface'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if ipv6.get('max_hop_count'):
+ payload = {'openconfig-relay-agent-ext:max-hop-count': ipv6['max_hop_count']}
+ url = self.dhcpv6_relay_intf_config_path['max_hop_count'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if ipv6.get('vrf_select') is not None:
+ vrf_select = BOOL_TO_SELECT_VALUE[ipv6['vrf_select']]
+ payload = {'openconfig-relay-agent-ext:vrf-select': vrf_select}
+ url = self.dhcpv6_relay_intf_config_path['vrf_select'].format(intf_name=name)
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ return requests
+
+ def get_delete_dhcp_dhcpv6_relay_completely_requests(self, have):
+ """Get requests to delete all existing DHCP and DHCPv6 relay
+ configurations in the chassis
+ """
+ requests = []
+ for cfg in have:
+ if cfg.get('ipv4'):
+ requests.append(self.get_delete_all_dhcp_relay_intf_request(cfg['name']))
+ if cfg.get('ipv6'):
+ requests.append(self.get_delete_all_dhcpv6_relay_intf_request(cfg['name']))
+
+ return requests
+
+ def get_delete_dhcp_dhcpv6_relay_requests(self, commands, have):
+ """Get requests to delete DHCP and DHCPv6 relay configurations
+ based on the commands specified
+ """
+ requests = []
+
+ for command in commands:
+ intf_name = command['name']
+ have_obj = next((cfg for cfg in have if cfg['name'] == intf_name), None)
+ if not have_obj:
+ continue
+
+ have_ipv4 = have_obj.get('ipv4')
+ have_ipv6 = have_obj.get('ipv6')
+
+ ipv4 = command.get('ipv4')
+ ipv6 = command.get('ipv6')
+ if not ipv4 and not ipv6:
+ if have_ipv4:
+ requests.append(self.get_delete_all_dhcp_relay_intf_request(intf_name))
+ if have_ipv6:
+ requests.append(self.get_delete_all_dhcpv6_relay_intf_request(intf_name))
+ else:
+ if ipv4 and have_ipv4:
+ requests.extend(self.get_delete_specific_dhcp_relay_param_requests(command, have_obj))
+ if ipv6 and have_ipv6:
+ requests.extend(self.get_delete_specific_dhcpv6_relay_param_requests(command, have_obj))
+
+ return requests
+
+ def get_delete_specific_dhcp_relay_param_requests(self, command, config, is_state_deleted=True):
+ """Get requests to delete specific DHCP relay configurations
+ based on the command specified for the interface
+ """
+ requests = []
+
+ name = command['name']
+ ipv4 = command.get('ipv4')
+ have_ipv4 = config.get('ipv4')
+ if not ipv4 or not have_ipv4:
+ return requests
+
+ server_addresses = self.get_server_addresses(ipv4.get('server_addresses'))
+ have_server_addresses = self.get_server_addresses(have_ipv4.get('server_addresses'))
+
+ # Delete all DHCP relay config for an interface, if only
+ # a single server address with no value is specified.
+ #
+ # This "special" YAML sequence is supported to provide
+ # "delete all AF parameters" functionality despite the Ansible
+ # infrastructure limitations that prevent use of a simpler
+ # syntax for deleting an entire AF parameter dictionary.
+ if (ipv4.get('server_addresses') and len(ipv4.get('server_addresses'))
+ and not server_addresses):
+ requests.append(self.get_delete_all_dhcp_relay_intf_request(name))
+ return requests
+
+ del_server_addresses = have_server_addresses.intersection(server_addresses)
+ if del_server_addresses:
+ # Deleting all DHCP server addresses configured on an
+ # interface automatically removes all DHCP relay config in
+ # that interface. Therefore, seperate requests to delete
+ # other DHCP relay configs are not required.
+ if is_state_deleted and len(del_server_addresses) == len(have_server_addresses):
+ requests.append(self.get_delete_all_dhcp_relay_intf_request(name))
+ return requests
+
+ for addr in del_server_addresses:
+ url = self.dhcp_relay_intf_config_path['server_address'].format(intf_name=name, server_address=addr)
+ requests.append({'path': url, 'method': DELETE})
+
+ # Specifying appropriate order for deletion to succeed
+ if ipv4.get('link_select') is not None and have_ipv4.get('link_select'):
+ url = self.dhcp_relay_intf_config_path['link_select'].format(intf_name=name)
+ requests.append({'path': url, 'method': DELETE})
+
+ if (ipv4.get('source_interface') and have_ipv4.get('source_interface')
+ and ipv4['source_interface'] == have_ipv4['source_interface']):
+ url = self.dhcp_relay_intf_config_path['source_interface'].format(intf_name=name)
+ requests.append({'path': url, 'method': DELETE})
+
+ if (ipv4.get('max_hop_count') and have_ipv4.get('max_hop_count')
+ and ipv4['max_hop_count'] == have_ipv4['max_hop_count']
+ and have_ipv4['max_hop_count'] != DEFAULT_MAX_HOP_COUNT):
+ url = self.dhcp_relay_intf_config_path['max_hop_count'].format(intf_name=name)
+ requests.append({'path': url, 'method': DELETE})
+
+ if ipv4.get('vrf_select') is not None and have_ipv4.get('vrf_select'):
+ url = self.dhcp_relay_intf_config_path['vrf_select'].format(intf_name=name)
+ requests.append({'path': url, 'method': DELETE})
+
+ if (ipv4.get('policy_action') and have_ipv4.get('policy_action')
+ and ipv4['policy_action'] == have_ipv4['policy_action']
+ and have_ipv4['policy_action'] != DEFAULT_POLICY_ACTION):
+ url = self.dhcp_relay_intf_config_path['policy_action'].format(intf_name=name)
+ requests.append({'path': url, 'method': DELETE})
+
+ if (ipv4.get('circuit_id') and have_ipv4.get('circuit_id')
+ and ipv4['circuit_id'] == have_ipv4['circuit_id']
+ and have_ipv4['circuit_id'] != DEFAULT_CIRCUIT_ID):
+ url = self.dhcp_relay_intf_config_path['circuit_id'].format(intf_name=name)
+ requests.append({'path': url, 'method': DELETE})
+
+ return requests
+
+ def get_delete_specific_dhcpv6_relay_param_requests(self, command, have, is_state_deleted=True):
+ """Get requests to delete specific DHCPv6 relay configurations
+ based on the command specified for the interface
+ """
+ requests = []
+
+ name = command['name']
+ ipv6 = command.get('ipv6')
+ have_ipv6 = have.get('ipv6')
+ if not ipv6 or not have_ipv6:
+ return requests
+
+ server_addresses = self.get_server_addresses(ipv6.get('server_addresses'))
+ have_server_addresses = self.get_server_addresses(have_ipv6.get('server_addresses'))
+
+ # Delete all DHCPv6 relay config for an interface, if only
+ # a single server address with no value is specified.
+ #
+ # This "special" YAML sequence is supported to provide
+ # "delete all AF parameters" functionality despite the Ansible
+ # infrastructure limitations that prevent use of a simpler
+ # syntax for deleting an entire AF parameter dictionary.
+ if (ipv6.get('server_addresses') and len(ipv6.get('server_addresses'))
+ and not server_addresses):
+ requests.append(self.get_delete_all_dhcpv6_relay_intf_request(name))
+ return requests
+
+ del_server_addresses = have_server_addresses.intersection(server_addresses)
+ if del_server_addresses:
+ # Deleting all DHCPv6 server addresses configured on an
+ # interface automatically removes all DHCPv6 relay config
+ # in that interface. Therefore, seperate requests to delete
+ # other DHCPv6 relay configs are not required.
+ if is_state_deleted and len(del_server_addresses) == len(have_server_addresses):
+ requests.append(self.get_delete_all_dhcpv6_relay_intf_request(name))
+ return requests
+
+ for addr in del_server_addresses:
+ url = self.dhcpv6_relay_intf_config_path['server_address'].format(intf_name=name, server_address=addr)
+ requests.append({'path': url, 'method': DELETE})
+
+ # Specifying appropriate order for deletion to succeed
+ if (ipv6.get('source_interface') and have_ipv6.get('source_interface')
+ and ipv6['source_interface'] == have_ipv6['source_interface']):
+ url = self.dhcpv6_relay_intf_config_path['source_interface'].format(intf_name=name)
+ requests.append({'path': url, 'method': DELETE})
+
+ if (ipv6.get('max_hop_count') and have_ipv6.get('max_hop_count')
+ and ipv6['max_hop_count'] == have_ipv6['max_hop_count']
+ and have_ipv6['max_hop_count'] != DEFAULT_MAX_HOP_COUNT):
+ url = self.dhcpv6_relay_intf_config_path['max_hop_count'].format(intf_name=name)
+ requests.append({'path': url, 'method': DELETE})
+
+ if ipv6.get('vrf_select') is not None and have_ipv6.get('vrf_select'):
+ url = self.dhcpv6_relay_intf_config_path['vrf_select'].format(intf_name=name)
+ requests.append({'path': url, 'method': DELETE})
+
+ return requests
+
+ def get_delete_all_dhcp_relay_intf_request(self, intf_name):
+ """Get request to delete all DHCP relay configurations in the
+ specified interface
+ """
+ return {'path': self.dhcp_relay_intf_config_path['server_addresses_all'].format(intf_name=intf_name), 'method': DELETE}
+
+ def get_delete_all_dhcpv6_relay_intf_request(self, intf_name):
+ """Get request to delete all DHCPv6 relay configurations in the
+ specified interface
+ """
+ return {'path': self.dhcpv6_relay_intf_config_path['server_addresses_all'].format(intf_name=intf_name), 'method': DELETE}
+
+ def get_delete_commands_requests_for_replaced_overridden(self, want, have, state):
+ """Returns the commands and requests necessary to remove applicable
+ current configurations when state is replaced or overridden
+ """
+ default_value = {
+ 'circuit_id': DEFAULT_CIRCUIT_ID,
+ 'max_hop_count': DEFAULT_MAX_HOP_COUNT,
+ 'policy_action': DEFAULT_POLICY_ACTION
+ }
+ commands = []
+ requests = []
+ if not have:
+ return commands, requests
+
+ for conf in have:
+ intf_name = conf['name']
+ ipv4_conf = conf.get('ipv4')
+ ipv6_conf = conf.get('ipv6')
+
+ match_obj = next((cmd for cmd in want if cmd['name'] == intf_name), None)
+ if not match_obj:
+ # Delete all DHCP and DHCPv6 relay config for interfaces,
+ # that are not specified in overridden.
+ if state == 'overridden':
+ commands.append(conf)
+ if ipv4_conf:
+ requests.append(self.get_delete_all_dhcp_relay_intf_request(intf_name))
+ if ipv6_conf:
+ requests.append(self.get_delete_all_dhcpv6_relay_intf_request(intf_name))
+ continue
+
+ command = {'name': intf_name}
+ if ipv4_conf:
+ match_ipv4 = match_obj.get('ipv4')
+ # Delete all DHCP relay config for an interface if not specified
+ if not match_ipv4:
+ command['ipv4'] = ipv4_conf
+ requests.append(self.get_delete_all_dhcp_relay_intf_request(intf_name))
+ else:
+ have_server_addresses = self.get_server_addresses(ipv4_conf.get('server_addresses'))
+ server_addresses = self.get_server_addresses(match_ipv4.get('server_addresses'))
+
+ # Delete all DHCP relay config for an interface, if
+ # all existing server addresses are to be replaced
+ # or if the VRF is to be removed.
+ if (not have_server_addresses.intersection(server_addresses)
+ or (ipv4_conf.get('vrf_name') and match_ipv4.get('vrf_name') is None)):
+ command['ipv4'] = ipv4_conf
+ requests.append(self.get_delete_all_dhcp_relay_intf_request(intf_name))
+ else:
+ ipv4_command = {}
+ del_server_addresses = have_server_addresses.difference(server_addresses)
+ if del_server_addresses:
+ ipv4_command['server_addresses'] = []
+ for address in del_server_addresses:
+ ipv4_command['server_addresses'].append({'address': address})
+
+ for option in ('source_interface', 'link_select', 'vrf_select'):
+ if ipv4_conf.get(option) and match_ipv4.get(option) is None:
+ ipv4_command[option] = ipv4_conf[option]
+
+ for option in ('circuit_id', 'max_hop_count', 'policy_action'):
+ if (ipv4_conf.get(option) and match_ipv4.get(option) is None
+ and ipv4_conf[option] != default_value[option]):
+ ipv4_command[option] = ipv4_conf[option]
+
+ if ipv4_command:
+ command['ipv4'] = ipv4_command
+ requests.extend(self.get_delete_specific_dhcp_relay_param_requests(command, command, False))
+
+ if ipv6_conf:
+ match_ipv6 = match_obj.get('ipv6')
+ # Delete all DHCPv6 relay config for an interface if not specified
+ if not match_ipv6:
+ command['ipv6'] = ipv6_conf
+ requests.append(self.get_delete_all_dhcpv6_relay_intf_request(intf_name))
+ else:
+ have_server_addresses = self.get_server_addresses(ipv6_conf.get('server_addresses'))
+ server_addresses = self.get_server_addresses(match_ipv6.get('server_addresses'))
+
+ # Delete all DHCPv6 relay config for an interface, if
+ # all existing server addresses are to be replaced
+ # or if the VRF is to be removed.
+ if (not have_server_addresses.intersection(server_addresses)
+ or (ipv6_conf.get('vrf_name') and match_ipv6.get('vrf_name') is None)):
+ command['ipv6'] = ipv6_conf
+ requests.append(self.get_delete_all_dhcpv6_relay_intf_request(intf_name))
+ else:
+ ipv6_command = {}
+ del_server_addresses = have_server_addresses.difference(server_addresses)
+ if del_server_addresses:
+ ipv6_command['server_addresses'] = []
+ for address in del_server_addresses:
+ ipv6_command['server_addresses'].append({'address': address})
+
+ for option in ('source_interface', 'vrf_select'):
+ if ipv6_conf.get(option) and match_ipv6.get(option) is None:
+ ipv6_command[option] = ipv6_conf[option]
+
+ if (ipv6_conf.get('max_hop_count') and match_ipv6.get('max_hop_count') is None
+ and ipv6_conf['max_hop_count'] != default_value['max_hop_count']):
+ ipv6_command['max_hop_count'] = ipv6_conf['max_hop_count']
+
+ if ipv6_command:
+ command['ipv6'] = ipv6_command
+ requests.extend(self.get_delete_specific_dhcpv6_relay_param_requests(command, command, False))
+
+ if command.get('ipv4') or command.get('ipv6'):
+ commands.append(command)
+
+ return commands, requests
+
+ @staticmethod
+ def get_server_addresses(server_addresses_dict):
+ """Get a set of server addresses available in the given
+ server_addresses dict
+ """
+ server_addresses = set()
+ if not server_addresses_dict:
+ return server_addresses
+
+ for addr in server_addresses_dict:
+ if addr.get('address'):
+ server_addresses.add(addr['address'])
+
+ return server_addresses
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_snooping/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_snooping/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_snooping/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_snooping/dhcp_snooping.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_snooping/dhcp_snooping.py
new file mode 100644
index 000000000..d3c3233b1
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/dhcp_snooping/dhcp_snooping.py
@@ -0,0 +1,649 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_dhcp_snooping class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+ remove_empties,
+ validate_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_diff,
+ update_states
+)
+
+
+class Dhcp_snooping(ConfigBase):
+ """
+ The sonic_dhcp_snooping class
+ """
+ test_keys = [
+ {'afis': {'afi': ''}},
+ {"source_bindings": {"mac_addr": ""}},
+ {"trusted": {"intf_name": ""}}
+ ]
+
+ ipv4_key = 'ipv4'
+ ipv6_key = 'ipv6'
+
+ delete_method_value = 'delete'
+ patch_method_value = 'patch'
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'dhcp_snooping',
+ ]
+
+ dhcp_snooping_uri = 'data/openconfig-dhcp-snooping:dhcp-snooping'
+ config_uri = dhcp_snooping_uri + '/config'
+ enable_uri = config_uri + '/dhcpv{v}-admin-enable'
+ verify_mac_uri = config_uri + '/dhcpv{v}-verify-mac-address'
+ binding_uri = dhcp_snooping_uri + '-static-binding/entry'
+ trusted_uri = 'data/openconfig-interfaces:interfaces/interface={name}/dhcpv{v}-snooping-trust/config/dhcpv{v}-snooping-trust'
+ vlans_uri = 'data/sonic-vlan:sonic-vlan/VLAN/VLAN_LIST={vlan_name}/dhcpv{v}_snooping_enable'
+
+ def __init__(self, module):
+ super(Dhcp_snooping, self).__init__(module)
+
+ def get_dhcp_snooping_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset,
+ self.gather_network_resources)
+ dhcp_snooping_facts = facts['ansible_network_resources'].get('dhcp_snooping')
+ if not dhcp_snooping_facts:
+ return []
+ return dhcp_snooping_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+
+ existing_dhcp_snooping_facts = self.get_dhcp_snooping_facts()
+ commands, requests = self.set_config(existing_dhcp_snooping_facts)
+ if commands:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_dhcp_snooping_facts = self.get_dhcp_snooping_facts()
+
+ result['before'] = existing_dhcp_snooping_facts
+ if result['changed']:
+ result['after'] = changed_dhcp_snooping_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_dhcp_snooping_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ have = existing_dhcp_snooping_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ afis = {}
+ want = self.remove_none(want)
+
+ # just in case weird arguments passed
+ if want is None:
+ want = {}
+ if have is None:
+ have = {}
+
+ if want.get('afis') is not None:
+ for want_afi in want.get('afis'):
+ if want_afi.get('afi') == self.ipv4_key:
+ afis['want_ipv4'] = want_afi
+ elif want_afi.get('afi') == self.ipv6_key:
+ afis['want_ipv6'] = want_afi
+
+ if have.get('afis') is not None:
+ for have_afi in have.get('afis'):
+ if have_afi.get('afi') == self.ipv4_key:
+ afis['have_ipv4'] = have_afi
+ elif have_afi.get('afi') == self.ipv6_key:
+ afis['have_ipv6'] = have_afi
+
+ state = self._module.params['state']
+ if state == 'merged':
+ commands, requests = self._state_merged(want, have, afis)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(want, have, afis)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have, afis)
+ elif state == 'overridden':
+ commands, requests = self._state_overridden(want, have, afis)
+
+ return commands, requests
+
+ def _state_merged(self, want, have, afis):
+ """ The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ want = remove_empties(want)
+ self.validate_config({"config": want})
+
+ commands = get_diff(want, have, test_keys=self.test_keys)
+ self.prep_replaced_to_merge(commands, afis)
+ requests = self.get_modify_requests(commands)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have, afis):
+ """ The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ requests = []
+ if not have or not have.get('afis'):
+ # nothing that could be deleted
+ commands = []
+ elif not want or not want.get('afis'):
+ # want is empty, meaning want to delete all config
+ # afis parameter only stores the on device config at this point
+ commands, requests = self.get_delete_all_have_requests(afis)
+ else:
+ # some mix of settings specified in both
+ commands, requests = self.get_delete_specific_requests(afis)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+ return commands, requests
+
+ def _state_overridden(self, want, have, afis):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ if not want:
+ return commands, requests
+
+ # Determine if there is any configuration specified in the playbook
+ # that is not contained in the current configuration.
+ diff_requested = get_diff(want, have, self.test_keys)
+ diff_requested_keyed = {}
+ for afi in diff_requested.get("afis", []):
+ diff_requested_keyed[afi["afi"]] = afi
+
+ # Determine if there is anything already configured that is not
+ # specified in the playbook.
+ diff_unwanted = get_diff(have, want, self.test_keys)
+
+ # Idempotency check: If the configuration already matches the
+ # requested configuration with no extra attributes, no
+ # commands should be executed on the device.
+ if not diff_requested and not diff_unwanted:
+ return commands, requests
+
+ used_commands_per_afi = []
+ commands = []
+
+ for diff_unwanted_afi in diff_unwanted.get("afis", []):
+ # enabled and verify_mac can't be deleted from config, only set to default.
+ # so in the case they appear in both the "need to delete" and "need to change", keeping in both results in double requests
+ if "enabled" in diff_unwanted_afi and "enabled" in diff_requested_keyed.get(diff_unwanted_afi["afi"], {}):
+ del diff_unwanted_afi["enabled"]
+ if "verify_mac" in diff_unwanted_afi and "verify_mac" in diff_requested_keyed.get(diff_unwanted_afi["afi"], {}):
+ del diff_unwanted_afi["verify_mac"]
+ afi_commands, afi_requests = self.get_delete_specific_afi_fields_requests(diff_unwanted_afi, afis["have_" + diff_unwanted_afi["afi"]])
+ if afi_commands:
+ afi_commands["afi"] = diff_unwanted_afi["afi"]
+ used_commands_per_afi.append(afi_commands)
+ requests.extend(afi_requests)
+ if len(used_commands_per_afi):
+ commands = {"afis": used_commands_per_afi}
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+
+ # apply the things to add or change
+ # need to add back in the source bindings since the diff could pick up only the different values in a source binding
+ self.prep_replaced_to_merge(diff_requested, afis)
+ overridden_requests = self.get_modify_requests(diff_requested)
+ requests.extend(overridden_requests)
+ if diff_requested and len(overridden_requests) > 0:
+ diff_requested = update_states(diff_requested, "overridden")
+ commands.extend(diff_requested)
+ return commands, requests
+
+ def _state_replaced(self, want, have, afis):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ # do needed deletes
+ commands, requests = self.get_delete_replaced_groupings(afis)
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ # getting what needs to be added/changed after deletes
+ # need to add back in the source bindings since the diff could pick up only the different values in a source binding
+ diff = get_diff(want, have, self.test_keys)
+ self.prep_replaced_to_merge(diff, afis)
+ merged_commands = diff
+
+ replaced_requests = self.get_modify_requests(merged_commands)
+ requests.extend(replaced_requests)
+ if merged_commands and len(replaced_requests) > 0:
+ merged_commands = update_states(merged_commands, "replaced")
+ commands.extend(merged_commands)
+ return commands, requests
+
+ def validate_config(self, config):
+ '''validate passed in config is argspec compliant. Also does checks on values in ranges that ansible might not do'''
+ validated_config = validate_config(self._module.argument_spec, config)
+ return validated_config
+
+ def remove_none(self, config):
+ '''goes through nested dictionary items and removes any keys that have None as value.
+ enables using empty list/dict to specify clear everything for that section and differentiate this
+ 'clear everything' case from when no value was given
+ remove_empties in ansible utils will remove empty lists and dicts as well as None'''
+ if isinstance(config, dict):
+ for k, v in list(config.items()):
+ if v is None:
+ del config[k]
+ else:
+ self.remove_none(v)
+ elif isinstance(config, list):
+ for item in list(config):
+ if item is None:
+ config.remove(item)
+ self.remove_none(item)
+ return config
+
+ def get_modify_requests(self, to_modify_config):
+ '''builds and returns requests to add in given config
+
+ :param to_modify: dictionary specifying what to modify in argspec format. expected to be at root level of config'''
+ requests = []
+
+ if to_modify_config.get('afis') is not None:
+ for afi_config in to_modify_config.get('afis'):
+ requests.extend(self.get_single_afi_modify_requests(afi_config))
+
+ return requests
+
+ def get_single_afi_modify_requests(self, to_modify_afi):
+ """build requests to modify a single afi family. Uses passed in config to find which family and what to change
+
+ :param to_modify_afi: dictionary specifying the config to add/change in argspec format. expected to be for a single afi
+ :param v: version number of afi to modify
+ """
+ requests = []
+ v = self.afi_to_vnum(to_modify_afi)
+
+ if to_modify_afi.get('enabled') is not None:
+ payload = {'openconfig-dhcp-snooping:dhcpv{v}-admin-enable'.format(v=v): to_modify_afi['enabled']}
+ uri = self.enable_uri.format(v=v)
+ requests.append({'path': uri, 'method': self.patch_method_value, 'data': payload})
+
+ if to_modify_afi.get('verify_mac') is not None:
+ payload = {'openconfig-dhcp-snooping:dhcpv{v}-verify-mac-address'.format(v=v): to_modify_afi['verify_mac']}
+ uri = self.verify_mac_uri.format(v=v)
+ requests.append({'path': uri, 'method': self.patch_method_value, 'data': payload})
+
+ if to_modify_afi.get('trusted'):
+ for intf in to_modify_afi.get('trusted'):
+ intf_name = intf.get("intf_name")
+ if intf_name:
+ payload = {'openconfig-interfaces:dhcpv{v}-snooping-trust'.format(v=v): 'ENABLE'}
+ uri = self.trusted_uri.format(name=intf_name, v=v)
+ requests.append({'path': uri, 'method': self.patch_method_value, 'data': payload})
+
+ if to_modify_afi.get('vlans'):
+ for vlan_id in to_modify_afi.get('vlans'):
+ payload = {'sonic-vlan:dhcpv{v}_snooping_enable'.format(v=v): 'enable'}
+ uri = self.vlans_uri.format(vlan_name='Vlan' + vlan_id, v=v)
+ requests.append({'path': uri, 'method': self.patch_method_value, 'data': payload})
+
+ if to_modify_afi.get('source_bindings'):
+ entries = []
+ for entry in to_modify_afi.get('source_bindings'):
+ if entry.get('mac_addr'):
+ entries.append({
+ 'mac': entry.get('mac_addr'),
+ 'iptype': 'ipv' + str(v),
+ 'config': {
+ 'mac': entry.get('mac_addr'),
+ 'iptype': 'ipv' + str(v),
+ 'vlan': "Vlan" + str(entry.get('vlan_id')),
+ 'interface': entry.get('intf_name'),
+ 'ip': entry.get('ip_addr'),
+ }
+ })
+
+ payload = {'openconfig-dhcp-snooping:entry': entries}
+ uri = self.binding_uri
+ requests.append({'path': uri, 'method': self.patch_method_value, 'data': payload})
+
+ return requests
+
+ def get_delete_all_have_requests(self, afis):
+ '''creates and builds list of requests to delete all current dhcp snooping config for ipv4 and ipv6'''
+ modified_afi_commands = []
+ requests = []
+ ipv4_commands, ipv4_requests = self.get_delete_specific_afi_fields_requests(afis.get('have_ipv4'), afis.get('have_ipv4'))
+ requests.extend(ipv4_requests)
+ if ipv4_commands:
+ ipv4_commands["afi"] = afis.get('have_ipv4')["afi"]
+ modified_afi_commands.append(ipv4_commands)
+ ipv6_commands, ipv6_requests = self.get_delete_specific_afi_fields_requests(afis.get('have_ipv6'), afis.get('have_ipv6'))
+ requests.extend(ipv6_requests)
+ if ipv6_commands:
+ ipv6_commands["afi"] = afis.get('have_ipv6')["afi"]
+ modified_afi_commands.append(ipv6_commands)
+
+ sent_commands = []
+ if modified_afi_commands:
+ sent_commands = {"afis": modified_afi_commands}
+
+ return sent_commands, requests
+
+ def get_delete_specific_requests(self, afis):
+ '''creates and returns list of requests to delete afi settings.
+ Checks if clearing settings for a ip family or just matching fields in config'''
+ modified_afi_commands = []
+ requests = []
+
+ want_ipv4 = afis.get('want_ipv4')
+ want_ipv6 = afis.get('want_ipv6')
+ have_ipv4 = afis.get('have_ipv4')
+ have_ipv6 = afis.get('have_ipv6')
+
+ if want_ipv4:
+ if want_ipv4.keys() == set(["afi"]):
+ # just afi key supplied, interpreting this as delete all config for that afi
+ ipv4_commands, ipv4_requests = self.get_delete_specific_afi_fields_requests(have_ipv4, have_ipv4)
+ else:
+ ipv4_commands, ipv4_requests = self.get_delete_specific_afi_fields_requests(want_ipv4, have_ipv4)
+ requests.extend(ipv4_requests)
+ if ipv4_commands:
+ ipv4_commands["afi"] = want_ipv4["afi"]
+ modified_afi_commands.append(ipv4_commands)
+ if want_ipv6:
+ if want_ipv6.keys() == set(["afi"]):
+ ipv6_commands, ipv6_requests = self.get_delete_specific_afi_fields_requests(have_ipv6, have_ipv6)
+ else:
+ ipv6_commands, ipv6_requests = self.get_delete_specific_afi_fields_requests(want_ipv6, have_ipv6)
+ requests.extend(ipv6_requests)
+ if ipv6_commands:
+ ipv6_commands["afi"] = want_ipv6["afi"]
+ modified_afi_commands.append(ipv6_commands)
+
+ sent_commands = []
+ if modified_afi_commands:
+ sent_commands = {"afis": modified_afi_commands}
+
+ return sent_commands, requests
+
+ def get_delete_specific_afi_fields_requests(self, want_afi, have_afi):
+ '''creates and builds list of requests for deleting some fields of dhcp snooping config for
+ one ip family. Each field checked and deleted independently from each other depending on if
+ it is specified in playbook and matches with current config'''
+ sent_commands = {}
+ requests = []
+
+ if want_afi.get('enabled') is True and have_afi.get('enabled') is True:
+ # only need to send a request if want from playbook is set to non default value and the setting currently configured is non default
+ sent_commands.update({"enabled": want_afi.get("enabled")})
+ requests.extend(self.get_delete_enabled_request(want_afi))
+ if want_afi.get('verify_mac') is False and have_afi.get('verify_mac') is False:
+ sent_commands.update({"verify_mac": want_afi.get("verify_mac")})
+ requests.extend(self.get_delete_verify_mac_request(want_afi))
+ if want_afi.get('vlans') is not None and have_afi.get('vlans') is not None and have_afi.get("vlans") != []:
+ # gathering list of vlans to be deleted. this section also handles cases where empty list of vlans is passed in
+ # which means delete all vlans
+ to_delete_vlans = have_afi["vlans"]
+ if len(want_afi["vlans"]) > 0:
+ to_delete_vlans = list(set(have_afi.get("vlans", [])).intersection(set(want_afi.get("vlans", []))))
+ to_delete = {"afi": want_afi["afi"], "vlans": to_delete_vlans}
+ if len(to_delete["vlans"]):
+ sent_commands.update({"vlans": deepcopy(to_delete_vlans)})
+ requests.extend(self.get_delete_vlans_requests(to_delete))
+ if want_afi.get('trusted') is not None and have_afi.get('trusted') is not None and have_afi.get('trusted') != []:
+ # gathering list of interfaces to be deleted. this section also handles cases where empty list of interfaces is passed in which
+ # means delete all trusted interfaces
+ to_delete_trusted = have_afi["trusted"]
+ if len(want_afi["trusted"]) > 0:
+ to_delete_trusted = want_afi["trusted"]
+ # removing interfaces that don't exist on device
+ for intf in list(to_delete_trusted):
+ if intf not in have_afi["trusted"]:
+ to_delete_trusted.remove(intf)
+ to_delete = {"afi": want_afi["afi"], "trusted": to_delete_trusted}
+ if len(to_delete["trusted"]):
+ sent_commands.update({"trusted": deepcopy(to_delete_trusted)})
+ requests.extend(self.get_delete_trusted_requests(to_delete))
+ if want_afi.get('source_bindings') is not None and have_afi.get('source_bindings') is not None and have_afi.get('source_bindings') != []:
+ # gathering list of source bindings to be deleted. this section also handles cases where empty list of bindings is passed in which
+ # means delete all trusted bindings
+ to_delete_bindings = have_afi["source_bindings"]
+ if len(want_afi["source_bindings"]) > 0:
+ to_delete_bindings = want_afi["source_bindings"]
+ # removing bindings that don't exist on device
+ existing_keys = [binding["mac_addr"] for binding in have_afi["source_bindings"]]
+ for binding in list(to_delete_bindings):
+ if binding["mac_addr"] not in existing_keys:
+ # need to check by the key since can have two different versions of same binding
+ to_delete_bindings.remove(binding)
+ to_delete = {"afi": want_afi["afi"], "source_bindings": to_delete_bindings}
+ if len(to_delete["source_bindings"]):
+ sent_commands.update({"source_bindings": deepcopy(to_delete_bindings)})
+ requests.extend(self.get_delete_specific_source_bindings_requests(to_delete))
+
+ return sent_commands, requests
+
+ def get_delete_enabled_request(self, afi):
+ '''makes and returns request to "delete" aka reset to default the enabled setting for one afi family. returns as a list'''
+ payload = {'openconfig-dhcp-snooping:dhcpv{v}-admin-enable'.format(v=self.afi_to_vnum(afi)): False}
+ return [{'path': self.enable_uri.format(v=self.afi_to_vnum(afi)), 'method': self.patch_method_value, 'data': payload}]
+
+ def get_delete_verify_mac_request(self, afi):
+ '''makes and returns request to "delete" aka reset to default the config for one afi family's verify mac setting'''
+ payload = {'openconfig-dhcp-snooping:dhcpv{v}-verify-mac-address'.format(v=self.afi_to_vnum(afi)): True}
+ return [{'path': self.verify_mac_uri.format(v=self.afi_to_vnum(afi)), 'method': self.patch_method_value, 'data': payload}]
+
+ def get_delete_vlans_requests(self, afi):
+ '''makes and returns request to delete the given vlans for the given afi faimily.
+ input expected as a dictionary of form {"afi": <ip_version>, "vlans": <list_of_vlans>}'''
+ requests = []
+ if afi.get('vlans'):
+ for vlan_id in afi.get('vlans'):
+ requests.append({
+ 'path': self.vlans_uri.format(vlan_name='Vlan' + vlan_id, v=self.afi_to_vnum(afi)),
+ 'method': self.delete_method_value
+ })
+ return requests
+
+ def get_delete_trusted_requests(self, afi):
+ '''makes and returns request to delete the given trusted interfaces for the given afi faimily.
+ input expected as a dictionary of form {"afi": <ip_version>, "trusted": [{"intf_name": <name>}...]}'''
+ requests = []
+ if afi.get('trusted'):
+ for intf in afi.get('trusted'):
+ intf_name = intf.get('intf_name')
+ if intf_name:
+ requests.append({
+ 'path': self.trusted_uri.format(name=intf_name, v=self.afi_to_vnum(afi)),
+ 'method': self.delete_method_value
+ })
+ return requests
+
+ def get_delete_all_source_bindings_request(self):
+ '''creates request to delete the source bindings list, which clears all bindings from both families'''
+ return [{'path': self.binding_uri, 'method': self.delete_method_value}]
+
+ def get_delete_specific_source_bindings_requests(self, afi):
+ '''creates and builds a list of requests to delete the source bindings listed in the given afi family
+ input expected as a dictionary of form to_delete = {"afi": <ip_version>, "source_bindings": <list of source_bindings>}'''
+ requests = []
+ for entry in afi.get('source_bindings'):
+ if entry.get('mac_addr'):
+ requests.append({
+ 'path': self.binding_uri + '={mac},{ipv}'.format(mac=entry.get('mac_addr'), ipv=afi.get('afi')),
+ 'method': self.delete_method_value
+ })
+ return requests
+
+ def get_delete_individual_source_bindings_requests(self, afi, entry):
+ '''create a request to delete the given source binding entry and address family specified
+ by afi'''
+ return [{'path': self.binding_uri + '={mac},{ipv}'.format(mac=entry.get('mac_addr'), ipv=afi.get('afi')), 'method': self.delete_method_value}]
+
+ def get_delete_replaced_groupings(self, afis):
+ '''builds list of requests to handle replaced state for both address families'''
+ modified_afi_commands = []
+ requests = []
+
+ want_ipv4 = afis.get('want_ipv4')
+ have_ipv4 = afis.get('have_ipv4')
+ want_ipv6 = afis.get('want_ipv6')
+ have_ipv6 = afis.get('have_ipv6')
+
+ if want_ipv4 and have_ipv4:
+ ipv4_commands, ipv4_requests = self.get_delete_replaced_groupings_afi(want_ipv4, have_ipv4)
+ requests.extend(ipv4_requests)
+ if ipv4_commands:
+ ipv4_commands["afi"] = want_ipv4["afi"]
+ modified_afi_commands.append(ipv4_commands)
+ if want_ipv6 and have_ipv6:
+ ipv6_commands, ipv6_requests = self.get_delete_replaced_groupings_afi(want_ipv6, have_ipv6)
+ requests.extend(ipv6_requests)
+ if ipv6_commands:
+ ipv6_commands["afi"] = want_ipv6["afi"]
+ modified_afi_commands.append(ipv6_commands)
+
+ sent_commands = []
+ if modified_afi_commands:
+ sent_commands = {"afis": modified_afi_commands}
+
+ return sent_commands, requests
+
+ def get_delete_replaced_groupings_afi(self, want_afi, have_afi):
+ '''creates and builds a list of requests to handle all parts that need to be deleted
+ while handling the replaced state for an address family'''
+ sent_commands = {}
+ requests = []
+ diff_requested = get_diff(have_afi, want_afi, self.test_keys)
+
+ if diff_requested.get("vlans") and "vlans" in want_afi:
+ # delete any vlans that are different
+ to_delete = {"afi": have_afi["afi"], "vlans": diff_requested["vlans"]}
+ sent_commands["vlans"] = deepcopy(diff_requested["vlans"])
+ requests.extend(self.get_delete_vlans_requests(to_delete))
+ if diff_requested.get('trusted') and 'trusted' in want_afi:
+ # delete anything that has a difference, covers things that are
+ # in have but not want and things in both but modified
+ to_delete = {"afi": have_afi["afi"], "trusted": diff_requested["trusted"]}
+ sent_commands["trusted"] = deepcopy(diff_requested["trusted"])
+ requests.extend(self.get_delete_trusted_requests(to_delete))
+ if diff_requested.get('source_bindings') and 'source_bindings' in want_afi:
+ # assuming source bindings considered a replaceable subsection ie the list afterwards
+ # should look exactly like what was passed into want
+ if want_afi["source_bindings"] == []:
+ # replaced told want to replace existing with blank list, only thing to do is delete existing bindings for family
+ sent_commands["source_bindings"] = deepcopy(have_afi["source_bindings"])
+ requests.extend(self.get_delete_specific_source_bindings_requests(have_afi))
+ else:
+ sent_commands["source_bindings"] = deepcopy(diff_requested["source_bindings"])
+ for entry in diff_requested["source_bindings"]:
+ requests.extend(self.get_delete_individual_source_bindings_requests(have_afi, entry))
+ return sent_commands, requests
+
+ def prep_replaced_to_merge(self, diff, afis):
+ '''preps results from a get diff for use in merging. needed for source bindings to have all data needed. get diff only returns the fields that
+ are different in each source binding when all data for it is needed instead. Fills in each source binding in diff with what is found for it in afis'''
+ if not diff or not diff.get("afis"):
+ return {}
+ for diff_afi in diff["afis"]:
+ if "source_bindings" in diff_afi:
+ for binding in diff_afi["source_bindings"]:
+ binding.update(self.match_binding(binding["mac_addr"], afis["want_" + diff_afi["afi"]]["source_bindings"]))
+
+ @staticmethod
+ def match_binding(mac_addr, bindings):
+ for binding in bindings:
+ if binding["mac_addr"] == mac_addr:
+ return binding
+ return {}
+
+ @staticmethod
+ def afi_to_vnum(afi):
+ if afi.get('afi') == 'ipv6':
+ return '6'
+ else:
+ return '4'
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/interfaces/interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/interfaces/interfaces.py
index acf985ebf..33607817b 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/interfaces/interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/interfaces/interfaces.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# © Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# © Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -18,6 +18,15 @@ try:
except ImportError:
from urllib.parse import quote
+"""
+The use of natsort causes sanity error due to it is not available in python version currently used.
+When natsort becomes available, the code here and below using it will be applied.
+from natsort import (
+ natsorted,
+ ns
+)
+"""
+from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -33,14 +42,22 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.interfaces_util import (
build_interfaces_create_request,
+ retrieve_default_intf_speed,
+ retrieve_port_group_interfaces
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
get_diff,
update_states,
- normalize_interface_name
+ normalize_interface_name,
+ remove_empties_from_list
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ get_new_config,
+ get_formatted_config_diff
)
from ansible.module_utils._text import to_native
from ansible.module_utils.connection import ConnectionError
+import re
import traceback
LIB_IMP_ERR = None
@@ -53,8 +70,54 @@ except Exception as e:
ERR_MSG = to_native(e)
LIB_IMP_ERR = traceback.format_exc()
+GET = 'get'
PATCH = 'patch'
DELETE = 'delete'
+url = 'data/openconfig-interfaces:interfaces/interface=%s'
+eth_conf_url = "/openconfig-if-ethernet:ethernet/config"
+
+port_num_regex = re.compile(r'[\d]{1,4}$')
+non_eth_attribute = ('description', 'mtu', 'enabled')
+eth_attribute = ('description', 'mtu', 'enabled', 'auto_negotiate', 'speed', 'fec', 'advertised_speed')
+
+attributes_default_value = {
+ "description": '',
+ "mtu": 9100,
+ "enabled": False,
+ "auto_negotiate": False,
+ "fec": 'FEC_DISABLED',
+ "advertised_speed": []
+}
+default_intf_speeds = {}
+port_group_interfaces = None
+
+
+def __derive_interface_config_delete_op(key_set, command, exist_conf):
+ new_conf = exist_conf
+ intf_name = command['name']
+
+ for attr in eth_attribute:
+ if attr in command:
+ if attr == "speed":
+ new_conf[attr] = default_intf_speeds[intf_name]
+ elif attr == "advertised_speed":
+ if new_conf[attr] is not None:
+ new_conf[attr] = list(set(new_conf[attr]).difference(command[attr]))
+ if new_conf[attr] == []:
+ new_conf[attr] = None
+ elif attr == "auto_negotiate":
+ new_conf[attr] = False
+ if new_conf.get('advertised_speed') is not None:
+ new_conf['advertised_speed'] = None
+ else:
+ new_conf[attr] = attributes_default_value[attr]
+
+ return True, new_conf
+
+
+TEST_KEYS_formatted_diff = [
+ {'config': {'name': '', '__delete_op': __derive_interface_config_delete_op}},
+]
class Interfaces(ConfigBase):
@@ -71,9 +134,6 @@ class Interfaces(ConfigBase):
'interfaces',
]
- params = ('description', 'mtu', 'enabled')
- delete_flag = False
-
def __init__(self, module):
super(Interfaces, self).__init__(module)
@@ -100,7 +160,7 @@ class Interfaces(ConfigBase):
warnings = list()
existing_interfaces_facts = self.get_interfaces_facts()
- commands, requests = self.set_config(existing_interfaces_facts)
+ commands, requests = self.set_config(existing_interfaces_facts, warnings)
if commands and len(requests) > 0:
if not self._module.check_mode:
try:
@@ -116,10 +176,27 @@ class Interfaces(ConfigBase):
if result['changed']:
result['after'] = changed_interfaces_facts
+ new_config = changed_interfaces_facts
+ old_config = existing_interfaces_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_interfaces_facts,
+ TEST_KEYS_formatted_diff)
+ # See the above comment about natsort module
+ # new_config = natsorted(new_config, key=lambda x: x['name'])
+ # For time-being, use simple "sort"
+ new_config.sort(key=lambda x: x['name'])
+ result['after(generated)'] = new_config
+ old_config.sort(key=lambda x: x['name'])
+
+ if self._module._diff:
+ result['diff'] = get_formatted_config_diff(old_config,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
- def set_config(self, existing_interfaces_facts):
+ def set_config(self, existing_interfaces_facts, warnings):
""" Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
@@ -128,12 +205,51 @@ class Interfaces(ConfigBase):
to the desired configuration
"""
want = self._module.params['config']
- normalize_interface_name(want, self._module)
have = existing_interfaces_facts
+ self.filter_out_mgmt_interface(want, have)
- resp = self.set_state(want, have)
+ new_want, new_have = self.validate_config(want, have, warnings)
+ resp = self.set_state(new_want, new_have)
return to_list(resp)
+ def validate_config(self, want, have, warnings):
+ new_want = deepcopy(want)
+ new_have = deepcopy(have)
+ normalize_interface_name(new_want, self._module)
+ for cmd in new_have:
+ # If auto_neg is true, ignore speed
+ if cmd.get('auto_negotiate') is True:
+ if cmd.get('speed'):
+ cmd.pop('speed')
+ elif cmd.get('advertised_speed'):
+ cmd.pop('advertised_speed')
+
+ if new_want:
+ for cmd in new_want:
+ intf = next((cfg for cfg in new_have if cfg['name'] == cmd['name']), None)
+ state = self._module.params['state']
+ if cmd.get('advertised_speed'):
+ cmd['advertised_speed'].sort()
+
+ if state != "deleted":
+ if intf:
+ want_autoneg = cmd.get('auto_negotiate')
+ have_autoneg = intf.get('auto_negotiate')
+ want_speed = cmd.get('speed')
+ want_ads = cmd.get('advertised_speed')
+
+ if want_speed is not None:
+ if want_autoneg or (want_ads and have_autoneg):
+ warnings.append("Speed cannot be configured when autoneg is enabled")
+ cmd.pop('speed')
+
+ if want_ads is not None:
+ if want_autoneg is False or (not want_autoneg and not have_autoneg):
+ warnings.append("Advertised speed cannot be configured when autoneg is disabled")
+ cmd.pop('advertised_speed')
+
+ return new_want, new_have
+
def set_state(self, want, have):
""" Select the appropriate function based on the state provided
@@ -149,18 +265,17 @@ class Interfaces(ConfigBase):
# removing the dict in case diff found
if state == 'overridden':
- have = [each_intf for each_intf in have if each_intf['name'].startswith('Ethernet')]
- commands, requests = self._state_overridden(want, have, diff)
+ commands, requests = self._state_overridden(want, have)
elif state == 'deleted':
- commands, requests = self._state_deleted(want, have, diff)
+ commands, requests = self._state_deleted(want, have)
elif state == 'merged':
commands, requests = self._state_merged(want, have, diff)
elif state == 'replaced':
- commands, requests = self._state_replaced(want, have, diff)
+ commands, requests = self._state_replaced(want, have)
return commands, requests
- def _state_replaced(self, want, have, diff):
+ def _state_replaced(self, want, have):
""" The command generator when state is replaced
:param want: the desired configuration as a dictionary
@@ -170,17 +285,13 @@ class Interfaces(ConfigBase):
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
- commands = self.filter_comands_to_change(diff, have)
- requests = self.get_delete_interface_requests(commands, have)
- requests.extend(self.get_modify_interface_requests(commands, have))
- if commands and len(requests) > 0:
- commands = update_states(commands, "replaced")
- else:
- commands = []
+ commands = []
+ requests = []
+ commands, requests = self.get_replaced_overridden_config(want, have, "replaced")
return commands, requests
- def _state_overridden(self, want, have, diff):
+ def _state_overridden(self, want, have):
""" The command generator when state is overridden
:param want: the desired configuration as a dictionary
@@ -190,18 +301,9 @@ class Interfaces(ConfigBase):
to the desired configuration
"""
commands = []
- commands_del = self.filter_comands_to_change(want, have)
- requests = self.get_delete_interface_requests(commands_del, have)
- del_req_count = len(requests)
- if commands_del and del_req_count > 0:
- commands_del = update_states(commands_del, "deleted")
- commands.extend(commands_del)
-
- commands_over = diff
- requests.extend(self.get_modify_interface_requests(commands_over, have))
- if commands_over and len(requests) > del_req_count:
- commands_over = update_states(commands_over, "overridden")
- commands.extend(commands_over)
+ requests = []
+
+ commands, requests = self.get_replaced_overridden_config(want, have, "overridden")
return commands, requests
@@ -214,8 +316,8 @@ class Interfaces(ConfigBase):
:returns: the commands necessary to merge the provided into
the current configuration
"""
- commands = diff
- requests = self.get_modify_interface_requests(commands, have)
+ commands = self.filter_commands_to_change(diff, have)
+ requests = self.get_interface_requests(commands, have)
if commands and len(requests) > 0:
commands = update_states(commands, "merged")
else:
@@ -223,7 +325,7 @@ class Interfaces(ConfigBase):
return commands, requests
- def _state_deleted(self, want, have, diff):
+ def _state_deleted(self, want, have):
""" The command generator when state is deleted
:param want: the objects from which the configuration should be removed
@@ -234,34 +336,23 @@ class Interfaces(ConfigBase):
of the provided objects
"""
# if want is none, then delete all the interfaces
+
+ want = remove_empties_from_list(want)
+ delete_all = False
if not want:
commands = have
+ delete_all = True
else:
commands = want
- requests = self.get_delete_interface_requests(commands, have)
-
- if commands and len(requests) > 0:
- commands = update_states(commands, "deleted")
- else:
- commands = []
-
- return commands, requests
-
- def filter_comands_to_delete(self, configs, have):
+ commands_del, requests = self.handle_delete_interface_config(commands, have, delete_all)
commands = []
+ if commands_del:
+ commands.extend(update_states(commands_del, "deleted"))
- for conf in configs:
- if self.is_this_delete_required(conf, have):
- temp_conf = dict()
- temp_conf['name'] = conf['name']
- temp_conf['description'] = ''
- temp_conf['mtu'] = 9100
- temp_conf['enabled'] = True
- commands.append(temp_conf)
- return commands
+ return commands, requests
- def filter_comands_to_change(self, configs, have):
+ def filter_commands_to_change(self, configs, have):
commands = []
if configs:
for conf in configs:
@@ -269,17 +360,18 @@ class Interfaces(ConfigBase):
commands.append(conf)
return commands
- def get_modify_interface_requests(self, configs, have):
- self.delete_flag = False
- commands = self.filter_comands_to_change(configs, have)
-
- return self.get_interface_requests(commands, have)
-
- def get_delete_interface_requests(self, configs, have):
- self.delete_flag = True
- commands = self.filter_comands_to_delete(configs, have)
+ def is_this_change_required(self, conf, have):
+ intf = next((e_intf for e_intf in have if conf['name'] == e_intf['name']), None)
+ if intf:
+ # Check all parameter if any one is different from existing
+ for param in eth_attribute:
+ if conf.get(param) is not None and conf.get(param) != intf.get(param):
+ return True
+ else:
+ # if given interface is not present
+ return True
- return self.get_interface_requests(commands, have)
+ return False
def get_interface_requests(self, configs, have):
requests = []
@@ -288,67 +380,285 @@ class Interfaces(ConfigBase):
# Create URL and payload
for conf in configs:
- name = conf["name"]
- if self.delete_flag and name.startswith('Loopback'):
- method = DELETE
- url = 'data/openconfig-interfaces:interfaces/interface=%s' % quote(name, safe='')
- request = {"path": url, "method": method}
+ name = conf['name']
+ have_conf = next((cfg for cfg in have if cfg['name'] == name), None)
+
+ # Create Loopback incase if not available in have
+ if name.startswith('Loopback'):
+ if not have_conf:
+ loopback_create_request = build_interfaces_create_request(name)
+ requests.append(loopback_create_request)
else:
- # Create Loopback in case not availble in have
- if name.startswith('Loopback'):
- have_conf = next((cfg for cfg in have if cfg['name'] == name), None)
- if not have_conf:
- loopback_create_request = build_interfaces_create_request(name)
- requests.append(loopback_create_request)
- method = PATCH
- url = 'data/openconfig-interfaces:interfaces/interface=%s/config' % quote(name, safe='')
- payload = self.build_create_payload(conf)
- request = {"path": url, "method": method, "data": payload}
- requests.append(request)
-
+ attribute = eth_attribute if name.startswith('Eth') else non_eth_attribute
+
+ for attr in attribute:
+ if attr in conf:
+ c_attr = conf.get(attr)
+ h_attr = have_conf.get(attr)
+ attr_request = self.build_create_request(c_attr, h_attr, name, attr)
+ if attr_request:
+ requests.append(attr_request)
return requests
- def is_this_delete_required(self, conf, have):
- if conf['name'] == "eth0":
- return False
- intf = next((e_intf for e_intf in have if conf['name'] == e_intf['name']), None)
- if intf:
- if (intf['name'].startswith('Loopback') or not ((intf.get('description') is None or intf.get('description') == '') and
- (intf.get('enabled') is None or intf.get('enabled') is True) and (intf.get('mtu') is None or intf.get('mtu') == 9100))):
- return True
- return False
+ def build_create_request(self, c_attr, h_attr, intf_name, attr):
+ attributes_payload = {
+ "speed": 'port-speed',
+ "auto_negotiate": 'auto-negotiate',
+ "fec": 'openconfig-if-ethernet-ext2:port-fec',
+ "advertised_speed": 'openconfig-if-ethernet-ext2:advertised-speed'
+ }
+
+ config_url = (url + eth_conf_url) % quote(intf_name, safe='')
+ payload = {'openconfig-if-ethernet:config': {}}
+ payload_attr = attributes_payload.get(attr, attr)
+ method = PATCH
+
+ if attr in ('description', 'mtu', 'enabled'):
+ config_url = (url + '/config') % quote(intf_name, safe='')
+ payload = {'openconfig-interfaces:config': {}}
+ payload['openconfig-interfaces:config'][payload_attr] = c_attr
+ return {"path": config_url, "method": method, "data": payload}
+
+ elif attr in ('fec'):
+ payload['openconfig-if-ethernet:config'][payload_attr] = 'openconfig-platform-types:' + c_attr
+ return {"path": config_url, "method": method, "data": payload}
+ else:
+ payload['openconfig-if-ethernet:config'][payload_attr] = c_attr
+ if attr == 'speed':
+ if self.is_port_in_port_group(intf_name):
+ self._module.fail_json(msg='Unable to configure speed in port group member. Please use port group module to change the speed')
+ payload['openconfig-if-ethernet:config'][payload_attr] = 'openconfig-if-ethernet:' + c_attr
+ if attr == 'advertised_speed':
+ c_ads = c_attr if c_attr else []
+ h_ads = h_attr if h_attr else []
+ new_ads = list(set(h_ads).union(c_ads))
+ if new_ads:
+ payload['openconfig-if-ethernet:config'][payload_attr] = ','.join(new_ads)
+
+ return {"path": config_url, "method": method, "data": payload}
+
+ return []
+
+ def handle_delete_interface_config(self, commands, have, delete_all=False):
+ if not commands:
+ return [], []
+
+ commands_del, requests = [], []
+ # Create URL and payload
+ for conf in commands:
+ name = conf['name']
+ have_conf = next((cfg for cfg in have if cfg['name'] == name), None)
+ if have_conf:
+ lp_key_set = set(conf.keys())
+ if name.startswith('Loopback'):
+ if delete_all or len(lp_key_set) == 1:
+ method = DELETE
+ lpbk_url = url % quote(name, safe='')
+ request = {"path": lpbk_url, "method": DELETE}
+ requests.append(request)
+
+ commands_del.append({'name': name})
+ continue
+
+ cmd = deepcopy(have_conf) if len(lp_key_set) == 1 else deepcopy(conf)
+
+ del_cmd = {'name': name}
+ attribute = eth_attribute if name.startswith('Eth') else non_eth_attribute
+
+ for attr in attribute:
+ if attr in conf:
+ c_attr = conf.get(attr)
+ h_attr = have_conf.get(attr)
+ default_val = self.get_default_value(attr, h_attr, name)
+ if c_attr is not None and h_attr is not None and h_attr != default_val:
+ if attr == 'advertised_speed':
+ c_ads = c_attr if c_attr else []
+ h_ads = h_attr if h_attr else []
+ new_ads = list(set(h_attr).intersection(c_attr))
+ if new_ads:
+ del_cmd.update({attr: new_ads})
+ requests.append(self.build_delete_request(c_ads, h_ads, name, attr))
+ else:
+ del_cmd.update({attr: h_attr})
+ requests.append(self.build_delete_request(c_attr, h_attr, name, attr))
+ if requests:
+ commands_del.append(del_cmd)
+
+ return commands_del, requests
+
+ def get_replaced_overridden_config(self, want, have, cur_state):
+ commands, requests = [], []
+
+ commands_add, commands_del = [], []
+ requests_add, requests_del = [], []
+
+ delete_all = False
+ for conf in want:
+ name = conf['name']
+ intf = next((e_intf for e_intf in have if name == e_intf['name']), None)
+ if name.startswith('Loopback'):
+ if not intf:
+ commands_add.append({'name': name})
+ continue
+
+ temp_conf = {}
+ add_conf, del_conf = {}, {}
+
+ temp_conf['name'] = name
+ attribute = eth_attribute if name.startswith('Eth') else non_eth_attribute
+
+ if not intf:
+ commands_add.append(conf)
+ else:
+ is_change = False
+ non_ads_attr_specified = False
+ if cur_state == "replaced":
+ for attr in conf:
+ if attr != 'name' and attr != 'advertised_speed' and conf.get(attr) is not None:
+ non_ads_attr_specified = True
+ break
+ else:
+ non_ads_attr_specified = True
+
+ for attr in attribute:
+ c_attr = conf.get(attr)
+ h_attr = intf.get(attr)
+ default_val = self.get_default_value(attr, h_attr, name)
+ if attr != 'advertised_speed':
+ if c_attr is None and h_attr is not None and h_attr != default_val and non_ads_attr_specified:
+ del_conf[attr] = h_attr
+ requests_del.append(self.build_delete_request(c_attr, h_attr, name, attr))
+ if c_attr is not None and c_attr != h_attr:
+ add_conf[attr] = c_attr
+ requests_add.append(self.build_create_request(c_attr, h_attr, name, attr))
+ else:
+ c_ads = c_attr if c_attr else []
+ h_ads = h_attr if h_attr else []
+ new_ads = list(set(c_ads).difference(h_ads))
+ delete_ads = list(set(h_ads).difference(c_ads))
+ if new_ads:
+ add_conf[attr] = new_ads
+ requests_add.append(self.build_create_request(new_ads, h_attr, name, attr))
+ if delete_ads:
+ del_conf[attr] = delete_ads
+ requests_del.append(self.build_delete_request(delete_ads, h_attr, name, attr))
+
+ if add_conf:
+ add_conf['name'] = name
+ commands_add.append(add_conf)
+
+ if del_conf:
+ del_conf['name'] = name
+ commands_del.append(del_conf)
+
+ if cur_state == "overridden":
+ for have_conf in have:
+ in_want = next((conf for conf in want if conf['name'] == have_conf['name']), None)
+ if not in_want:
+ del_conf = {}
+ for attr in attribute:
+ h_attr = have_conf.get(attr)
+ if h_attr is not None and h_attr != self.get_default_value(attr, h_attr, have_conf['name']):
+ del_conf[attr] = h_attr
+ requests_del.append(self.build_delete_request([], h_attr, have_conf['name'], attr))
+ if del_conf:
+ del_conf['name'] = have_conf['name']
+ commands_del.append(del_conf)
+
+ if len(requests_del) > 0:
+ commands.extend(update_states(commands_del, "deleted"))
+ requests.extend(requests_del)
+
+ if len(requests_add) > 0:
+ commands.extend(update_states(commands_add, cur_state))
+ requests.extend(requests_add)
- def is_this_change_required(self, conf, have):
- if conf['name'] == "eth0":
- return False
- ret_flag = False
- intf = next((e_intf for e_intf in have if conf['name'] == e_intf['name']), None)
- if intf:
- # Check all parameter if any one is differen from existing
- for param in self.params:
- if conf.get(param) is not None and conf.get(param) != intf.get(param):
- ret_flag = True
- break
- # if given interface is not present
+ return commands, requests
+
+ def build_delete_request(self, c_attr, h_attr, intf_name, attr):
+ method = DELETE
+ attributes_payload = {
+ "speed": 'port-speed',
+ "auto_negotiate": 'auto-negotiate',
+ "fec": 'openconfig-if-ethernet-ext2:port-fec',
+ "advertised_speed": 'openconfig-if-ethernet-ext2:advertised-speed'
+ }
+
+ config_url = (url + eth_conf_url) % quote(intf_name, safe='')
+ payload = {'openconfig-if-ethernet:config': {}}
+ payload_attr = attributes_payload.get(attr, attr)
+
+ if attr in ('description', 'mtu', 'enabled'):
+ attr_url = "/config/" + payload_attr
+ config_url = (url + attr_url) % quote(intf_name, safe='')
+ return {"path": config_url, "method": method}
+
+ elif attr in ('fec'):
+ payload_attr = attributes_payload[attr]
+ payload['openconfig-if-ethernet:config'][payload_attr] = 'FEC_DISABLED'
+ return {"path": config_url, "method": PATCH, "data": payload}
else:
- ret_flag = True
+ payload_attr = attributes_payload[attr]
+ if attr == 'auto_negotiate':
+ # For auto-negotiate, we assign value to False since deleting the attribute will become None if deleted
+ # In case, if auto-negotiate is disabled, both speed and advertised_speed will have default value.
+ payload['openconfig-if-ethernet:config'][payload_attr] = False
+ return {"path": config_url, "method": PATCH, "data": payload}
+
+ if attr == 'speed':
+ attr_url = eth_conf_url + "/" + attributes_payload[attr]
+ del_config_url = (url + attr_url) % quote(intf_name, safe='')
+ return {"path": del_config_url, "method": method}
+
+ if attr == 'advertised_speed':
+ new_ads = list(set(h_attr).difference(c_attr))
+ if new_ads:
+ payload['openconfig-if-ethernet:config'][payload_attr] = ','.join(new_ads)
+ return {"path": config_url, "method": PATCH, "data": payload}
+ else:
+ attr_url = eth_conf_url + "/" + attributes_payload[attr]
+ del_config_url = (url + attr_url) % quote(intf_name, safe='')
+ return {"path": del_config_url, "method": method}
+ return {}
+
+ # Utils
+ def get_default_value(self, attr, h_attr, intf_name):
+ if attr == 'speed':
+ default_val = self._retrieve_default_intf_speed(intf_name)
+ if default_val == 'SPEED_DEFAULT':
+ # Incase if the port belongs to port-group, we can not able to delete the speed
+ default_val = h_attr
+ return default_val
+ else:
+ return attributes_default_value[attr]
+
+ def filter_out_mgmt_interface(self, want, have):
+ if want:
+ mgmt_intf = next((intf for intf in want if intf['name'] == 'Management0'), None)
+ if mgmt_intf:
+ self._module.fail_json(msg='Management interface should not be configured.')
+
+ for intf in have:
+ if intf['name'] == 'Management0':
+ have.remove(intf)
+ break
+
+ def is_port_in_port_group(self, intf_name):
+ global port_group_interfaces
+ if port_group_interfaces is None:
+ port_group_interfaces = retrieve_port_group_interfaces(self._module)
+ port_num = re.search(port_num_regex, intf_name)
+ port_num = int(port_num.group(0))
+ if port_num in port_group_interfaces:
+ return True
- return ret_flag
+ return False
- def build_create_payload(self, conf):
- temp_conf = dict()
- temp_conf['name'] = conf['name']
+ def _retrieve_default_intf_speed(self, intf_name):
+ # To avoid multiple get requests
+ if self.is_port_in_port_group(intf_name):
+ return "SPEED_DEFAULT"
- if not temp_conf['name'].startswith('Loopback'):
- if conf.get('enabled') is not None:
- if conf.get('enabled'):
- temp_conf['enabled'] = True
- else:
- temp_conf['enabled'] = False
- if conf.get('description') is not None:
- temp_conf['description'] = conf['description']
- if conf.get('mtu') is not None:
- temp_conf['mtu'] = conf['mtu']
-
- payload = {'openconfig-interfaces:config': temp_conf}
- return payload
+ if default_intf_speeds.get(intf_name) is None:
+ default_intf_speeds[intf_name] = retrieve_default_intf_speed(self._module, intf_name)
+ return default_intf_speeds[intf_name]
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/ip_neighbor/ip_neighbor.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/ip_neighbor/ip_neighbor.py
new file mode 100644
index 000000000..ab3a4dde6
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/ip_neighbor/ip_neighbor.py
@@ -0,0 +1,420 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_ip_neighbor class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+ remove_empties
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import (
+ Facts
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_diff,
+ update_states,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ get_new_config,
+ get_formatted_config_diff
+)
+from ansible.module_utils.connection import ConnectionError
+
+GET = 'get'
+PATCH = 'patch'
+PUT = 'put'
+DELETE = 'delete'
+GLB_URL = 'data/openconfig-neighbor:neighbor-globals/neighbor-global'
+URL = 'data/openconfig-neighbor:neighbor-globals/neighbor-global=Values'
+CONFIG_URL = 'data/openconfig-neighbor:neighbor-globals/neighbor-global=Values/config'
+
+IP_NEIGH_CONFIG_DEFAULT = {
+ 'ipv4_arp_timeout': 180,
+ 'ipv4_drop_neighbor_aging_time': 300,
+ 'ipv6_drop_neighbor_aging_time': 300,
+ 'ipv6_nd_cache_expiry': 180,
+ 'num_local_neigh': 0
+}
+
+IP_NEIGH_CONFIG_REQ_DEFAULT = {
+ 'name': 'Values',
+ 'ipv4-arp-timeout': 180,
+ 'ipv4-drop-neighbor-aging-time': 300,
+ 'ipv6-drop-neighbor-aging-time': 300,
+ 'ipv6-nd-cache-expiry': 180,
+ 'num-local-neigh': 0
+}
+
+
+def __derive_ip_neighbor_config_delete_op(key_set, command, exist_conf):
+ new_conf = exist_conf
+
+ if 'ipv4_arp_timeout' in command:
+ new_conf['ipv4_arp_timeout'] = IP_NEIGH_CONFIG_DEFAULT['ipv4_arp_timeout']
+
+ if 'ipv4_drop_neighbor_aging_time' in command:
+ new_conf['ipv4_drop_neighbor_aging_time'] = \
+ IP_NEIGH_CONFIG_DEFAULT['ipv4_drop_neighbor_aging_time']
+
+ if 'ipv6_drop_neighbor_aging_time' in command:
+ new_conf['ipv6_drop_neighbor_aging_time'] = \
+ IP_NEIGH_CONFIG_DEFAULT['ipv6_drop_neighbor_aging_time']
+
+ if 'ipv6_nd_cache_expiry' in command:
+ new_conf['ipv6_nd_cache_expiry'] = IP_NEIGH_CONFIG_DEFAULT['ipv6_nd_cache_expiry']
+
+ if 'num_local_neigh' in command:
+ new_conf['num_local_neigh'] = IP_NEIGH_CONFIG_DEFAULT['num_local_neigh']
+
+ return True, new_conf
+
+
+TEST_KEYS_formatted_diff = [
+ {'__default_ops': {'__delete_op': __derive_ip_neighbor_config_delete_op}},
+]
+
+
+class Ip_neighbor(ConfigBase):
+ """
+ The sonic_ip_neighbor class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'ip_neighbor',
+ ]
+
+ def __init__(self, module):
+ super(Ip_neighbor, self).__init__(module)
+
+ def get_ip_neighbor_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ ip_neighbor_facts = facts['ansible_network_resources'].get('ip_neighbor')
+ if not ip_neighbor_facts:
+ requests = self.build_create_all_requests()
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ ip_neighbor_facts = facts['ansible_network_resources'].get('ip_neighbor')
+
+ if not ip_neighbor_facts:
+ err_msg = "IP neighbor module: get facts failed."
+ self._module.fail_json(msg=err_msg, code=500)
+
+ return ip_neighbor_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+ commands = list()
+ requests = list()
+
+ existing_ip_neighbor_facts = self.get_ip_neighbor_facts()
+
+ commands, requests = self.set_config(existing_ip_neighbor_facts)
+
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_ip_neighbor_facts = self.get_ip_neighbor_facts()
+
+ result['before'] = existing_ip_neighbor_facts
+ if result['changed']:
+ result['after'] = changed_ip_neighbor_facts
+
+ new_config = changed_ip_neighbor_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_ip_neighbor_facts,
+ TEST_KEYS_formatted_diff)
+ result['after(generated)'] = new_config
+
+ if self._module._diff:
+ result['diff'] = get_formatted_config_diff(existing_ip_neighbor_facts,
+ new_config,
+ self._module._verbosity)
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_ip_neighbor_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ have = existing_ip_neighbor_facts
+
+ resp = self.set_state(want, have)
+
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params['state']
+ want = remove_empties(want)
+
+ if state == 'merged':
+ commands, requests = self._state_merged(want, have)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have)
+ elif state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+
+ return commands, requests
+
+ def _state_merged(self, want, have):
+ """ The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = get_diff(want, have)
+ requests = []
+
+ if commands:
+ requests = self.build_merge_requests(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ delete_all = False
+ if not want:
+ tmp_commands = have
+ delete_all = True
+ else:
+ tmp_commands = want
+ tmp_commands = self.preprocess_delete_commands(tmp_commands, have)
+
+ commands = get_diff(tmp_commands, IP_NEIGH_CONFIG_DEFAULT)
+
+ requests = []
+ if commands:
+ requests = self.build_delete_requests(commands, delete_all)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_replaced(self, want, have):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ new_want = self.augment_want_with_default(want)
+ commands = get_diff(new_want, have)
+
+ requests = []
+ if commands:
+ requests = self.build_merge_requests(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "replaced")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ new_want = self.augment_want_with_default(want)
+ commands = get_diff(new_want, have)
+
+ requests = []
+ if commands:
+ requests = self.build_merge_requests(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "overridden")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def preprocess_delete_commands(self, commands, have):
+ new_commands = dict()
+
+ if 'ipv4_arp_timeout' in commands:
+ new_commands['ipv4_arp_timeout'] = have['ipv4_arp_timeout']
+
+ if 'ipv4_drop_neighbor_aging_time' in commands:
+ new_commands['ipv4_drop_neighbor_aging_time'] = have['ipv4_drop_neighbor_aging_time']
+
+ if 'ipv6_drop_neighbor_aging_time' in commands:
+ new_commands['ipv6_drop_neighbor_aging_time'] = have['ipv6_drop_neighbor_aging_time']
+
+ if 'ipv6_nd_cache_expiry' in commands:
+ new_commands['ipv6_nd_cache_expiry'] = have['ipv6_nd_cache_expiry']
+
+ if 'num_local_neigh' in commands:
+ new_commands['num_local_neigh'] = have['num_local_neigh']
+
+ return new_commands
+
+ def augment_want_with_default(self, want):
+ new_want = IP_NEIGH_CONFIG_DEFAULT
+
+ if 'ipv4_arp_timeout' in want:
+ new_want['ipv4_arp_timeout'] = want['ipv4_arp_timeout']
+
+ if 'ipv4_drop_neighbor_aging_time' in want:
+ new_want['ipv4_drop_neighbor_aging_time'] = want['ipv4_drop_neighbor_aging_time']
+
+ if 'ipv6_drop_neighbor_aging_time' in want:
+ new_want['ipv6_drop_neighbor_aging_time'] = want['ipv6_drop_neighbor_aging_time']
+
+ if 'ipv6_nd_cache_expiry' in want:
+ new_want['ipv6_nd_cache_expiry'] = want['ipv6_nd_cache_expiry']
+
+ if 'num_local_neigh' in want:
+ new_want['num_local_neigh'] = want['num_local_neigh']
+
+ return new_want
+
+ def build_create_all_requests(self):
+ requests = []
+ payload = {
+ "openconfig-neighbor:neighbor-global":
+ [{"name": "Values",
+ "config": IP_NEIGH_CONFIG_REQ_DEFAULT}]
+ }
+ method = PUT
+
+ request = {"path": GLB_URL, "method": method, "data": payload}
+ requests.append(request)
+ return requests
+
+ def build_merge_requests(self, conf):
+ requests = []
+ ip_neigh_config = dict()
+
+ if 'ipv4_arp_timeout' in conf:
+ ip_neigh_config['ipv4-arp-timeout'] = conf['ipv4_arp_timeout']
+
+ if 'ipv4_drop_neighbor_aging_time' in conf:
+ ip_neigh_config['ipv4-drop-neighbor-aging-time'] = conf['ipv4_drop_neighbor_aging_time']
+
+ if 'ipv6_drop_neighbor_aging_time' in conf:
+ ip_neigh_config['ipv6-drop-neighbor-aging-time'] = conf['ipv6_drop_neighbor_aging_time']
+
+ if 'ipv6_nd_cache_expiry' in conf:
+ ip_neigh_config['ipv6-nd-cache-expiry'] = conf['ipv6_nd_cache_expiry']
+
+ if 'num_local_neigh' in conf:
+ ip_neigh_config['num-local-neigh'] = conf['num_local_neigh']
+
+ if ip_neigh_config:
+ payload = {'config': ip_neigh_config}
+ method = PATCH
+ requests = {"path": CONFIG_URL, "method": method, "data": payload}
+
+ return requests
+
+ def build_delete_requests(self, conf, delete_all):
+ requests = []
+ method = DELETE
+
+ if delete_all:
+ request = {"path": URL, "method": method}
+ requests.append(request)
+ return requests
+
+ if 'ipv4_arp_timeout' in conf:
+ req_url = CONFIG_URL + '/ipv4-arp-timeout'
+ request = {"path": req_url, "method": method}
+ requests.append(request)
+
+ if 'ipv4_drop_neighbor_aging_time' in conf:
+ req_url = CONFIG_URL + '/ipv4-drop-neighbor-aging-time'
+ request = {"path": req_url, "method": method}
+ requests.append(request)
+
+ if 'ipv6_drop_neighbor_aging_time' in conf:
+ req_url = CONFIG_URL + '/ipv6-drop-neighbor-aging-time'
+ request = {"path": req_url, "method": method}
+ requests.append(request)
+
+ if 'ipv6_nd_cache_expiry' in conf:
+ req_url = CONFIG_URL + '/ipv6-nd-cache-expiry'
+ request = {"path": req_url, "method": method}
+ requests.append(request)
+
+ if 'num_local_neigh' in conf:
+ req_url = CONFIG_URL + '/num-local-neigh'
+ request = {"path": req_url, "method": method}
+ requests.append(request)
+
+ return requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_acls/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_acls/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_acls/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_acls/l2_acls.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_acls/l2_acls.py
new file mode 100644
index 000000000..392e69039
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_acls/l2_acls.py
@@ -0,0 +1,602 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_l2_acls class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ast import literal_eval
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.common.validation import check_required_arguments
+from ansible.module_utils.connection import ConnectionError
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+ remove_empties,
+ validate_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ update_states
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ get_new_config,
+ get_formatted_config_diff
+)
+
+DELETE = 'delete'
+PATCH = 'patch'
+POST = 'post'
+
+TEST_KEYS_formatted_diff = [
+ {'config': {'name': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+ {'rules': {'sequence_num': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+]
+
+L2_ACL_TYPE = 'ACL_L2'
+ETHERTYPE_FORMAT = '0x{:04x}'
+
+ethertype_value_to_protocol_map = {
+ '0x0800': 'ipv4',
+ '0x0806': 'arp',
+ '0x86dd': 'ipv6'
+}
+pcp_value_to_traffic_map = {
+ 0: 'be',
+ 1: 'bk',
+ 2: 'ee',
+ 3: 'ca',
+ 4: 'vi',
+ 5: 'vo',
+ 6: 'ic',
+ 7: 'nc'
+}
+
+# Spec value to payload value mappings
+action_value_to_payload_map = {
+ 'permit': 'ACCEPT',
+ 'discard': 'DISCARD',
+ 'do-not-nat': 'DO_NOT_NAT',
+ 'deny': 'DROP',
+ 'transit': 'TRANSIT'
+}
+ethertype_protocol_to_payload_map = {
+ 'arp': 'ETHERTYPE_ARP',
+ 'ipv4': 'ETHERTYPE_IPV4',
+ 'ipv6': 'ETHERTYPE_IPV6'
+}
+ethertype_value_to_payload_map = {
+ '0x8847': 'ETHERTYPE_MPLS',
+ '0x88cc': 'ETHERTYPE_LLDP',
+ '0x8915': 'ETHERTYPE_ROCE'
+}
+pcp_traffic_to_value_map = {v: k for k, v in pcp_value_to_traffic_map.items()}
+
+
+class L2_acls(ConfigBase):
+ """
+ The sonic_l2_acls class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'l2_acls',
+ ]
+
+ acl_path = 'data/openconfig-acl:acl/acl-sets/acl-set'
+ l2_acl_path = 'data/openconfig-acl:acl/acl-sets/acl-set={acl_name},ACL_L2'
+ l2_acl_rule_path = 'data/openconfig-acl:acl/acl-sets/acl-set={acl_name},ACL_L2/acl-entries'
+ l2_acl_remark_path = 'data/openconfig-acl:acl/acl-sets/acl-set={acl_name},ACL_L2/config/description'
+
+ def __init__(self, module):
+ super(L2_acls, self).__init__(module)
+
+ def get_l2_acls_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ l2_acls_facts = facts['ansible_network_resources'].get('l2_acls')
+ if not l2_acls_facts:
+ return []
+ return l2_acls_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = []
+
+ existing_l2_acls_facts = self.get_l2_acls_facts()
+ commands, requests = self.set_config(existing_l2_acls_facts)
+ if commands:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._handle_failure_response(exc)
+
+ result['changed'] = True
+
+ changed_l2_acls_facts = self.get_l2_acls_facts()
+
+ result['before'] = existing_l2_acls_facts
+ if result['changed']:
+ result['after'] = changed_l2_acls_facts
+
+ result['commands'] = commands
+
+ new_config = changed_l2_acls_facts
+ old_config = existing_l2_acls_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_l2_acls_facts,
+ TEST_KEYS_formatted_diff)
+ result['after(generated)'] = new_config
+ if self._module._diff:
+ self.sort_config(new_config)
+ self.sort_config(old_config)
+ result['diff'] = get_formatted_config_diff(old_config,
+ new_config,
+ self._module._verbosity)
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_l2_acls_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ if want:
+ want = self.validate_and_normalize_config(want)
+ else:
+ want = []
+
+ have = existing_l2_acls_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params['state']
+ if state in ('merged', 'overridden', 'replaced'):
+ commands, requests = self._state_merged_overridden_replaced(want, have, state)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+
+ return commands, requests
+
+ def _handle_failure_response(self, connection_error):
+ log = None
+ try:
+ response = literal_eval(connection_error.args[0])
+ error_app_tag = response['ietf-restconf:errors']['error'][0].get('error-app-tag')
+ except Exception:
+ pass
+ else:
+ if error_app_tag == 'too-many-elements':
+ log = 'Exceeds maximum number of ACL / ACL Rules'
+ elif error_app_tag == 'update-not-allowed':
+ log = 'Creating ACLs with same name and different type not allowed'
+
+ if log:
+ response.update({u'log': log})
+ self._module.fail_json(msg=to_text(response), code=connection_error.code)
+ else:
+ self._module.fail_json(msg=str(connection_error), code=connection_error.code)
+
+ def _state_merged_overridden_replaced(self, want, have, state):
+ """ The command generator when state is merged/overridden/replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ add_commands = []
+ del_commands = []
+ commands = []
+
+ add_requests = []
+ del_requests = []
+ requests = []
+
+ have_dict = self._convert_config_list_to_dict(have)
+ want_dict = self._convert_config_list_to_dict(want)
+ have_acl_names = set(have_dict.keys())
+ want_acl_names = set(want_dict.keys())
+
+ if state == 'overridden':
+ # Delete non-modified ACLs
+ for acl_name in have_acl_names.difference(want_acl_names):
+ del_commands.append({'name': acl_name})
+ del_requests.append(self.get_delete_l2_acl_request(acl_name))
+
+ # Modify existing ACLs
+ for acl_name in want_acl_names.intersection(have_acl_names):
+ acl_add_command = {'name': acl_name}
+ acl_del_command = {'name': acl_name}
+ rule_add_commands = []
+ rule_del_commands = []
+
+ have_acl = have_dict[acl_name]
+ want_acl = want_dict[acl_name]
+ if not want_acl['remark']:
+ if have_acl['remark'] and state in ('replaced', 'overridden'):
+ acl_del_command['remark'] = have_acl['remark']
+ del_requests.append(self.get_delete_l2_acl_remark_request(acl_name))
+ else:
+ if want_acl['remark'] != have_acl['remark']:
+ acl_add_command['remark'] = want_acl['remark']
+ add_requests.append(self.get_create_l2_acl_remark_request(acl_name, want_acl['remark']))
+
+ have_seq_nums = set(have_acl['rules'].keys())
+ want_seq_nums = set(want_acl['rules'].keys())
+
+ if state in ('replaced', 'overridden'):
+ # Delete non-modified rules
+ for seq_num in have_seq_nums.difference(want_seq_nums):
+ rule_del_commands.append({'sequence_num': seq_num})
+ del_requests.append(self.get_delete_l2_acl_rule_request(acl_name, seq_num))
+
+ for seq_num in want_seq_nums.intersection(have_seq_nums):
+ # Replace existing rules
+ if have_acl['rules'][seq_num] != want_acl['rules'][seq_num]:
+ if state == 'merged':
+ self._module.fail_json(
+ msg="Cannot update existing sequence {0} of L2 ACL {1} with state merged."
+ " Please use state replaced or overridden.".format(seq_num, acl_name)
+ )
+
+ rule_del_commands.append({'sequence_num': seq_num})
+ del_requests.append(self.get_delete_l2_acl_rule_request(acl_name, seq_num))
+
+ rule_add_commands.append(want_acl['rules'][seq_num])
+ add_requests.append(self.get_create_l2_acl_rule_request(acl_name, seq_num, want_acl['rules'][seq_num]))
+
+ # Add new rules
+ for seq_num in want_seq_nums.difference(have_seq_nums):
+ rule_add_commands.append(want_acl['rules'][seq_num])
+ add_requests.append(self.get_create_l2_acl_rule_request(acl_name, seq_num, want_acl['rules'][seq_num]))
+
+ if rule_del_commands:
+ acl_del_command['rules'] = rule_del_commands
+ if rule_add_commands:
+ acl_add_command['rules'] = rule_add_commands
+
+ if acl_del_command.get('rules') or acl_del_command.get('remark'):
+ del_commands.append(acl_del_command)
+ if acl_add_command.get('rules') or acl_add_command.get('remark'):
+ add_commands.append(acl_add_command)
+
+ # Add new ACLs
+ for acl_name in want_acl_names.difference(have_acl_names):
+ acl_add_command = {'name': acl_name}
+ add_requests.append(self.get_create_l2_acl_request(acl_name))
+
+ want_acl = want_dict[acl_name]
+ if want_acl['remark']:
+ acl_add_command['remark'] = want_acl['remark']
+ add_requests.append(self.get_create_l2_acl_remark_request(acl_name, want_acl['remark']))
+
+ # Add new rules
+ want_seq_nums = set(want_acl['rules'].keys())
+ if want_seq_nums:
+ acl_add_command['rules'] = []
+ for seq_num in want_seq_nums:
+ acl_add_command['rules'].append(want_acl['rules'][seq_num])
+ add_requests.append(self.get_create_l2_acl_rule_request(acl_name, seq_num, want_acl['rules'][seq_num]))
+
+ add_commands.append(acl_add_command)
+
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+ requests = del_requests
+
+ if add_commands:
+ commands.extend(update_states(add_commands, state))
+ requests.extend(add_requests)
+
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ requests = []
+
+ if not want:
+ for acl in have:
+ commands.append({'name': acl['name']})
+ requests.append(self.get_delete_l2_acl_request(acl['name']))
+ else:
+ have_dict = self._convert_config_list_to_dict(have)
+ want_dict = self._convert_config_list_to_dict(want)
+ have_acl_names = set(have_dict.keys())
+ want_acl_names = set(want_dict.keys())
+
+ # Delete existing ACLs
+ for acl_name in want_acl_names.intersection(have_acl_names):
+ have_acl = have_dict[acl_name]
+ want_acl = want_dict[acl_name]
+
+ # Delete entire ACL if only the name is specified
+ if not want_acl['remark'] and not want_acl['rules']:
+ commands.append({'name': acl_name})
+ requests.append(self.get_delete_l2_acl_request(acl_name))
+ continue
+
+ acl_del_command = {'name': acl_name}
+ rule_del_commands = []
+ have_seq_nums = set(have_acl['rules'].keys())
+ want_seq_nums = set(want_acl['rules'].keys())
+
+ if want_acl['remark'] and want_acl['remark'] == have_acl['remark']:
+ acl_del_command['remark'] = want_acl['remark']
+ requests.append(self.get_delete_l2_acl_remark_request(acl_name))
+
+ # Delete existing rules
+ # When state is deleted, options other than sequence_num are not considered
+ for seq_num in want_seq_nums.intersection(have_seq_nums):
+ rule_del_commands.append({'sequence_num': seq_num})
+ requests.append(self.get_delete_l2_acl_rule_request(acl_name, seq_num))
+
+ if rule_del_commands:
+ acl_del_command['rules'] = rule_del_commands
+
+ if acl_del_command.get('rules') or acl_del_command.get('remark'):
+ commands.append(acl_del_command)
+
+ commands = update_states(commands, "deleted")
+ return commands, requests
+
+ def get_create_l2_acl_request(self, acl_name):
+ """Get request to create L2 ACL with specified name"""
+ url = self.acl_path
+ payload = {
+ 'acl-set': [{
+ 'name': acl_name,
+ 'type': L2_ACL_TYPE,
+ 'config': {
+ 'name': acl_name,
+ 'type': L2_ACL_TYPE
+ }
+ }]
+ }
+
+ return {'path': url, 'method': PATCH, 'data': payload}
+
+ def get_create_l2_acl_remark_request(self, acl_name, remark):
+ """Get request to add given remark to the specified L2 ACL"""
+ url = self.l2_acl_remark_path.format(acl_name=acl_name)
+ payload = {'description': remark}
+ return {'path': url, 'method': PATCH, 'data': payload}
+
+ def get_create_l2_acl_rule_request(self, acl_name, seq_num, rule):
+ """Get request to create a rule with given sequence number
+ and configuration in the specified L2 ACL
+ """
+ url = self.l2_acl_rule_path.format(acl_name=acl_name)
+ payload = {
+ 'openconfig-acl:acl-entry': [{
+ 'sequence-id': seq_num,
+ 'config': {
+ 'sequence-id': seq_num
+ },
+ 'l2': {
+ 'config': {}
+ },
+ 'actions': {
+ 'config': {
+ 'forwarding-action': action_value_to_payload_map[rule['action']]
+ }
+ }
+ }]
+ }
+ rule_l2_config = payload['openconfig-acl:acl-entry'][0]['l2']['config']
+
+ if rule['source'].get('host'):
+ rule_l2_config['source-mac'] = rule['source']['host']
+ elif rule['source'].get('address'):
+ rule_l2_config['source-mac'] = rule['source']['address']
+ rule_l2_config['source-mac-mask'] = rule['source']['address_mask']
+
+ if rule['destination'].get('host'):
+ rule_l2_config['destination-mac'] = rule['destination']['host']
+ elif rule['destination'].get('address'):
+ rule_l2_config['destination-mac'] = rule['destination']['address']
+ rule_l2_config['destination-mac-mask'] = rule['destination']['address_mask']
+
+ if rule.get('ethertype'):
+ if rule['ethertype'].get('value'):
+ rule_l2_config['ethertype'] = ethertype_value_to_payload_map.get(rule['ethertype']['value'], int(rule['ethertype']['value'], 16))
+ else:
+ rule_l2_config['ethertype'] = ethertype_protocol_to_payload_map[next(iter(rule['ethertype']))]
+
+ if rule.get('vlan_id') is not None:
+ rule_l2_config['vlanid'] = rule['vlan_id']
+
+ if rule.get('vlan_tag_format') and rule['vlan_tag_format'].get('multi_tagged'):
+ rule_l2_config['vlan-tag-format'] = 'openconfig-acl-ext:MULTI_TAGGED'
+
+ if rule.get('dei') is not None:
+ rule_l2_config['dei'] = rule['dei']
+
+ if rule.get('pcp'):
+ if rule['pcp'].get('traffic_type'):
+ rule_l2_config['pcp'] = pcp_traffic_to_value_map[rule['pcp']['traffic_type']]
+ else:
+ rule_l2_config['pcp'] = rule['pcp']['value']
+ rule_l2_config['pcp-mask'] = rule['pcp']['mask']
+
+ if rule.get('remark'):
+ payload['openconfig-acl:acl-entry'][0]['config']['description'] = rule['remark']
+
+ return {'path': url, 'method': POST, 'data': payload}
+
+ def get_delete_l2_acl_request(self, acl_name):
+ """Get request to delete L2 ACL with specified name"""
+ url = self.l2_acl_path.format(acl_name=acl_name)
+ return {'path': url, 'method': DELETE}
+
+ def get_delete_l2_acl_remark_request(self, acl_name):
+ """Get request to delete remark of the specified L2 ACL"""
+ url = self.l2_acl_remark_path.format(acl_name=acl_name)
+ return {'path': url, 'method': DELETE}
+
+ def get_delete_l2_acl_rule_request(self, acl_name, seq_num):
+ """Get request to delete the rule with given sequence number
+ in the specified L2 ACL
+ """
+ url = self.l2_acl_rule_path.format(acl_name=acl_name)
+ url += '/acl-entry={0}'.format(seq_num)
+ return {'path': url, 'method': DELETE}
+
+ def validate_and_normalize_config(self, config_list):
+ """Validate and normalize the given config"""
+ # Remove empties and validate the config with argument spec
+ updated_config_list = [remove_empties(config) for config in config_list]
+ validate_config(self._module.argument_spec, {'config': updated_config_list})
+
+ state = self._module.params['state']
+ # When state is deleted, options other than sequence_num are not considered
+ if state == 'deleted':
+ return updated_config_list
+
+ for acl in updated_config_list:
+ if not acl.get('rules'):
+ continue
+
+ for rule in acl['rules']:
+ self._check_required(['action', 'source', 'destination'], rule, ['config', 'rules'])
+ for endpoint in ('source', 'destination'):
+ if rule[endpoint].get('any') is False:
+ self._invalid_rule('True is the only valid value for {0} -> any'.format(endpoint), acl['name'], rule['sequence_num'])
+ elif rule[endpoint].get('host'):
+ rule[endpoint]['host'] = rule[endpoint]['host'].lower()
+ elif rule[endpoint].get('address'):
+ rule[endpoint]['address'] = rule[endpoint]['address'].lower()
+ rule[endpoint]['address_mask'] = rule[endpoint]['address_mask'].lower()
+
+ self._normalize_ethertype(rule)
+ self._normalize_pcp(rule)
+ self._normalize_vlan_tag_format(rule)
+
+ return updated_config_list
+
+ def _invalid_rule(self, err_msg, acl_name, seq_num):
+ self._module.fail_json(msg='L2 ACL {0}, sequence number {1}: {2}'.format(acl_name, seq_num, err_msg))
+
+ def _check_required(self, required_parameters, parameters, options_context=None):
+ if required_parameters:
+ spec = {}
+ for parameter in required_parameters:
+ spec[parameter] = {'required': True}
+
+ try:
+ check_required_arguments(spec, parameters, options_context)
+ except TypeError as exc:
+ self._module.fail_json(msg=str(exc))
+
+ @staticmethod
+ def _normalize_ethertype(rule):
+ ethertype = rule.get('ethertype')
+ if ethertype:
+ if ethertype.get('value'):
+ value = ethertype.pop('value')
+ if value.startswith('0x'):
+ value = ETHERTYPE_FORMAT.format(int(value, 16))
+ else:
+ # If the hexadecimal number is not enclosed within
+ # quotes, it will be passed as a string after being
+ # converted to decimal.
+ value = ETHERTYPE_FORMAT.format(int(value, 10))
+
+ if value in ethertype_value_to_protocol_map:
+ ethertype[ethertype_value_to_protocol_map[value]] = True
+ else:
+ ethertype['value'] = value
+ else:
+ # Remove ethertype option if its value is False
+ if not next(iter(ethertype.values())):
+ del rule['ethertype']
+
+ @staticmethod
+ def _normalize_pcp(rule):
+ pcp = rule.get('pcp')
+ if pcp and pcp.get('value') is not None and pcp.get('mask') is None:
+ pcp['traffic_type'] = pcp_value_to_traffic_map[pcp['value']]
+ del pcp['value']
+
+ @staticmethod
+ def _normalize_vlan_tag_format(rule):
+ vlan_tag_format = rule.get('vlan_tag_format')
+ # Remove vlan_tag_format option if the value is False
+ if vlan_tag_format and not vlan_tag_format.get('multi_tagged'):
+ del rule['vlan_tag_format']
+
+ @staticmethod
+ def _convert_config_list_to_dict(config_list):
+ config_dict = {}
+ for config in config_list:
+ acl_name = config['name']
+ config_dict[acl_name] = {}
+ config_dict[acl_name]['remark'] = config.get('remark')
+ config_dict[acl_name]['rules'] = {}
+ if config.get('rules'):
+ for rule in config['rules']:
+ config_dict[acl_name]['rules'][rule['sequence_num']] = rule
+
+ return config_dict
+
+ def sort_config(self, configs):
+ # natsort provides better result.
+ # The use of natsort causes sanity error due to it is not available in
+ # python version currently used.
+ # new_config = natsorted(new_config, key=lambda x: x['name'])
+ # For time-being, use simple "sort"
+ configs.sort(key=lambda x: x['name'])
+
+ for conf in configs:
+ if conf.get('rules', []):
+ conf['rules'].sort(key=lambda x: x['sequence_num'])
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_interfaces/l2_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_interfaces/l2_interfaces.py
index fccba7707..c47f06940 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_interfaces/l2_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_interfaces/l2_interfaces.py
@@ -13,17 +13,19 @@ created
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import json
+import traceback
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
get_diff,
+ get_ranges_in_list,
update_states,
normalize_interface_name
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_empties,
to_list
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import (
@@ -33,9 +35,14 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
to_request,
edit_config
)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG,
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ get_new_config,
+ get_formatted_config_diff
+)
from ansible.module_utils._text import to_native
from ansible.module_utils.connection import ConnectionError
-import traceback
LIB_IMP_ERR = None
ERR_MSG = None
@@ -47,6 +54,7 @@ except Exception as e:
ERR_MSG = to_native(e)
LIB_IMP_ERR = traceback.format_exc()
+DELETE = 'delete'
PATCH = 'patch'
intf_key = 'openconfig-if-ethernet:ethernet'
port_chnl_key = 'openconfig-if-aggregate:aggregation'
@@ -54,6 +62,10 @@ port_chnl_key = 'openconfig-if-aggregate:aggregation'
TEST_KEYS = [
{'allowed_vlans': {'vlan': ''}},
]
+TEST_KEYS_formatted_diff = [
+ {'config': {'name': '', '__delete_op': __DELETE_CONFIG}},
+ {'allowed_vlans': {'vlan': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+]
class L2_interfaces(ConfigBase):
@@ -112,6 +124,19 @@ class L2_interfaces(ConfigBase):
if result['changed']:
result['after'] = changed_l2_interfaces_facts
+ new_config = changed_l2_interfaces_facts
+ old_config = existing_l2_interfaces_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_l2_interfaces_facts,
+ TEST_KEYS_formatted_diff)
+ result['after(generated)'] = new_config
+ if self._module._diff:
+ self.sort_config(new_config)
+ self.sort_config(old_config)
+ result['diff'] = get_formatted_config_diff(old_config,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
@@ -123,7 +148,15 @@ class L2_interfaces(ConfigBase):
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
+ state = self._module.params['state']
want = self._module.params['config']
+ if want:
+ # In state deleted, specific empty parameters are supported
+ if state != 'deleted':
+ want = [remove_empties(conf) for conf in want]
+ else:
+ want = []
+
normalize_interface_name(want, self._module)
have = existing_l2_interfaces_facts
@@ -147,45 +180,40 @@ class L2_interfaces(ConfigBase):
"""
state = self._module.params['state']
- diff = get_diff(want, have, TEST_KEYS)
-
if state == 'overridden':
- commands, requests = self._state_overridden(want, have, diff)
+ commands, requests = self._state_overridden(want, have)
elif state == 'deleted':
- commands, requests = self._state_deleted(want, have, diff)
+ commands, requests = self._state_deleted(want, have)
elif state == 'merged':
- commands, requests = self._state_merged(want, have, diff)
+ commands, requests = self._state_merged(want, have)
elif state == 'replaced':
- commands, requests = self._state_replaced(want, have, diff)
+ commands, requests = self._state_replaced(want, have)
return commands, requests
- def _state_replaced(self, want, have, diff):
+ def _state_replaced(self, want, have):
""" The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
-
+ commands = []
requests = []
- commands = diff
- if commands:
- requests_del = self.get_delete_all_switchport_requests(commands)
- if requests_del:
- requests.extend(requests_del)
-
- requests_rep = self.get_create_l2_interface_request(commands)
- if len(requests_del) or len(requests_rep):
- requests.extend(requests_rep)
- commands = update_states(commands, "replaced")
- else:
- commands = []
+ del_commands, del_requests = self.get_delete_commands_requests_for_replaced_overridden(want, have, 'replaced')
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+ requests = del_requests
+
+ add_commands, add_requests = self.get_merge_commands_requests(want, have)
+ if add_commands:
+ commands.extend(update_states(add_commands, 'replaced'))
+ requests.extend(add_requests)
return commands, requests
- def _state_overridden(self, want, have, diff):
+ def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
@@ -195,23 +223,19 @@ class L2_interfaces(ConfigBase):
commands = []
requests = []
- commands_del = get_diff(have, want, TEST_KEYS)
- requests_del = self.get_delete_all_switchport_requests(commands_del)
- if len(requests_del):
- requests.extend(requests_del)
- commands_del = update_states(commands_del, "deleted")
- commands.extend(commands_del)
+ del_commands, del_requests = self.get_delete_commands_requests_for_replaced_overridden(want, have, 'overridden')
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+ requests = del_requests
- commands_over = diff
- requests_over = self.get_create_l2_interface_request(commands_over)
- if requests_over:
- requests.extend(requests_over)
- commands_over = update_states(commands_over, "overridden")
- commands.extend(commands_over)
+ add_commands, add_requests = self.get_merge_commands_requests(want, have)
+ if add_commands:
+ commands.extend(update_states(add_commands, 'overridden'))
+ requests.extend(add_requests)
return commands, requests
- def _state_merged(self, want, have, diff):
+ def _state_merged(self, want, have):
""" The command generator when state is merged
:rtype: A list
@@ -220,77 +244,235 @@ class L2_interfaces(ConfigBase):
Requests necessary to merge to the current configuration
at position-1
"""
- commands = diff
- requests = self.get_create_l2_interface_request(commands)
- if commands and len(requests):
- commands = update_states(commands, "merged")
+ commands, requests = self.get_merge_commands_requests(want, have)
+ if commands:
+ commands = update_states(commands, 'merged')
+
return commands, requests
- def _state_deleted(self, want, have, diff):
+ def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
+ commands, requests = self.get_delete_commands_requests_for_deleted(want, have)
+ if commands:
+ commands = update_states(commands, 'deleted')
- # if want is none, then delete all the vlan links
- if not want or len(have) == 0:
- commands = have
- requests = self.get_delete_all_switchport_requests(commands)
+ return commands, requests
+
+ def get_merge_commands_requests(self, want, have):
+ """Returns the commands and requests necessary to merge the provided
+ configurations into the current configuration
+ """
+ commands = []
+ requests = []
+ if not want:
+ return commands, requests
+
+ if have:
+ diff = get_diff(want, have, TEST_KEYS)
else:
- commands = want
- requests = self.get_delete_specifig_switchport_requests(want, have)
- if len(requests) == 0:
- commands = []
+ diff = want
- if commands:
- commands = update_states(commands, "deleted")
+ for cmd in diff:
+ name = cmd['name']
+ if name == 'eth0':
+ continue
+
+ if cmd.get('trunk') and cmd['trunk'].get('allowed_vlans'):
+ match = next((cnf for cnf in have if cnf['name'] == name), None)
+ if match:
+ cmd['trunk']['allowed_vlans'] = self.get_trunk_allowed_vlans_diff(cmd, match)
+ if not cmd['trunk']['allowed_vlans']:
+ cmd.pop('trunk')
+
+ if cmd.get('access') or cmd.get('trunk'):
+ commands.append(cmd)
+ requests = self.get_create_l2_interface_requests(commands)
return commands, requests
- def get_trunk_delete_switchport_request(self, config, match_config):
- method = "DELETE"
- name = config['name']
+ def get_delete_commands_requests_for_deleted(self, want, have):
+ """Returns the commands and requests necessary to remove the current
+ configuration of the provided objects when state is deleted
+ """
+ commands = []
requests = []
- match_trunk = match_config.get('trunk')
- if match_trunk:
- conf_allowed_vlans = config['trunk'].get('allowed_vlans', [])
- if conf_allowed_vlans:
- for each_allowed_vlan in conf_allowed_vlans:
- if each_allowed_vlan in match_trunk.get('allowed_vlans'):
- vlan_id = each_allowed_vlan['vlan']
- key = intf_key
- if name.startswith('PortChannel'):
- key = port_chnl_key
- url = "data/openconfig-interfaces:interfaces/interface={0}/{1}/".format(name, key)
- url += "openconfig-vlan:switched-vlan/config/trunk-vlans={0}".format(vlan_id)
- request = {"path": url, "method": method}
- requests.append(request)
- return requests
+ if not have:
+ return commands, requests
+
+ if not want:
+ # Delete all L2 interface config
+ commands = [remove_empties(conf) for conf in have]
+ requests = self.get_delete_all_switchport_requests(commands)
+ return commands, requests
+
+ for conf in want:
+ name = conf['name']
+ matched = next((cnf for cnf in have if cnf['name'] == name), None)
+ if matched:
+ # If both access and trunk are not mentioned, delete all config
+ # in that interface
+ if not conf.get('access') and not conf.get('trunk'):
+ command = {'name': name}
+ if matched.get('access'):
+ command['access'] = matched['access']
+ if matched.get('trunk'):
+ command['trunk'] = matched['trunk']
+
+ commands.append(command)
+ requests.extend(self.get_delete_all_switchport_requests([command]))
+ else:
+ command = {}
+ if conf.get('access'):
+ access_match = matched.get('access')
+ if conf['access'].get('vlan'):
+ if access_match and access_match.get('vlan') == conf['access']['vlan']:
+ command['access'] = {'vlan': conf['access']['vlan']}
+ requests.append(self.get_access_delete_switchport_request(name))
+ else:
+ # If access -> vlan is mentioned without value,
+ # delete existing access vlan config
+ if access_match and access_match.get('vlan'):
+ command['access'] = {'vlan': access_match['vlan']}
+ requests.append(self.get_access_delete_switchport_request(name))
+
+ if conf.get('trunk'):
+ if conf['trunk'].get('allowed_vlans'):
+ trunk_vlans_to_delete = self.get_trunk_allowed_vlans_common(conf, matched)
+ if trunk_vlans_to_delete:
+ command['trunk'] = {'allowed_vlans': trunk_vlans_to_delete}
+ requests.append(self.get_trunk_allowed_vlans_delete_switchport_request(name, command['trunk']['allowed_vlans']))
+ else:
+ # If trunk -> allowed_vlans is mentioned without
+ # value, delete existing trunk allowed vlans config
+ trunk_match = matched.get('trunk')
+ if trunk_match and trunk_match.get('allowed_vlans'):
+ command['trunk'] = {'allowed_vlans': trunk_match['allowed_vlans'].copy()}
+ requests.append(self.get_trunk_allowed_vlans_delete_switchport_request(name, command['trunk']['allowed_vlans']))
+
+ if command:
+ command['name'] = name
+ commands.append(command)
+
+ return commands, requests
+
+ def get_delete_commands_requests_for_replaced_overridden(self, want, have, state):
+ """Returns the commands and requests necessary to remove applicable
+ current configurations when state is replaced or overridden
+ """
+ commands = []
+ requests = []
+ if not have:
+ return commands, requests
+
+ have_interfaces = self.get_interface_names(have)
+ want_interfaces = self.get_interface_names(want)
+ interfaces_to_replace = have_interfaces.intersection(want_interfaces)
+ if state == 'overridden':
+ interfaces_to_delete = have_interfaces.difference(want_interfaces)
+ else:
+ interfaces_to_delete = []
+
+ if want:
+ del_diff = get_diff(have, want, TEST_KEYS)
+ else:
+ del_diff = have
+
+ for conf in del_diff:
+ name = conf['name']
+
+ # Delete all config in interfaces not specified in overridden
+ if name in interfaces_to_delete:
+ command = {'name': name}
+ if conf.get('access'):
+ command['access'] = conf['access']
+ if conf.get('trunk'):
+ command['trunk'] = conf['trunk']
+
+ commands.append(command)
+ requests.extend(self.get_delete_all_switchport_requests([command]))
+
+ # Delete config in interfaces that are replaced/overridden
+ elif name in interfaces_to_replace:
+ command = {}
+
+ if conf.get('access') and conf['access'].get('vlan'):
+ command['access'] = {'vlan': conf['access']['vlan']}
+ requests.append(self.get_access_delete_switchport_request(name))
+
+ if conf.get('trunk') and conf['trunk'].get('allowed_vlans'):
+ matched = next((cnf for cnf in want if cnf['name'] == name), None)
+ if matched:
+ trunk_vlans_to_delete = self.get_trunk_allowed_vlans_diff(conf, matched)
+ if trunk_vlans_to_delete:
+ command['trunk'] = {'allowed_vlans': trunk_vlans_to_delete}
+ requests.append(self.get_trunk_allowed_vlans_delete_switchport_request(name, command['trunk']['allowed_vlans']))
+
+ if command:
+ command['name'] = name
+ commands.append(command)
+
+ return commands, requests
+
+ def get_trunk_allowed_vlans_delete_switchport_request(self, intf_name, allowed_vlans):
+ """Returns the request as a dict to delete the trunk vlan ranges
+ specified in allowed_vlans for the given interface
+ """
+ method = DELETE
+ vlan_id_list = ""
+ for each_allowed_vlan in allowed_vlans:
+ vlan_id = each_allowed_vlan['vlan']
+
+ if '-' in vlan_id:
+ vlan_id_fmt = vlan_id.replace('-', '..')
+ else:
+ vlan_id_fmt = vlan_id
+
+ if vlan_id_list:
+ vlan_id_list += ",{0}".format(vlan_id_fmt)
+ else:
+ vlan_id_list = vlan_id_fmt
+
+ key = intf_key
+ if intf_name.startswith('PortChannel'):
+ key = port_chnl_key
+
+ url = "data/openconfig-interfaces:interfaces/interface={0}/{1}/".format(intf_name, key)
+ url += "openconfig-vlan:switched-vlan/config/"
+ url += "trunk-vlans=" + vlan_id_list.replace(',', '%2C')
+
+ request = {"path": url, "method": method}
+ return request
+
+ def get_access_delete_switchport_request(self, intf_name):
+ """Returns the request as a dict to delete the access vlan
+ configuration for the given interface
+ """
+ method = DELETE
+ key = intf_key
+ if intf_name.startswith('PortChannel'):
+ key = port_chnl_key
+ url = "data/openconfig-interfaces:interfaces/interface={}/{}/openconfig-vlan:switched-vlan/config/access-vlan"
+ request = {"path": url.format(intf_name, key), "method": method}
- def get_access_delete_switchport_request(self, config, match_config):
- method = "DELETE"
- request = None
- name = config['name']
- match_access = match_config.get('access')
- if match_access and match_access.get('vlan') == config['access'].get('vlan'):
- key = intf_key
- if name.startswith('PortChannel'):
- key = port_chnl_key
- url = "data/openconfig-interfaces:interfaces/interface={}/{}/openconfig-vlan:switched-vlan/config/access-vlan"
- request = {"path": url.format(name, key), "method": method}
return request
def get_delete_all_switchport_requests(self, configs):
+ """Returns a list of requests to delete all switchport
+ configuration for all interfaces specified in the config list
+ """
requests = []
if not configs:
return requests
# Create URL and payload
url = "data/openconfig-interfaces:interfaces/interface={}/{}/openconfig-vlan:switched-vlan/config"
- method = "DELETE"
+ method = DELETE
for intf in configs:
- name = intf.get("name")
+ name = intf['name']
key = intf_key
if name.startswith('PortChannel'):
key = port_chnl_key
@@ -301,78 +483,19 @@ class L2_interfaces(ConfigBase):
return requests
- def get_delete_specifig_switchport_requests(self, configs, have):
+ def get_create_l2_interface_requests(self, configs):
+ """Returns a list of requests to add the switchport
+ configurations specified in the config list
+ """
requests = []
if not configs:
return requests
- for conf in configs:
- name = conf['name']
-
- matched = next((cnf for cnf in have if cnf['name'] == name), None)
- if matched:
- keys = conf.keys()
-
- # if both access and trunk not mention in delete
- if not ('access' in keys) and not ('trunk' in keys):
- requests.extend(self.get_delete_all_switchport_requests([conf]))
- else:
- # if access or trnuk is mentioned with value
- if conf.get('access') or conf.get('trunk'):
- # if access is mentioned with value
- if conf.get('access'):
- vlan = conf.get('access').get('vlan')
- if vlan:
- request = self.get_access_delete_switchport_request(conf, matched)
- if request:
- requests.append(request)
- else:
- if matched.get('access') and matched.get('access').get('vlan'):
- conf['access']['vlan'] = matched.get('access').get('vlan')
- request = self.get_access_delete_switchport_request(conf, matched)
- if request:
- requests.append(request)
-
- # if trunk is mentioned with value
- if conf.get('trunk'):
- allowed_vlans = conf['trunk'].get('allowed_vlans')
- if allowed_vlans:
- requests.extend(self.get_trunk_delete_switchport_request(conf, matched))
- # allowed vlans mentinoed without value
- else:
- if matched.get('trunk') and matched.get('trunk').get('allowed_vlans'):
- conf['trunk']['allowed_vlans'] = matched.get('trunk') and matched.get('trunk').get('allowed_vlans').copy()
- requests.extend(self.get_trunk_delete_switchport_request(conf, matched))
- # check for access or trunk is mentioned without value
- else:
- # access mentioned wothout value
- if ('access' in keys) and conf.get('access', None) is None:
- # get the existing values and delete it
- if matched.get('access'):
- conf['access'] = matched.get('access').copy()
- request = self.get_access_delete_switchport_request(conf, matched)
- if request:
- requests.append(request)
- # trunk mentioned wothout value
- if ('trunk' in keys) and conf.get('trunk', None) is None:
- # get the existing values and delete it
- if matched.get('trunk'):
- conf['trunk'] = matched.get('trunk').copy()
- requests.extend(self.get_trunk_delete_switchport_request(conf, matched))
-
- return requests
-
- def get_create_l2_interface_request(self, configs):
- requests = []
- if not configs:
- return requests
# Create URL and payload
url = "data/openconfig-interfaces:interfaces/interface={}/{}/openconfig-vlan:switched-vlan/config"
- method = "PATCH"
+ method = PATCH
for conf in configs:
- name = conf.get('name')
- if name == "eth0":
- continue
+ name = conf['name']
key = intf_key
if name.startswith('PortChannel'):
key = port_chnl_key
@@ -382,33 +505,124 @@ class L2_interfaces(ConfigBase):
"data": payload
}
requests.append(request)
+
return requests
def build_create_payload(self, conf):
- payload_url = '{"openconfig-vlan:config":{ '
- access_payload = ''
- trunk_payload = ''
- if conf.get('access'):
- access_vlan_id = conf['access']['vlan']
- access_payload = '"access-vlan": {0}'.format(access_vlan_id)
- if conf.get('trunk'):
- trunk_payload = '"trunk-vlans": ['
- cnt = 0
+ """Returns the payload to add the switchport configurations
+ specified in the interface config
+ """
+ payload = {'openconfig-vlan:config': {}}
+ trunk_payload = []
+
+ if conf.get('access') and conf['access'].get('vlan'):
+ payload['openconfig-vlan:config']['access-vlan'] = int(conf['access']['vlan'])
+
+ if conf.get('trunk') and conf['trunk'].get('allowed_vlans'):
for each_allowed_vlan in conf['trunk']['allowed_vlans']:
- if cnt > 0:
- trunk_payload += ','
- trunk_payload += str(each_allowed_vlan['vlan'])
- cnt = cnt + 1
- trunk_payload += ']'
-
- if access_payload != '':
- payload_url += access_payload
- if trunk_payload != '':
- if access_payload != '':
- payload_url += ','
- payload_url += trunk_payload
-
- payload_url += '}}'
-
- ret_payload = json.loads(payload_url)
- return ret_payload
+ vlan_val = each_allowed_vlan['vlan']
+ if '-' in vlan_val:
+ trunk_payload.append('{0}'.format(vlan_val.replace('-', '..')))
+ else:
+ trunk_payload.append(int(vlan_val))
+
+ if trunk_payload:
+ payload['openconfig-vlan:config']['trunk-vlans'] = trunk_payload
+
+ return payload
+
+ def get_trunk_allowed_vlans_common(self, config, match):
+ """Returns the allowed vlan ranges that are common in the
+ interface configurations specified by 'config' and 'match' in
+ allowed_vlans spec format
+ """
+ trunk_vlans = []
+ match_trunk_vlans = []
+ if config.get('trunk') and config['trunk'].get('allowed_vlans'):
+ trunk_vlans = config['trunk']['allowed_vlans']
+
+ if not trunk_vlans:
+ return []
+
+ if match.get('trunk') and match['trunk'].get('allowed_vlans'):
+ match_trunk_vlans = match['trunk']['allowed_vlans']
+
+ if not match_trunk_vlans:
+ return []
+
+ trunk_vlans = self.get_vlan_id_list(trunk_vlans)
+ match_trunk_vlans = self.get_vlan_id_list(match_trunk_vlans)
+ return self.get_allowed_vlan_range_list(list(set(trunk_vlans).intersection(set(match_trunk_vlans))))
+
+ def get_trunk_allowed_vlans_diff(self, config, match):
+ """Returns the allowed vlan ranges present only in 'config'
+ and and not in 'match' in allowed_vlans spec format
+ """
+ trunk_vlans = []
+ match_trunk_vlans = []
+ if config.get('trunk') and config['trunk'].get('allowed_vlans'):
+ trunk_vlans = config['trunk']['allowed_vlans']
+
+ if not trunk_vlans:
+ return []
+
+ if match.get('trunk') and match['trunk'].get('allowed_vlans'):
+ match_trunk_vlans = match['trunk']['allowed_vlans']
+
+ if not match_trunk_vlans:
+ return trunk_vlans
+
+ trunk_vlans = self.get_vlan_id_list(trunk_vlans)
+ match_trunk_vlans = self.get_vlan_id_list(match_trunk_vlans)
+ return self.get_allowed_vlan_range_list(list(set(trunk_vlans) - set(match_trunk_vlans)))
+
+ @staticmethod
+ def get_vlan_id_list(allowed_vlan_range_list):
+ """Returns a list of all VLAN IDs specified in allowed_vlans list"""
+ vlan_id_list = []
+ if allowed_vlan_range_list:
+ for vlan_range in allowed_vlan_range_list:
+ vlan_val = vlan_range['vlan']
+ if '-' in vlan_val:
+ start, end = vlan_val.split('-')
+ vlan_id_list.extend(range(int(start), int(end) + 1))
+ else:
+ # Single VLAN ID
+ vlan_id_list.append(int(vlan_val))
+
+ return vlan_id_list
+
+ @staticmethod
+ def get_allowed_vlan_range_list(vlan_id_list):
+ """Returns the allowed_vlans list for given list of VLAN IDs"""
+ allowed_vlan_range_list = []
+
+ if vlan_id_list:
+ vlan_id_list.sort()
+ for vlan_range in get_ranges_in_list(vlan_id_list):
+ allowed_vlan_range_list.append({'vlan': '-'.join(map(str, (vlan_range[0], vlan_range[-1])[:len(vlan_range)]))})
+
+ return allowed_vlan_range_list
+
+ @staticmethod
+ def get_interface_names(configs):
+ """Returns a set of interface names available in the given
+ configs list
+ """
+ interface_names = set()
+ for conf in configs:
+ interface_names.add(conf['name'])
+
+ return interface_names
+
+ def sort_config(self, configs):
+ # natsort provides better result.
+ # The use of natsort causes sanity error due to it is not available in
+ # python version currently used.
+ # new_config = natsorted(new_config, key=lambda x: x['name'])
+ # For time-being, use simple "sort"
+ configs.sort(key=lambda x: x['name'])
+
+ for conf in configs:
+ if conf.get('trunk', {}) and conf['trunk'].get('allowed_vlans', []):
+ conf['trunk']['allowed_vlans'].sort(key=lambda x: x['vlan'])
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_acls/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_acls/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_acls/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_acls/l3_acls.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_acls/l3_acls.py
new file mode 100644
index 000000000..26fbb7fdb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_acls/l3_acls.py
@@ -0,0 +1,763 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_l3_acls class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ast import literal_eval
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.common.validation import check_required_arguments
+from ansible.module_utils.connection import ConnectionError
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+ remove_empties,
+ validate_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ update_states
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ get_new_config,
+ get_formatted_config_diff
+)
+
+DELETE = 'delete'
+PATCH = 'patch'
+POST = 'post'
+
+TEST_KEYS_formatted_diff = [
+ {'config': {'address_family': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+ {'acls': {'name': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+ {'rules': {'sequence_num': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+]
+
+L4_PORT_START = 0
+L4_PORT_END = 65535
+
+protocol_number_to_name_map = {
+ 1: 'icmp',
+ 6: 'tcp',
+ 17: 'udp',
+ 58: 'icmpv6'
+}
+dscp_value_to_name_map = {
+ 0: 'default',
+ 8: 'cs1',
+ 16: 'cs2',
+ 24: 'cs3',
+ 32: 'cs4',
+ 40: 'cs5',
+ 48: 'cs6',
+ 56: 'cs7',
+ 10: 'af11',
+ 12: 'af12',
+ 14: 'af13',
+ 18: 'af21',
+ 20: 'af22',
+ 22: 'af23',
+ 26: 'af31',
+ 28: 'af32',
+ 30: 'af33',
+ 34: 'af41',
+ 36: 'af42',
+ 38: 'af43',
+ 46: 'ef',
+ 44: 'voice_admit'
+}
+
+# Spec value to payload value mappings
+acl_type_to_payload_map = {
+ 'ipv4': 'ACL_IPV4',
+ 'ipv6': 'ACL_IPV6'
+}
+acl_type_to_host_mask_map = {
+ 'ipv4': '/32',
+ 'ipv6': '/128'
+}
+action_value_to_payload_map = {
+ 'permit': 'ACCEPT',
+ 'discard': 'DISCARD',
+ 'do-not-nat': 'DO_NOT_NAT',
+ 'deny': 'DROP',
+ 'transit': 'TRANSIT'
+}
+protocol_name_to_payload_map = {
+ 'icmp': 'IP_ICMP',
+ 'icmpv6': 58,
+ 'tcp': 'IP_TCP',
+ 'udp': 'IP_UDP'
+}
+protocol_number_to_payload_map = {
+ 2: 'IP_IGMP',
+ 46: 'IP_RSVP',
+ 47: 'IP_GRE',
+ 51: 'IP_AUTH',
+ 103: 'IP_PIM',
+ 115: 'IP_L2TP'
+}
+dscp_name_to_value_map = {v: k for k, v in dscp_value_to_name_map.items()}
+
+
+class L3_acls(ConfigBase):
+ """
+ The sonic_l3_acls class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'l3_acls',
+ ]
+
+ acl_path = 'data/openconfig-acl:acl/acl-sets/acl-set'
+ l3_acl_path = 'data/openconfig-acl:acl/acl-sets/acl-set={acl_name},{acl_type}'
+ l3_acl_rule_path = 'data/openconfig-acl:acl/acl-sets/acl-set={acl_name},{acl_type}/acl-entries'
+ l3_acl_remark_path = 'data/openconfig-acl:acl/acl-sets/acl-set={acl_name},{acl_type}/config/description'
+
+ def __init__(self, module):
+ super(L3_acls, self).__init__(module)
+
+ def get_l3_acls_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ l3_acls_facts = facts['ansible_network_resources'].get('l3_acls')
+ if not l3_acls_facts:
+ return []
+ return l3_acls_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = []
+
+ existing_l3_acls_facts = self.get_l3_acls_facts()
+ commands, requests = self.set_config(existing_l3_acls_facts)
+ if commands:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._handle_failure_response(exc)
+
+ result['changed'] = True
+
+ changed_l3_acls_facts = self.get_l3_acls_facts()
+
+ result['before'] = existing_l3_acls_facts
+ if result['changed']:
+ result['after'] = changed_l3_acls_facts
+
+ result['commands'] = commands
+
+ new_config = changed_l3_acls_facts
+ old_config = existing_l3_acls_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_l3_acls_facts,
+ TEST_KEYS_formatted_diff)
+ self.post_process_generated_config(new_config)
+ result['after(generated)'] = new_config
+ if self._module._diff:
+ self.sort_config(new_config)
+ self.sort_config(old_config)
+ result['diff'] = get_formatted_config_diff(old_config,
+ new_config,
+ self._module._verbosity)
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_l3_acls_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ if want:
+ want = self.validate_and_normalize_config(want)
+ else:
+ want = []
+
+ have = existing_l3_acls_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params['state']
+ if state in ('merged', 'overridden', 'replaced'):
+ commands, requests = self._state_merged_overridden_replaced(want, have, state)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+
+ return commands, requests
+
+ def _handle_failure_response(self, connection_error):
+ log = None
+ try:
+ response = literal_eval(connection_error.args[0])
+ error_app_tag = response['ietf-restconf:errors']['error'][0].get('error-app-tag')
+ except Exception:
+ pass
+ else:
+ if error_app_tag == 'too-many-elements':
+ log = 'Exceeds maximum number of ACL / ACL Rules'
+ elif error_app_tag == 'update-not-allowed':
+ log = 'Creating ACLs with same name and different type not allowed'
+
+ if log:
+ response.update({u'log': log})
+ self._module.fail_json(msg=to_text(response), code=connection_error.code)
+ else:
+ self._module.fail_json(msg=str(connection_error), code=connection_error.code)
+
+ def _state_merged_overridden_replaced(self, want, have, state):
+ """ The command generator when state is merged/overridden/replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ add_commands = []
+ del_commands = []
+ commands = []
+
+ add_requests = []
+ del_requests = []
+ requests = []
+
+ have_dict = self._convert_config_list_to_dict(have)
+ want_dict = self._convert_config_list_to_dict(want)
+
+ for acl_type in ('ipv4', 'ipv6'):
+ acl_type_add_commands = []
+ acl_type_del_commands = []
+
+ have_acl_names = set(have_dict.get(acl_type, {}).keys())
+ want_acl_names = set(want_dict.get(acl_type, {}).keys())
+
+ if state == 'overridden':
+ # Delete non-modified ACLs
+ for acl_name in have_acl_names.difference(want_acl_names):
+ acl_type_del_commands.append({'name': acl_name})
+ del_requests.append(self.get_delete_l3_acl_request(acl_type, acl_name))
+
+ # Modify existing ACLs
+ for acl_name in want_acl_names.intersection(have_acl_names):
+ acl_add_command = {'name': acl_name}
+ acl_del_command = {'name': acl_name}
+ rule_add_commands = []
+ rule_del_commands = []
+
+ have_acl = have_dict[acl_type][acl_name]
+ want_acl = want_dict[acl_type][acl_name]
+ if not want_acl['remark']:
+ if have_acl['remark'] and state in ('replaced', 'overridden'):
+ acl_del_command['remark'] = have_acl['remark']
+ del_requests.append(self.get_delete_l3_acl_remark_request(acl_type, acl_name))
+ else:
+ if want_acl['remark'] != have_acl['remark']:
+ acl_add_command['remark'] = want_acl['remark']
+ add_requests.append(self.get_create_l3_acl_remark_request(acl_type, acl_name, want_acl['remark']))
+
+ have_seq_nums = set(have_acl['rules'].keys())
+ want_seq_nums = set(want_acl['rules'].keys())
+
+ if state in ('replaced', 'overridden'):
+ # Delete non-modified rules
+ for seq_num in have_seq_nums.difference(want_seq_nums):
+ rule_del_commands.append({'sequence_num': seq_num})
+ del_requests.append(self.get_delete_l3_acl_rule_request(acl_type, acl_name, seq_num))
+
+ for seq_num in want_seq_nums.intersection(have_seq_nums):
+ # Replace existing rules
+ if have_acl['rules'][seq_num] != want_acl['rules'][seq_num]:
+ if state == 'merged':
+ self._module.fail_json(
+ msg="Cannot update existing sequence {0} of {1} ACL {2} with state merged."
+ " Please use state replaced or overridden.".format(seq_num, acl_type, acl_name)
+ )
+
+ rule_del_commands.append({'sequence_num': seq_num})
+ del_requests.append(self.get_delete_l3_acl_rule_request(acl_type, acl_name, seq_num))
+
+ rule_add_commands.append(want_acl['rules'][seq_num])
+ add_requests.append(self.get_create_l3_acl_rule_request(acl_type, acl_name, seq_num, want_acl['rules'][seq_num]))
+
+ # Add new rules
+ for seq_num in want_seq_nums.difference(have_seq_nums):
+ rule_add_commands.append(want_acl['rules'][seq_num])
+ add_requests.append(self.get_create_l3_acl_rule_request(acl_type, acl_name, seq_num, want_acl['rules'][seq_num]))
+
+ if rule_del_commands:
+ acl_del_command['rules'] = rule_del_commands
+ if rule_add_commands:
+ acl_add_command['rules'] = rule_add_commands
+
+ if acl_del_command.get('rules') or acl_del_command.get('remark'):
+ acl_type_del_commands.append(acl_del_command)
+ if acl_add_command.get('rules') or acl_add_command.get('remark'):
+ acl_type_add_commands.append(acl_add_command)
+
+ # Add new ACLs
+ for acl_name in want_acl_names.difference(have_acl_names):
+ acl_add_command = {'name': acl_name}
+ add_requests.append(self.get_create_l3_acl_request(acl_type, acl_name))
+
+ want_acl = want_dict[acl_type][acl_name]
+ if want_acl['remark']:
+ acl_add_command['remark'] = want_acl['remark']
+ add_requests.append(self.get_create_l3_acl_remark_request(acl_type, acl_name, want_acl['remark']))
+
+ # Add new rules
+ want_seq_nums = set(want_acl['rules'].keys())
+ if want_seq_nums:
+ acl_add_command['rules'] = []
+ for seq_num in want_seq_nums:
+ acl_add_command['rules'].append(want_acl['rules'][seq_num])
+ add_requests.append(self.get_create_l3_acl_rule_request(acl_type, acl_name, seq_num, want_acl['rules'][seq_num]))
+
+ acl_type_add_commands.append(acl_add_command)
+
+ if acl_type_del_commands:
+ del_commands.append({'address_family': acl_type, 'acls': acl_type_del_commands})
+
+ if acl_type_add_commands:
+ add_commands.append({'address_family': acl_type, 'acls': acl_type_add_commands})
+
+ if del_commands:
+ commands = update_states(del_commands, 'deleted')
+ requests = del_requests
+
+ if add_commands:
+ commands.extend(update_states(add_commands, state))
+ requests.extend(add_requests)
+
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ requests = []
+
+ if not want:
+ for config in have:
+ if not config.get('acls'):
+ continue
+
+ acl_type_commands = []
+ acl_type = config['address_family']
+ for acl in config['acls']:
+ acl_type_commands.append({'name': acl['name']})
+ requests.append(self.get_delete_l3_acl_request(acl_type, acl['name']))
+
+ if acl_type_commands:
+ commands.append({'address_family': acl_type, 'acls': acl_type_commands})
+ else:
+ have_dict = self._convert_config_list_to_dict(have)
+ want_dict = self._convert_config_list_to_dict(want)
+
+ for acl_type in ('ipv4', 'ipv6'):
+ acl_type_commands = []
+ have_acl_names = set(have_dict.get(acl_type, {}).keys())
+ want_acl_names = set(want_dict.get(acl_type, {}).keys())
+
+ # If only the type is specified, delete all ACLs of that type
+ if acl_type in want_dict and not want_acl_names:
+ for acl_name in have_acl_names:
+ acl_type_commands.append({'name': acl_name})
+ requests.append(self.get_delete_l3_acl_request(acl_type, acl_name))
+
+ # Delete existing ACLs
+ for acl_name in want_acl_names.intersection(have_acl_names):
+ have_acl = have_dict[acl_type][acl_name]
+ want_acl = want_dict[acl_type][acl_name]
+
+ # Delete entire ACL if only the name is specified
+ if not want_acl['remark'] and not want_acl['rules']:
+ acl_type_commands.append({'name': acl_name})
+ requests.append(self.get_delete_l3_acl_request(acl_type, acl_name))
+ continue
+
+ acl_del_command = {'name': acl_name}
+ rule_del_commands = []
+ have_seq_nums = set(have_acl['rules'].keys())
+ want_seq_nums = set(want_acl['rules'].keys())
+
+ if want_acl['remark'] and want_acl['remark'] == have_acl['remark']:
+ acl_del_command['remark'] = want_acl['remark']
+ requests.append(self.get_delete_l3_acl_remark_request(acl_type, acl_name))
+
+ # Delete existing rules
+ # When state is deleted, options other than sequence_num are not considered
+ for seq_num in want_seq_nums.intersection(have_seq_nums):
+ rule_del_commands.append({'sequence_num': seq_num})
+ requests.append(self.get_delete_l3_acl_rule_request(acl_type, acl_name, seq_num))
+
+ if rule_del_commands:
+ acl_del_command['rules'] = rule_del_commands
+
+ if acl_del_command.get('rules') or acl_del_command.get('remark'):
+ acl_type_commands.append(acl_del_command)
+
+ if acl_type_commands:
+ commands.append({'address_family': acl_type, 'acls': acl_type_commands})
+
+ commands = update_states(commands, "deleted")
+ return commands, requests
+
+ def get_create_l3_acl_request(self, acl_type, acl_name):
+ """Get request to create L3 ACL with specified type and name"""
+ url = self.acl_path
+ payload = {
+ 'acl-set': [{
+ 'name': acl_name,
+ 'type': acl_type_to_payload_map[acl_type],
+ 'config': {
+ 'name': acl_name,
+ 'type': acl_type_to_payload_map[acl_type]
+ }
+ }]
+ }
+
+ return {'path': url, 'method': PATCH, 'data': payload}
+
+ def get_create_l3_acl_remark_request(self, acl_type, acl_name, remark):
+ """Get request to add given remark to the specified L3 ACL"""
+ url = self.l3_acl_remark_path.format(acl_name=acl_name, acl_type=acl_type_to_payload_map[acl_type])
+ payload = {'description': remark}
+ return {'path': url, 'method': PATCH, 'data': payload}
+
+ def get_create_l3_acl_rule_request(self, acl_type, acl_name, seq_num, rule):
+ """Get request to create a rule with given sequence number
+ and configuration in the specified L3 ACL
+ """
+ url = self.l3_acl_rule_path.format(acl_name=acl_name, acl_type=acl_type_to_payload_map[acl_type])
+ payload = {
+ 'openconfig-acl:acl-entry': [{
+ 'sequence-id': seq_num,
+ 'config': {
+ 'sequence-id': seq_num
+ },
+ acl_type: {
+ 'config': {}
+ },
+ 'transport': {
+ 'config': {}
+ },
+ 'actions': {
+ 'config': {
+ 'forwarding-action': action_value_to_payload_map[rule['action']]
+ }
+ }
+ }]
+ }
+ rule_l3_config = payload['openconfig-acl:acl-entry'][0][acl_type]['config']
+ rule_l4_config = payload['openconfig-acl:acl-entry'][0]['transport']['config']
+
+ if rule['protocol'].get('number') is not None:
+ protocol = rule['protocol']['number']
+ rule_l3_config['protocol'] = protocol_number_to_payload_map.get(protocol, protocol)
+ else:
+ protocol = rule['protocol']['name']
+ if protocol not in ('ip', 'ipv6'):
+ rule_l3_config['protocol'] = protocol_name_to_payload_map[protocol]
+
+ if rule['source'].get('host'):
+ rule_l3_config['source-address'] = rule['source']['host'] + acl_type_to_host_mask_map[acl_type]
+ elif rule['source'].get('prefix'):
+ rule_l3_config['source-address'] = rule['source']['prefix']
+
+ src_port_number = self._convert_port_dict_to_payload_format(rule['source'].get('port_number'))
+ if src_port_number:
+ rule_l4_config['source-port'] = src_port_number
+
+ if rule['destination'].get('host'):
+ rule_l3_config['destination-address'] = rule['destination']['host'] + acl_type_to_host_mask_map[acl_type]
+ elif rule['destination'].get('prefix'):
+ rule_l3_config['destination-address'] = rule['destination']['prefix']
+
+ dest_port_number = self._convert_port_dict_to_payload_format(rule['destination'].get('port_number'))
+ if dest_port_number:
+ rule_l4_config['destination-port'] = dest_port_number
+
+ if rule.get('protocol_options'):
+ if protocol in ('icmp', 'icmpv6') and rule['protocol_options'].get(protocol):
+ if rule['protocol_options'][protocol].get('type') is not None:
+ rule_l4_config['icmp-type'] = rule['protocol_options'][protocol]['type']
+ if rule['protocol_options'][protocol].get('code') is not None:
+ rule_l4_config['icmp-code'] = rule['protocol_options'][protocol]['code']
+ elif rule['protocol_options'].get('tcp'):
+ if rule['protocol_options']['tcp'].get('established'):
+ rule_l4_config['tcp-session-established'] = True
+ else:
+ tcp_flag_list = []
+ for tcp_flag in rule['protocol_options']['tcp'].keys():
+ if rule['protocol_options']['tcp'][tcp_flag]:
+ tcp_flag_list.append('tcp_{0}'.format(tcp_flag).upper())
+
+ if tcp_flag_list:
+ rule_l4_config['tcp-flags'] = tcp_flag_list
+
+ if rule.get('vlan_id') is not None:
+ payload['openconfig-acl:acl-entry'][0]['l2'] = {
+ 'config': {
+ 'vlanid': rule['vlan_id']
+ }
+ }
+
+ if rule.get('dscp'):
+ if rule['dscp'].get('value') is not None:
+ rule_l3_config['dscp'] = rule['dscp']['value']
+ else:
+ dscp_opt = next(iter(rule['dscp']))
+ if rule['dscp'][dscp_opt]:
+ rule_l3_config['dscp'] = dscp_name_to_value_map[dscp_opt]
+
+ if rule.get('remark'):
+ payload['openconfig-acl:acl-entry'][0]['config']['description'] = rule['remark']
+
+ return {'path': url, 'method': POST, 'data': payload}
+
+ def get_delete_l3_acl_request(self, acl_type, acl_name):
+ """Get request to delete L3 ACL with specified type and name"""
+ url = self.l3_acl_path.format(acl_name=acl_name, acl_type=acl_type_to_payload_map[acl_type])
+ return {'path': url, 'method': DELETE}
+
+ def get_delete_l3_acl_remark_request(self, acl_type, acl_name):
+ """Get request to delete remark of the specified L3 ACL"""
+ url = self.l3_acl_remark_path.format(acl_name=acl_name, acl_type=acl_type_to_payload_map[acl_type])
+ return {'path': url, 'method': DELETE}
+
+ def get_delete_l3_acl_rule_request(self, acl_type, acl_name, seq_num):
+ """Get request to delete the rule with given sequence number
+ in the specified L3 ACL
+ """
+ url = self.l3_acl_rule_path.format(acl_name=acl_name, acl_type=acl_type_to_payload_map[acl_type])
+ url += '/acl-entry={0}'.format(seq_num)
+ return {'path': url, 'method': DELETE}
+
+ def validate_and_normalize_config(self, config_list):
+ """Validate and normalize the given config"""
+ # Remove empties and validate the config with argument spec
+ updated_config_list = [remove_empties(config) for config in config_list]
+ validate_config(self._module.argument_spec, {'config': updated_config_list})
+
+ state = self._module.params['state']
+ # When state is deleted, options other than sequence_num are not considered
+ if state == 'deleted':
+ return updated_config_list
+
+ for config in updated_config_list:
+ if not config.get('acls'):
+ continue
+
+ acl_type = config['address_family']
+ for acl in config['acls']:
+ if not acl.get('rules'):
+ continue
+
+ acl_name = acl['name']
+ for rule in acl['rules']:
+ seq_num = rule['sequence_num']
+
+ self._check_required(['action', 'source', 'destination', 'protocol'], rule, ['config', 'acls', 'rules'])
+ self._validate_and_normalize_protocol(acl_type, acl_name, rule)
+ protocol = rule['protocol']['name'] if rule['protocol'].get('name') else str(rule['protocol']['number'])
+
+ for endpoint in ('source', 'destination'):
+ if rule[endpoint].get('any') is False:
+ self._invalid_rule('True is the only valid value for {0} -> any'.format(endpoint), acl_type, acl_name, seq_num)
+ elif rule[endpoint].get('host'):
+ rule[endpoint]['host'] = rule[endpoint]['host'].lower()
+ elif rule[endpoint].get('prefix'):
+ rule[endpoint]['prefix'] = rule[endpoint]['prefix'].lower()
+
+ if rule[endpoint].get('port_number'):
+ if protocol not in ('tcp', 'udp'):
+ self._invalid_rule('{0} -> port_number is valid only for TCP or UDP protocol'.format(endpoint), acl_type, acl_name, seq_num)
+
+ self._validate_and_normalize_port_number(acl_type, acl_name, rule, endpoint)
+
+ if rule.get('protocol_options'):
+ protocol_options = next(iter(rule['protocol_options']))
+ if protocol != protocol_options:
+ self._invalid_rule('protocol_options -> {0} is not valid for protocol {1}'.format(protocol_options, protocol),
+ acl_type, acl_name, seq_num)
+
+ self._normalize_protocol_options(rule)
+
+ self._normalize_dscp(rule)
+
+ return updated_config_list
+
+ def _validate_and_normalize_protocol(self, acl_type, acl_name, rule):
+ protocol = rule.get('protocol')
+ if protocol:
+ if protocol.get('number') is not None:
+ if protocol['number'] in protocol_number_to_name_map:
+ protocol['name'] = protocol_number_to_name_map[protocol.pop('number')]
+
+ protocol_name = protocol.get('name')
+ if (acl_type == 'ipv4' and protocol_name in ('ipv6', 'icmpv6')) or (acl_type == 'ipv6' and protocol_name in ('ip', 'icmp')):
+ self._invalid_rule('invalid protocol {0} for {1} ACL'.format(protocol_name, acl_type), acl_type, acl_name, rule['sequence_num'])
+
+ def _validate_and_normalize_port_number(self, acl_type, acl_name, rule, endpoint):
+ port_number = rule.get(endpoint, {}).get('port_number')
+ if port_number:
+ # Greater than 0 is the same as less than 65535
+ if port_number.get('gt') == L4_PORT_START:
+ port_number['lt'] = L4_PORT_END
+ del port_number['gt']
+ elif rule[endpoint]['port_number'].get('range'):
+ port_range = rule[endpoint]['port_number']['range']
+ if port_range['begin'] >= port_range['end']:
+ self._invalid_rule('begin must be less than end in {0} -> port_number -> range'.format(endpoint), acl_type, acl_name, rule['sequence_num'])
+
+ # Range of 0 to x is the same as less than x and
+ # range of x to 65535 is the same as greater than x
+ if port_range['begin'] == L4_PORT_START:
+ port_number['lt'] = port_range['end']
+ del port_number['range']
+ elif port_range['end'] == L4_PORT_END:
+ port_number['gt'] = port_range['begin']
+ del port_number['range']
+
+ def _invalid_rule(self, err_msg, acl_type, acl_name, seq_num):
+ self._module.fail_json(msg='{0} ACL {1}, sequence number {2}: {3}'.format(acl_type, acl_name, seq_num, err_msg))
+
+ def _check_required(self, required_parameters, parameters, options_context=None):
+ if required_parameters:
+ spec = {}
+ for parameter in required_parameters:
+ spec[parameter] = {'required': True}
+
+ try:
+ check_required_arguments(spec, parameters, options_context)
+ except TypeError as exc:
+ self._module.fail_json(msg=str(exc))
+
+ @staticmethod
+ def _normalize_protocol_options(rule):
+ tcp = rule.get('protocol_options', {}).get('tcp')
+ if tcp:
+ # Remove protocol_options option if all tcp options are False
+ if not any(list(tcp.values())):
+ del rule['protocol_options']
+ else:
+ tcp_flag_list = list(tcp.keys())
+ for tcp_flag in tcp_flag_list:
+ # Remove tcp option if its value is False
+ if not tcp[tcp_flag]:
+ del tcp[tcp_flag]
+
+ @staticmethod
+ def _normalize_dscp(rule):
+ dscp = rule.get('dscp')
+ if dscp:
+ if dscp.get('value') is not None:
+ if dscp['value'] in dscp_value_to_name_map:
+ dscp[dscp_value_to_name_map[dscp.pop('value')]] = True
+ else:
+ # Remove dscp option if its value is False
+ if not next(iter(dscp.values())):
+ del rule['dscp']
+
+ @staticmethod
+ def _convert_config_list_to_dict(config_list):
+ config_dict = {}
+ for config in config_list:
+ acl_type = config['address_family']
+ config_dict[acl_type] = {}
+ if config.get('acls'):
+ for acl in config['acls']:
+ acl_name = acl['name']
+ config_dict[acl_type][acl_name] = {}
+ config_dict[acl_type][acl_name]['remark'] = acl.get('remark')
+ config_dict[acl_type][acl_name]['rules'] = {}
+ if acl.get('rules'):
+ for rule in acl['rules']:
+ config_dict[acl_type][acl_name]['rules'][rule['sequence_num']] = rule
+
+ return config_dict
+
+ @staticmethod
+ def _convert_port_dict_to_payload_format(port_dict):
+ payload = None
+ if port_dict:
+ if port_dict.get('eq') is not None:
+ payload = port_dict['eq']
+ elif port_dict.get('lt') is not None:
+ payload = '{0}..{1}'.format(L4_PORT_START, port_dict['lt'])
+ elif port_dict.get('gt') is not None:
+ payload = '{0}..{1}'.format(port_dict['gt'], L4_PORT_END)
+ elif port_dict.get('range'):
+ payload = '{0}..{1}'.format(port_dict['range']['begin'], port_dict['range']['end'])
+
+ return payload
+
+ def sort_config(self, configs):
+ # natsort provides better result.
+ # The use of natsort causes sanity error due to it is not available in
+ # python version currently used.
+ # new_config = natsorted(new_config, key=lambda x: x['name'])
+ # For time-being, use simple "sort"
+ configs.sort(key=lambda x: x['address_family'])
+
+ for conf in configs:
+ acls = conf.get('acls', [])
+ if acls:
+ acls.sort(key=lambda x: x['name'])
+ for acl in acls:
+ if acl.get('rules', []):
+ acl['rules'].sort(key=lambda x: x['sequence_num'])
+
+ def post_process_generated_config(self, configs):
+ for conf in configs[:]:
+ if not conf.get('acls', []):
+ configs.remove(conf)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_interfaces/l3_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_interfaces/l3_interfaces.py
index d1b735251..200d8552b 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_interfaces/l3_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_interfaces/l3_interfaces.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -30,7 +30,6 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
to_request,
edit_config
)
-from ansible.module_utils._text import to_native
from ansible.module_utils.connection import ConnectionError
TEST_KEYS = [
@@ -125,17 +124,17 @@ class L3_interfaces(ConfigBase):
state = self._module.params['state']
diff = get_diff(want, have, TEST_KEYS)
if state == 'overridden':
- commands, requests = self._state_overridden(want, have, diff)
+ commands, requests = self._state_overridden(want, have)
elif state == 'deleted':
- commands, requests = self._state_deleted(want, have, diff)
+ commands, requests = self._state_deleted(want, have)
elif state == 'merged':
commands, requests = self._state_merged(want, have, diff)
elif state == 'replaced':
- commands, requests = self._state_replaced(want, have, diff)
+ commands, requests = self._state_replaced(want, have)
ret_commands = commands
return ret_commands, requests
- def _state_replaced(self, want, have, diff):
+ def _state_replaced(self, want, have):
""" The command generator when state is replaced
:rtype: A list
@@ -144,19 +143,22 @@ class L3_interfaces(ConfigBase):
"""
ret_requests = list()
commands = list()
- l3_interfaces_to_delete = get_diff(have, want, TEST_KEYS)
- obj = self.get_object(l3_interfaces_to_delete, want)
- diff = get_diff(obj, want, TEST_KEYS)
+ new_want = self.update_object(want)
+ new_have = self.remove_default_entries(have)
+ get_replace_interfaces_list = self.get_interface_object_for_replaced(new_have, want)
+
+ diff = get_diff(get_replace_interfaces_list, new_want, TEST_KEYS)
+
if diff:
- delete_l3_interfaces_requests = self.get_delete_all_requests(want)
+ delete_l3_interfaces_requests = self.get_delete_all_requests(diff)
ret_requests.extend(delete_l3_interfaces_requests)
- commands.extend(update_states(want, "deleted"))
+ commands.extend(update_states(diff, "deleted"))
l3_interfaces_to_create_requests = self.get_create_l3_interfaces_requests(want, have, want)
ret_requests.extend(l3_interfaces_to_create_requests)
- commands.extend(update_states(want, "merged"))
+ commands.extend(update_states(want, "replaced"))
return commands, ret_requests
- def _state_overridden(self, want, have, diff):
+ def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
@@ -165,16 +167,19 @@ class L3_interfaces(ConfigBase):
"""
ret_requests = list()
commands = list()
- interfaces_to_delete = get_diff(have, want, TEST_KEYS)
- if interfaces_to_delete:
- delete_interfaces_requests = self.get_delete_l3_interfaces_requests(want, have)
+ new_want = self.update_object(want)
+ new_have = self.remove_default_entries(have)
+ get_override_interfaces = self.get_interface_object_for_overridden(new_have)
+ diff = get_diff(get_override_interfaces, new_want, TEST_KEYS)
+ diff2 = get_diff(new_want, get_override_interfaces, TEST_KEYS)
+
+ if diff or diff2:
+ delete_interfaces_requests = self.get_delete_all_requests(have)
ret_requests.extend(delete_interfaces_requests)
- commands.extend(update_states(interfaces_to_delete, "deleted"))
-
- if diff:
- interfaces_to_create_requests = self.get_create_l3_interfaces_requests(diff, have, want)
+ commands.extend(update_states(diff, "deleted"))
+ interfaces_to_create_requests = self.get_create_l3_interfaces_requests(want, have, want)
ret_requests.extend(interfaces_to_create_requests)
- commands.extend(update_states(diff, "merged"))
+ commands.extend(update_states(want, "overridden"))
return commands, ret_requests
@@ -195,7 +200,7 @@ class L3_interfaces(ConfigBase):
return commands, requests
- def _state_deleted(self, want, have, diff):
+ def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
@@ -215,7 +220,16 @@ class L3_interfaces(ConfigBase):
commands = update_states(commands, "deleted")
return commands, requests
- def get_object(self, have, want):
+ def remove_default_entries(self, have):
+ new_have = list()
+ for obj in have:
+ if obj['ipv4']['addresses'] is not None or obj['ipv4']['anycast_addresses'] is not None:
+ new_have.append(obj)
+ elif obj['ipv6']['addresses'] is not None or obj['ipv6']['enabled']:
+ new_have.append(obj)
+ return new_have
+
+ def get_interface_object_for_replaced(self, have, want):
objects = list()
names = [i.get('name', None) for i in want]
for obj in have:
@@ -223,6 +237,43 @@ class L3_interfaces(ConfigBase):
objects.append(obj.copy())
return objects
+ def update_object(self, want):
+ objects = list()
+ for obj in want:
+ new_obj = {}
+ if 'name' in obj:
+ new_obj['name'] = obj['name']
+ if obj['ipv4'] is None:
+ new_obj['ipv4'] = {'addresses': None, 'anycast_addresses': None}
+ else:
+ new_obj['ipv4'] = obj['ipv4']
+
+ if obj['ipv6'] is None:
+ new_obj['ipv6'] = {'addresses': None, 'enabled': False}
+ else:
+ new_obj['ipv6'] = obj['ipv6']
+
+ objects.append(new_obj)
+ return objects
+
+ def get_interface_object_for_overridden(self, have):
+ objects = list()
+ for obj in have:
+ if 'name' in obj and obj['name'] != "Management0":
+ ipv4_addresses = obj['ipv4']['addresses']
+ ipv6_addresses = obj['ipv6']['addresses']
+ anycast_addresses = obj['ipv4']['anycast_addresses']
+ ipv6_enable = obj['ipv6']['enabled']
+
+ if ipv4_addresses is not None or ipv6_addresses is not None:
+ objects.append(obj.copy())
+ continue
+
+ if ipv6_enable or anycast_addresses is not None:
+ objects.append(obj.copy())
+ continue
+ return objects
+
def get_address(self, ip_str, have_obj):
to_return = list()
for i in have_obj:
@@ -241,6 +292,8 @@ class L3_interfaces(ConfigBase):
ipv6_addr_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/addresses/address={address}'
ipv6_enabled_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config/enabled'
+ if not want:
+ return requests
for each_l3 in want:
l3 = each_l3.copy()
name = l3.pop('name')
@@ -274,7 +327,7 @@ class L3_interfaces(ConfigBase):
if name and ipv4 is None and ipv6 is None:
is_del_ipv4 = True
is_del_ipv6 = True
- elif ipv4 and ipv4.get('addresses') and not ipv4.get('anycast_addresses'):
+ elif ipv4 and not ipv4.get('addresses') and not ipv4.get('anycast_addresses'):
is_del_ipv4 = True
elif ipv6 and not ipv6.get('addresses') and ipv6.get('enabled') is None:
is_del_ipv6 = True
@@ -299,24 +352,27 @@ class L3_interfaces(ConfigBase):
# Store the primary ip at end of the list. So primary ip will be deleted after the secondary ips
ipv4_del_reqs = []
- for ip in ipv4_addrs:
- match_ip = next((addr for addr in have_ipv4_addrs if addr['address'] == ip['address']), None)
- if match_ip:
- addr = ip['address'].split('/')[0]
- del_url = ipv4_addr_url.format(intf_name=name, sub_intf_name=sub_intf, address=addr)
- if match_ip['secondary']:
- del_url += '/config/secondary'
- ipv4_del_reqs.insert(0, {"path": del_url, "method": DELETE})
- else:
- ipv4_del_reqs.append({"path": del_url, "method": DELETE})
- if ipv4_del_reqs:
- requests.extend(ipv4_del_reqs)
-
- for ip in ipv4_anycast_addrs:
- if have_ipv4_addrs and ip in have_ipv4_addrs:
- ip = ip.replace('/', '%2f')
- anycast_delete_request = {"path": ipv4_anycast_url.format(intf_name=name, sub_intf_name=sub_intf, anycast_ip=ip), "method": DELETE}
- requests.append(anycast_delete_request)
+ if ipv4_addrs:
+ for ip in ipv4_addrs:
+ if have_ipv4_addrs:
+ match_ip = next((addr for addr in have_ipv4_addrs if addr['address'] == ip['address']), None)
+ if match_ip:
+ addr = ip['address'].split('/')[0]
+ del_url = ipv4_addr_url.format(intf_name=name, sub_intf_name=sub_intf, address=addr)
+ if match_ip['secondary']:
+ del_url += '/config/secondary'
+ ipv4_del_reqs.insert(0, {"path": del_url, "method": DELETE})
+ else:
+ ipv4_del_reqs.append({"path": del_url, "method": DELETE})
+ if ipv4_del_reqs:
+ requests.extend(ipv4_del_reqs)
+
+ if ipv4_anycast_addrs:
+ for ip in ipv4_anycast_addrs:
+ if have_ipv4_anycast_addrs and ip in have_ipv4_anycast_addrs:
+ ip = ip.replace('/', '%2f')
+ anycast_delete_request = {"path": ipv4_anycast_url.format(intf_name=name, sub_intf_name=sub_intf, anycast_ip=ip), "method": DELETE}
+ requests.append(anycast_delete_request)
if is_del_ipv6:
if have_ipv6_addrs and len(have_ipv6_addrs) != 0:
@@ -334,12 +390,12 @@ class L3_interfaces(ConfigBase):
ipv6_addrs = l3['ipv6']['addresses']
if 'enabled' in l3['ipv6']:
ipv6_enabled = l3['ipv6']['enabled']
-
- for ip in ipv6_addrs:
- if have_ipv6_addrs and ip['address'] in have_ipv6_addrs:
- addr = ip['address'].split('/')[0]
- request = {"path": ipv6_addr_url.format(intf_name=name, sub_intf_name=sub_intf, address=addr), "method": DELETE}
- requests.append(request)
+ if ipv6_addrs:
+ for ip in ipv6_addrs:
+ if have_ipv6_addrs and ip['address'] in have_ipv6_addrs:
+ addr = ip['address'].split('/')[0]
+ request = {"path": ipv6_addr_url.format(intf_name=name, sub_intf_name=sub_intf, address=addr), "method": DELETE}
+ requests.append(request)
if have_ipv6_enabled and ipv6_enabled is not None:
request = {"path": ipv6_enabled_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE}
@@ -349,8 +405,9 @@ class L3_interfaces(ConfigBase):
def get_delete_all_completely_requests(self, configs):
delete_requests = list()
for l3 in configs:
- if l3['ipv4'] or l3['ipv6']:
- delete_requests.append(l3)
+ if l3['name'] != "Management0":
+ if l3['ipv4'] or l3['ipv6']:
+ delete_requests.append(l3)
return self.get_delete_all_requests(delete_requests)
def get_delete_all_requests(self, configs):
@@ -364,6 +421,8 @@ class L3_interfaces(ConfigBase):
name = l3.get('name')
ipv4_addrs = []
ipv4_anycast = []
+ if name == "Management0":
+ continue
if l3.get('ipv4'):
if l3['ipv4'].get('addresses'):
ipv4_addrs = l3['ipv4']['addresses']
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py
index 541de2c4c..7ccd8ce02 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py
@@ -21,6 +21,9 @@ except ImportError:
import json
+from copy import (
+ deepcopy
+)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -39,6 +42,11 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
to_request,
edit_config
)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ get_new_config,
+ get_formatted_config_diff
+)
from ansible.module_utils._text import to_native
from ansible.module_utils.connection import ConnectionError
import traceback
@@ -60,6 +68,10 @@ DELETE = 'delete'
TEST_KEYS = [
{'interfaces': {'member': ''}},
]
+TEST_KEYS_formatted_diff = [
+ {'config': {'name': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+ {'interfaces': {'member': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+]
class Lag_interfaces(ConfigBase):
@@ -119,6 +131,19 @@ class Lag_interfaces(ConfigBase):
if result['changed']:
result['after'] = changed_lag_interfaces_facts
+ new_config = changed_lag_interfaces_facts
+ old_config = existing_lag_interfaces_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_lag_interfaces_facts,
+ TEST_KEYS_formatted_diff)
+ result['after(generated)'] = new_config
+ if self._module._diff:
+ self.sort_config(new_config)
+ self.sort_config(old_config)
+ result['diff'] = get_formatted_config_diff(old_config,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
@@ -188,7 +213,7 @@ class Lag_interfaces(ConfigBase):
replaced_list.append(list_obj)
requests = self.get_delete_lag_interfaces_requests(replaced_list)
if requests:
- commands.extend(update_states(replaced_list, "replaced"))
+ commands.extend(update_states(replaced_list, "deleted"))
replaced_commands, replaced_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, "replaced")
if replaced_requests:
commands.extend(replaced_commands)
@@ -208,20 +233,31 @@ class Lag_interfaces(ConfigBase):
delete_list = list()
delete_list = get_diff(have, want, TEST_KEYS)
delete_members, delete_portchannels = self.diff_list_for_member_creation(delete_list)
+
replaced_list = list()
for i in want:
list_obj = search_obj_in_list(i['name'], delete_members, "name")
if list_obj:
replaced_list.append(list_obj)
+
requests = self.get_delete_lag_interfaces_requests(replaced_list)
- commands.extend(update_states(replaced_list, "overridden"))
- delete_members = get_diff(delete_members, replaced_list, TEST_KEYS)
- commands_overridden, requests_overridden = self.template_for_lag_deletion(have, delete_members, delete_portchannels, "overridden")
- requests.extend(requests_overridden)
- commands.extend(commands_overridden)
+ commands.extend(update_states(replaced_list, "deleted"))
+
+ deleted_po_list = list()
+ for i in delete_list:
+ list_obj = search_obj_in_list(i['name'], want, "name")
+ if not list_obj:
+ deleted_po_list.append(i)
+
+ requests_deleted_po = self.get_delete_portchannel_requests(deleted_po_list)
+ requests.extend(requests_deleted_po)
+ commands_del = self.prune_commands(deleted_po_list)
+ commands.extend(update_states(commands_del, "deleted"))
+
override_commands, override_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, "overridden")
commands.extend(override_commands)
requests.extend(override_requests)
+
return commands, requests
def _state_merged(self, want, have, diff_members, diff_portchannels):
@@ -248,7 +284,8 @@ class Lag_interfaces(ConfigBase):
requests = self.get_delete_all_lag_interfaces_requests()
portchannel_requests = self.get_delete_all_portchannel_requests()
requests.extend(portchannel_requests)
- commands.extend(update_states(have, "Deleted"))
+ commands_del = self.prune_commands(have)
+ commands.extend(update_states(commands_del, "deleted"))
else: # delete specific lag interfaces and specific portchannels
commands = get_diff(want, diff, TEST_KEYS)
commands = remove_empties_from_list(commands)
@@ -312,7 +349,8 @@ class Lag_interfaces(ConfigBase):
commands.extend(update_states(delete_members, state_name))
if delete_portchannels:
portchannel_requests = self.get_delete_portchannel_requests(delete_portchannels)
- commands.extend(update_states(delete_portchannels, state_name))
+ commands_del = self.prune_commands(delete_portchannels)
+ commands.extend(update_states(commands_del, state_name))
if requests:
requests.extend(portchannel_requests)
else:
@@ -336,8 +374,7 @@ class Lag_interfaces(ConfigBase):
def build_create_payload_member(self, name):
payload_template = """{\n"openconfig-if-aggregate:aggregate-id": "{{name}}"\n}"""
- temp = name.split("PortChannel", 1)[1]
- input_data = {"name": temp}
+ input_data = {"name": name}
env = jinja2.Environment(autoescape=False)
t = env.from_string(payload_template)
intended_payload = t.render(input_data)
@@ -419,3 +456,21 @@ class Lag_interfaces(ConfigBase):
requests.append(request)
return requests
+
+ def sort_config(self, configs):
+ # natsort provides better result.
+ # The use of natsort causes sanity error due to it is not available in
+ # python version currently used.
+ # new_config = natsorted(new_config, key=lambda x: x['name'])
+ # For time-being, use simple "sort"
+ configs.sort(key=lambda x: x['name'])
+
+ for conf in configs:
+ if conf.get('members', {}) and conf['members'].get('interfaces', []):
+ conf['members']['interfaces'].sort(key=lambda x: x['member'])
+
+ def prune_commands(self, commands):
+ cmds = deepcopy(commands)
+ for cmd in cmds:
+ cmd.pop('members', None)
+ return cmds
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lldp_global/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lldp_global/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lldp_global/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lldp_global/lldp_global.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lldp_global/lldp_global.py
new file mode 100644
index 000000000..f27d63e81
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lldp_global/lldp_global.py
@@ -0,0 +1,296 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_lldp_global class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_diff,
+ update_states
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils.connection import ConnectionError
+
+
+PATCH = 'patch'
+DELETE = 'delete'
+
+
+class Lldp_global(ConfigBase):
+ """
+ The sonic_lldp_global class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'lldp_global',
+ ]
+
+ lldp_global_path = 'data/openconfig-lldp:lldp/config'
+ lldp_global_config_path = {
+ 'enable': lldp_global_path + '/enabled',
+ 'hello_time': lldp_global_path + '/hello-timer',
+ 'mode': lldp_global_path + '/openconfig-lldp-ext:mode',
+ 'multiplier': lldp_global_path + '/openconfig-lldp-ext:multiplier',
+ 'system_description': lldp_global_path + '/system-description',
+ 'system_name': lldp_global_path + '/system-name',
+ 'tlv_select': lldp_global_path + '/suppress-tlv-advertisement',
+ }
+ lldp_suppress_tlv = '/data/openconfig-lldp:lldp/config/suppress-tlv-advertisement={lldp_suppress_tlv}'
+
+ def __init__(self, module):
+ super(Lldp_global, self).__init__(module)
+
+ def get_lldp_global_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ lldp_global_facts = facts['ansible_network_resources'].get('lldp_global')
+ if not lldp_global_facts:
+ return []
+ return lldp_global_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = []
+
+ existing_lldp_global_facts = self.get_lldp_global_facts()
+ commands, requests = self.set_config(existing_lldp_global_facts)
+ if commands:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+
+ changed_lldp_global_facts = self.get_lldp_global_facts()
+
+ result['before'] = existing_lldp_global_facts
+ if result['changed']:
+ result['after'] = changed_lldp_global_facts
+
+ result['commands'] = commands
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_lldp_global_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ have = existing_lldp_global_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params['state']
+ diff = get_diff(want, have)
+ if state == 'deleted':
+ commands, requests = self._state_deleted(want, have, diff)
+ elif state == 'merged':
+ commands, requests = self._state_merged(diff)
+ return commands, requests
+
+ def _state_merged(self, diff):
+ """ The command generator when state is merged
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = diff
+ requests = []
+ requests.extend(self.get_modify_specific_lldp_global_param_requests(commands))
+ if commands and len(requests) > 0:
+ commands = update_states(commands, 'merged')
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have, diff):
+ """ The command generator when state is deleted
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ requests = []
+
+ if not want:
+ commands = have
+ requests.extend(self.get_delete_lldp_global_completely_requests(commands))
+ else:
+ commands = get_diff(want, diff)
+ requests.extend(self.get_delete_specific_lldp_global_param_requests(commands, have))
+
+ if len(requests) == 0:
+ commands = []
+
+ if commands:
+ commands = update_states(commands, "deleted")
+
+ return commands, requests
+
+ def get_modify_specific_lldp_global_param_requests(self, command):
+ """Get requests to modify specific LLDP Global configurations
+ based on the command specified for the interface
+ """
+ requests = []
+
+ if not command:
+ return requests
+ if 'enable' in command and command['enable'] is not None:
+ payload = {'openconfig-lldp:enabled': command['enable']}
+ url = self.lldp_global_config_path['enable']
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if 'hello_time' in command and command['hello_time'] is not None:
+ payload = {'openconfig-lldp:hello-timer': str(command['hello_time'])}
+ url = self.lldp_global_config_path['hello_time']
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if 'mode' in command and command['mode'] is not None:
+ payload = {'openconfig-lldp-ext:mode': command['mode'].upper()}
+ url = self.lldp_global_config_path['mode']
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if 'multiplier' in command and command['multiplier'] is not None:
+ payload = {'openconfig-lldp-ext:multiplier': int(command['multiplier'])}
+ url = self.lldp_global_config_path['multiplier']
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if 'system_name' in command and command['system_name'] is not None:
+ payload = {'openconfig-lldp:system-name': command['system_name']}
+ url = self.lldp_global_config_path['system_name']
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if 'system_description' in command and command['system_description'] is not None:
+ payload = {'openconfig-lldp:system-description': command['system_description']}
+ url = self.lldp_global_config_path['system_description']
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ if 'tlv_select' in command:
+ if 'management_address' in command['tlv_select']:
+ payload = {'openconfig-lldp:suppress-tlv-advertisement': ["MANAGEMENT_ADDRESS"]}
+ url = self.lldp_global_config_path['tlv_select']
+ if command['tlv_select']['management_address'] is False:
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+ elif command['tlv_select']['management_address'] is True:
+ url = self.lldp_suppress_tlv.format(lldp_suppress_tlv="MANAGEMENT_ADDRESS")
+ requests.append({'path': url, 'method': DELETE})
+ if 'system_capabilities' in command['tlv_select']:
+ payload = {'openconfig-lldp:suppress-tlv-advertisement': ["SYSTEM_CAPABILITIES"]}
+ url = self.lldp_global_config_path['tlv_select']
+ if command['tlv_select']['system_capabilities'] is False:
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+ elif command['tlv_select']['system_capabilities'] is True:
+ url = self.lldp_suppress_tlv.format(lldp_suppress_tlv="SYSTEM_CAPABILITIES")
+ requests.append({'path': url, 'method': DELETE})
+ return requests
+
+ def get_delete_lldp_global_completely_requests(self, have):
+ """Get requests to delete all existing LLDP global
+ configurations in the chassis
+ """
+ default_config_dict = {"enable": True, "tlv_select": {"management_address": True, "system_capabilities": True}}
+ requests = []
+ if default_config_dict != have:
+ return [{'path': self.lldp_global_path, 'method': DELETE}]
+ return requests
+
+ def get_delete_specific_lldp_global_param_requests(self, command, config):
+ """Get requests to delete specific LLDP global configurations
+ based on the command specified for the interface
+ """
+ requests = []
+
+ if not command:
+ return requests
+ if 'hello_time' in command:
+ url = self.lldp_global_config_path['hello_time']
+ requests.append({'path': url, 'method': DELETE})
+
+ if 'enable' in command:
+ url = self.lldp_global_config_path['enable']
+ if command['enable'] is False:
+ payload = {'openconfig-lldp:enabled': True}
+ elif command['enable'] is True:
+ payload = {'openconfig-lldp:enabled': False}
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+ if 'mode' in command:
+ url = self.lldp_global_config_path['mode']
+ requests.append({'path': url, 'method': DELETE})
+
+ if 'multiplier' in command:
+ url = self.lldp_global_config_path['multiplier']
+ requests.append({'path': url, 'method': DELETE})
+
+ if 'system_name' in command:
+ url = self.lldp_global_config_path['system_name']
+ requests.append({'path': url, 'method': DELETE})
+
+ if 'system_description' in command:
+ url = self.lldp_global_config_path['system_description']
+ requests.append({'path': url, 'method': DELETE})
+ # The tlv_select configs are enabled by default.Hence false leads deletion of configs.
+ if 'tlv_select' in command:
+ if 'management_address' in command['tlv_select']:
+ payload = {'openconfig-lldp:suppress-tlv-advertisement': ["MANAGEMENT_ADDRESS"]}
+ url = self.lldp_global_config_path['tlv_select']
+ if command['tlv_select']['management_address'] is True:
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+ elif command['tlv_select']['management_address'] is False:
+ url = self.lldp_suppress_tlv.format(lldp_suppress_tlv="MANAGEMENT_ADDRESS")
+ requests.append({'path': url, 'method': DELETE})
+ if 'system_capabilities' in command['tlv_select']:
+ payload = {'openconfig-lldp:suppress-tlv-advertisement': ["SYSTEM_CAPABILITIES"]}
+ url = self.lldp_global_config_path['tlv_select']
+ if command['tlv_select']['system_capabilities'] is True:
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+ elif command['tlv_select']['system_capabilities'] is False:
+ url = self.lldp_suppress_tlv.format(lldp_suppress_tlv="SYSTEM_CAPABILITIES")
+ requests.append({'path': url, 'method': DELETE})
+ return requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/logging/logging.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/logging/logging.py
new file mode 100644
index 000000000..82262b561
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/logging/logging.py
@@ -0,0 +1,458 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_logging class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_diff,
+ update_states,
+ get_normalize_interface_name,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ get_new_config,
+ get_formatted_config_diff
+)
+from ansible.module_utils.connection import ConnectionError
+
+PATCH = 'PATCH'
+DELETE = 'DELETE'
+
+DEFAULT_REMOTE_PORT = 514
+DEFAULT_LOG_TYPE = 'log'
+
+TEST_KEYS = [
+ {
+ "remote_servers": {"host": ""}
+ }
+]
+TEST_KEYS_formatted_diff = [
+ {
+ "remote_servers": {"host": "", '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}
+ }
+]
+
+
+class Logging(ConfigBase):
+ """
+ The sonic_logging class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'logging',
+ ]
+
+ def __init__(self, module):
+ super(Logging, self).__init__(module)
+
+ def get_logging_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ logging_facts = facts['ansible_network_resources'].get('logging')
+ if not logging_facts:
+ return []
+ return logging_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+ commands = list()
+ requests = list()
+
+ existing_logging_facts = self.get_logging_facts()
+
+ commands, requests = self.set_config(existing_logging_facts)
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_logging_facts = self.get_logging_facts()
+
+ result['before'] = existing_logging_facts
+ if result['changed']:
+ result['after'] = changed_logging_facts
+
+ new_config = changed_logging_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_logging_facts,
+ TEST_KEYS_formatted_diff)
+ result['after(generated)'] = new_config
+
+ if self._module._diff:
+ result['diff'] = get_formatted_config_diff(existing_logging_facts,
+ new_config,
+ self._module._verbosity)
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_logging_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ if want is None:
+ want = []
+
+ have = existing_logging_facts
+ resp = self.set_state(want, have)
+
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params['state']
+
+ self.validate_want(want, state)
+ self.preprocess_want(want, state)
+
+ if state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'merged':
+ commands, requests = self._state_merged(want, have)
+ elif state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have)
+
+ return commands, requests
+
+ def _state_merged(self, want, have):
+ """ The command generator when state is merged
+
+ :param want: the additive configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ diff = get_diff(want, have, TEST_KEYS)
+
+ commands = diff
+ requests = []
+ if commands:
+ requests = self.get_merge_requests(commands, have)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+
+ :param want: the objects from which the configuration should be removed
+ :param have: the current configuration as a dictionary
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ # Get a list of requested servers to delete that are not present in the current
+ # configuration on the device. This list can be used to filter out these
+ # unconfigured servers from the list of "delete" commands to be sent to the switch.
+ unconfigured = get_diff(want, have, TEST_KEYS)
+
+ want_none = {'remote_servers': None}
+ want_any = get_diff(want, want_none, TEST_KEYS)
+ # if want_any is none, then delete all NTP configurations
+
+ delete_all = False
+ if not want_any:
+ commands = have
+ delete_all = True
+ else:
+ if not unconfigured:
+ commands = want_any
+ else:
+ # Some of the servers requested for deletion are not in the current
+ # device configuration. Filter these out of the list to be used for sending
+ # "delete" commands to the device.
+ commands = get_diff(want_any, unconfigured, TEST_KEYS)
+
+ requests = []
+ if commands:
+ requests = self.get_delete_requests(commands, delete_all)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_replaced(self, want, have):
+ """ The command generator when state is replaced
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ replaced_config = self.get_replaced_config(have, want)
+ if 'remote_servers' in replaced_config:
+ replaced_config['remote_servers'].sort(key=self.get_host)
+ if 'remote_servers' in want:
+ want['remote_servers'].sort(key=self.get_host)
+
+ if replaced_config and replaced_config != want:
+ delete_all = False
+ del_requests = self.get_delete_requests(replaced_config, delete_all)
+ requests.extend(del_requests)
+ commands.extend(update_states(replaced_config, "deleted"))
+ replaced_config = []
+
+ if not replaced_config and want:
+ add_commands = want
+ add_requests = self.get_merge_requests(add_commands, replaced_config)
+
+ if len(add_requests) > 0:
+ requests.extend(add_requests)
+ commands.extend(update_states(add_commands, "replaced"))
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ if 'remote_servers' in have:
+ have['remote_servers'].sort(key=self.get_host)
+ if 'remote_servers' in want:
+ want['remote_servers'].sort(key=self.get_host)
+
+ commands = []
+ requests = []
+
+ if have and have != want:
+ delete_all = True
+ del_requests = self.get_delete_requests(have, delete_all)
+ requests.extend(del_requests)
+ commands.extend(update_states(have, "deleted"))
+ have = []
+
+ if not have and want:
+ add_commands = want
+ add_requests = self.get_merge_requests(add_commands, have)
+
+ if len(add_requests) > 0:
+ requests.extend(add_requests)
+ commands.extend(update_states(add_commands, "overridden"))
+
+ return commands, requests
+
+ def get_host(self, remote_server):
+ return remote_server.get('host')
+
+ def search_config_servers(self, host, servers):
+
+ if servers is not None:
+ for server in servers:
+ if server['host'] == host:
+ return server
+ return []
+
+ def get_replaced_config(self, have, want):
+
+ replaced_config = dict()
+ replaced_servers = []
+ if 'remote_servers' in have and 'remote_servers' in want:
+ for server in want['remote_servers']:
+ replaced_server = self.search_config_servers(server['host'], have['remote_servers'])
+ if replaced_server:
+ replaced_servers.append(replaced_server)
+
+ replaced_config['remote_servers'] = replaced_servers
+ return replaced_config
+
+ def validate_want(self, want, state):
+
+ if state == 'deleted':
+
+ if 'remote_servers' in want and want['remote_servers'] is not None:
+ for server in want['remote_servers']:
+ source_interface_config = server.get('source_interface', None)
+ remote_port_config = server.get('remote_port', None)
+ message_type_config = server.get('message_type', None)
+ vrf_config = server.get('vrf', None)
+ if source_interface_config or remote_port_config or \
+ message_type_config or vrf_config:
+ err_msg = "Logging remote_server parameter(s) can not be deleted."
+ self._module.fail_json(msg=err_msg, code=405)
+
+ def preprocess_want(self, want, state):
+
+ if state == 'merged':
+ if 'remote_servers' in want and want['remote_servers'] is not None:
+ for server in want['remote_servers']:
+ if 'source_interface' in server and not server['source_interface']:
+ server.pop('source_interface', None)
+ else:
+ server['source_interface'] = \
+ get_normalize_interface_name(server['source_interface'], self._module)
+ if 'remote_port' in server and not server['remote_port']:
+ server.pop('remote_port', None)
+ if 'message_type' in server and not server['message_type']:
+ server.pop('message_type', None)
+ if 'vrf' in server and not server['vrf']:
+ server.pop('vrf', None)
+
+ if state == 'replaced' or state == 'overridden':
+ if 'remote_servers' in want and want['remote_servers'] is not None:
+ for server in want['remote_servers']:
+ if 'source_interface' in server and not server['source_interface']:
+ server.pop('source_interface', None)
+ else:
+ server['source_interface'] = \
+ get_normalize_interface_name(server['source_interface'], self._module)
+ if 'remote_port' in server and not server['remote_port']:
+ server['remote_port'] = DEFAULT_REMOTE_PORT
+ if 'message_type' in server and not server['message_type']:
+ server['message_type'] = DEFAULT_LOG_TYPE
+
+ def get_merge_requests(self, configs, have):
+
+ requests = []
+
+ servers_config = configs.get('remote_servers', None)
+ if servers_config:
+ servers_request = self.get_create_servers_requests(servers_config, have)
+ if servers_request:
+ requests.extend(servers_request)
+
+ return requests
+
+ def get_delete_requests(self, configs, delete_all):
+
+ requests = []
+
+ servers_config = configs.get('remote_servers', None)
+ if servers_config:
+ servers_request = []
+ if delete_all:
+ servers_request = self.get_delete_all_servers_requests()
+ else:
+ servers_request = self.get_delete_servers_requests(servers_config)
+
+ if servers_request:
+ requests.extend(servers_request)
+
+ return requests
+
+ def get_create_servers_requests(self, configs, have):
+
+ requests = []
+
+ # Create URL and payload
+ method = PATCH
+ url = 'data/openconfig-system:system/logging/remote-servers'
+ server_configs = []
+ for config in configs:
+ req_config = dict()
+ req_config['host'] = config['host']
+ if 'source_interface' in config:
+ req_config['source-interface'] = config['source_interface']
+ if 'message_type' in config:
+ req_config['message-type'] = config['message_type']
+ if 'remote_port' in config:
+ req_config['remote-port'] = config['remote_port']
+ if 'vrf' in config:
+ req_config['vrf-name'] = config['vrf']
+
+ server_host = config['host']
+ server_config = {"host": server_host, "config": req_config}
+ server_configs.append(server_config)
+
+ payload = {"openconfig-system:remote-servers": {"remote-server": server_configs}}
+ request = {"path": url, "method": method, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_servers_requests(self, configs):
+
+ requests = []
+
+ # Create URL and payload
+ method = DELETE
+ for config in configs:
+ server_host = config['host']
+ url = 'data/openconfig-system:system/logging/remote-servers/remote-server={0}'.format(server_host)
+ request = {"path": url, "method": method}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_all_servers_requests(self):
+
+ requests = []
+
+ # Create URL and payload
+ method = DELETE
+ url = 'data/openconfig-system:system/logging/remote-servers'
+ request = {"path": url, "method": method}
+ requests.append(request)
+
+ return requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mac/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mac/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mac/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mac/mac.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mac/mac.py
new file mode 100644
index 000000000..866ff3934
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mac/mac.py
@@ -0,0 +1,431 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_mac class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ update_states,
+ get_diff,
+ get_replaced_config,
+ send_requests
+)
+
+NETWORK_INSTANCE_PATH = '/data/openconfig-network-instance:network-instances/network-instance'
+PATCH = 'patch'
+DELETE = 'delete'
+TEST_KEYS = [
+ {'config': {'vrf_name': ''}},
+ {'mac_table_entries': {'mac_address': '', 'vlan_id': ''}}
+]
+
+
+class Mac(ConfigBase):
+ """
+ The sonic_mac class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'mac',
+ ]
+
+ def __init__(self, module):
+ super(Mac, self).__init__(module)
+
+ def get_mac_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ mac_facts = facts['ansible_network_resources'].get('mac')
+ if not mac_facts:
+ return []
+ return mac_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = []
+ commands = []
+
+ existing_mac_facts = self.get_mac_facts()
+ commands, requests = self.set_config(existing_mac_facts)
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_mac_facts = self.get_mac_facts()
+
+ result['before'] = existing_mac_facts
+ if result['changed']:
+ result['after'] = changed_mac_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_mac_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ have = existing_mac_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ state = self._module.params['state']
+
+ diff = get_diff(want, have, TEST_KEYS)
+
+ if state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'merged':
+ commands, requests = self._state_merged(diff)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have, diff)
+ return commands, requests
+
+ def _state_replaced(self, want, have, diff):
+ """ The command generator when state is replaced
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ replaced_config = get_replaced_config(want, have, TEST_KEYS)
+
+ if replaced_config:
+ self.sort_lists_in_config(replaced_config)
+ self.sort_lists_in_config(have)
+ is_delete_all = (replaced_config == have)
+ requests = self.get_delete_mac_requests(replaced_config, have, is_delete_all)
+ send_requests(self._module, requests)
+
+ commands = want
+ else:
+ commands = diff
+
+ requests = []
+
+ if commands:
+ requests = self.get_modify_mac_requests(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "replaced")
+ else:
+ commands = []
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ self.sort_lists_in_config(want)
+ self.sort_lists_in_config(have)
+
+ if have and have != want:
+ is_delete_all = True
+ requests = self.get_delete_mac_requests(have, None, is_delete_all)
+ send_requests(self._module, requests)
+ have = []
+
+ commands = []
+ requests = []
+
+ if not have and want:
+ commands = want
+ requests = self.get_modify_mac_requests(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "overridden")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_merged(self, diff):
+ """ The command generator when state is merged
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = diff
+ requests = self.get_modify_mac_requests(commands)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ is_delete_all = False
+ # if want is none, then delete ALL
+ if not want:
+ commands = have
+ is_delete_all = True
+ else:
+ commands = want
+
+ commands = self.remove_default_entries(commands)
+ requests = self.get_delete_mac_requests(commands, have, is_delete_all)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def get_modify_mac_requests(self, commands):
+
+ requests = []
+
+ if not commands:
+ return requests
+
+ for cmd in commands:
+ vrf_name = cmd.get('vrf_name', None)
+ mac = cmd.get('mac', {})
+ if mac:
+ aging_time = mac.get('aging_time', None)
+ dampening_interval = mac.get('dampening_interval', None)
+ dampening_threshold = mac.get('dampening_threshold', None)
+ mac_table_entries = mac.get('mac_table_entries', [])
+ fdb_dict = {}
+ dampening_cfg_dict = {}
+ if aging_time:
+ fdb_dict['config'] = {'mac-aging-time': aging_time}
+ if dampening_interval:
+ dampening_cfg_dict['interval'] = dampening_interval
+ if dampening_threshold:
+ dampening_cfg_dict['threshold'] = dampening_threshold
+ if mac_table_entries:
+ entry_list = []
+ entries_dict = {}
+ mac_table_dict = {}
+ for entry in mac_table_entries:
+ entry_dict = {}
+ entry_cfg_dict = {}
+ mac_address = entry.get('mac_address', None)
+ vlan_id = entry.get('vlan_id', None)
+ interface = entry.get('interface', None)
+ if mac_address:
+ entry_dict['mac-address'] = mac_address
+ entry_cfg_dict['mac-address'] = mac_address
+ if vlan_id:
+ entry_dict['vlan'] = vlan_id
+ entry_cfg_dict['vlan'] = vlan_id
+ if entry_cfg_dict:
+ entry_dict['config'] = entry_cfg_dict
+ if interface:
+ entry_dict['interface'] = {'interface-ref': {'config': {'interface': interface, 'subinterface': 0}}}
+ if entry_dict:
+ entry_list.append(entry_dict)
+ if entry_list:
+ entries_dict['entry'] = entry_list
+ if entries_dict:
+ mac_table_dict['entries'] = entries_dict
+ if mac_table_dict:
+ fdb_dict['mac-table'] = mac_table_dict
+ if fdb_dict:
+ url = '%s=%s/fdb' % (NETWORK_INSTANCE_PATH, vrf_name)
+ payload = {'openconfig-network-instance:fdb': fdb_dict}
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+ if dampening_cfg_dict:
+ url = '%s=%s/openconfig-mac-dampening:mac-dampening' % (NETWORK_INSTANCE_PATH, vrf_name)
+ payload = {'openconfig-mac-dampening:mac-dampening': {'config': dampening_cfg_dict}}
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ return requests
+
+ def get_delete_mac_requests(self, commands, have, is_delete_all):
+ requests = []
+
+ for cmd in commands:
+ vrf_name = cmd.get('vrf_name', None)
+ if vrf_name and is_delete_all:
+ requests.extend(self.get_delete_all_mac_requests(vrf_name))
+ else:
+ mac = cmd.get('mac', {})
+ if mac:
+ aging_time = mac.get('aging_time', None)
+ dampening_interval = mac.get('dampening_interval', None)
+ dampening_threshold = mac.get('dampening_threshold', None)
+ mac_table_entries = mac.get('mac_table_entries', [])
+
+ for cfg in have:
+ cfg_vrf_name = cfg.get('vrf_name', None)
+ cfg_mac = cfg.get('mac', {})
+ if cfg_mac:
+ cfg_aging_time = cfg_mac.get('aging_time', None)
+ cfg_dampening_interval = cfg_mac.get('dampening_interval', None)
+ cfg_dampening_threshold = cfg_mac.get('dampening_threshold', None)
+ cfg_mac_table_entries = cfg_mac.get('mac_table_entries', [])
+
+ if vrf_name and vrf_name == cfg_vrf_name:
+ if aging_time and aging_time == cfg_aging_time:
+ requests.append(self.get_delete_fdb_cfg_attr(vrf_name, 'mac-aging-time'))
+ if dampening_interval and dampening_interval == cfg_dampening_interval:
+ requests.append(self.get_delete_mac_dampening_attr(vrf_name, 'interval'))
+ if dampening_threshold and dampening_threshold == cfg_dampening_threshold:
+ requests.append(self.get_delete_mac_dampening_attr(vrf_name, 'threshold'))
+
+ if mac_table_entries:
+ for entry in mac_table_entries:
+ mac_address = entry.get('mac_address', None)
+ vlan_id = entry.get('vlan_id', None)
+ interface = entry.get('interface', None)
+
+ if cfg_mac_table_entries:
+ for cfg_entry in cfg_mac_table_entries:
+ cfg_mac_address = cfg_entry.get('mac_address', None)
+ cfg_vlan_id = cfg_entry.get('vlan_id', None)
+ cfg_interface = cfg_entry.get('interface', None)
+ if mac_address and vlan_id and mac_address == cfg_mac_address and vlan_id == cfg_vlan_id:
+ if interface and interface == cfg_interface:
+ requests.append(self.get_delete_mac_table_intf(vrf_name, mac_address, vlan_id))
+ elif not interface:
+ requests.append(self.get_delete_mac_table_entry(vrf_name, mac_address, vlan_id))
+ return requests
+
+ def get_delete_all_mac_requests(self, vrf_name):
+ requests = []
+ url = '%s=%s/fdb' % (NETWORK_INSTANCE_PATH, vrf_name)
+ requests.append({'path': url, 'method': DELETE})
+ url = '%s=%s/openconfig-mac-dampening:mac-dampening' % (NETWORK_INSTANCE_PATH, vrf_name)
+ requests.append({'path': url, 'method': DELETE})
+
+ return requests
+
+ def get_delete_fdb_cfg_attr(self, vrf_name, attr):
+ url = '%s=%s/fdb/config/%s' % (NETWORK_INSTANCE_PATH, vrf_name, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_mac_dampening_attr(self, vrf_name, attr):
+ url = '%s=%s/openconfig-mac-dampening:mac-dampening/config/%s' % (NETWORK_INSTANCE_PATH, vrf_name, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_mac_table_entry(self, vrf_name, mac_address, vlan_id):
+ url = '%s=%s/fdb/mac-table/entries/entry=%s,%s' % (NETWORK_INSTANCE_PATH, vrf_name, mac_address, vlan_id)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_mac_table_intf(self, vrf_name, mac_address, vlan_id):
+ url = '%s=%s/fdb/mac-table/entries/entry=%s,%s/interface' % (NETWORK_INSTANCE_PATH, vrf_name, mac_address, vlan_id)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_mac_vrf_name(self, vrf_name):
+ return vrf_name.get('vrf_name')
+
+ def sort_lists_in_config(self, config):
+ if config:
+ config.sort(key=self.get_mac_vrf_name)
+ for cfg in config:
+ if 'mac' in cfg and cfg['mac'] is not None:
+ if 'mac_table_entries' in cfg['mac'] and cfg['mac']['mac_table_entries'] is not None:
+ cfg['mac']['mac_table_entries'].sort(key=lambda x: (x['mac_address'], x['vlan_id']))
+
+ def remove_default_entries(self, data):
+ new_data = []
+
+ if not data:
+ return new_data
+
+ for conf in data:
+ new_conf = {}
+ vrf_name = conf.get('vrf_name', None)
+ mac = conf.get('mac', None)
+ if mac:
+ new_mac = {}
+ aging_time = mac.get('aging_time', None)
+ dampening_interval = mac.get('dampening_interval', None)
+ dampening_threshold = mac.get('dampening_threshold', None)
+ mac_table_entries = mac.get('mac_table_entries', None)
+
+ if aging_time and aging_time != 600:
+ new_mac['aging_time'] = aging_time
+ if dampening_interval and dampening_interval != 5:
+ new_mac['dampening_interval'] = dampening_interval
+ if dampening_threshold and dampening_threshold != 5:
+ new_mac['dampening_threshold'] = dampening_threshold
+ if mac_table_entries is not None:
+ new_mac['mac_table_entries'] = mac_table_entries
+ if new_mac:
+ new_conf['mac'] = new_mac
+ new_conf['vrf_name'] = vrf_name
+ if new_conf:
+ new_data.append(new_conf)
+
+ return new_data
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mclag/mclag.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mclag/mclag.py
index 88215e8fc..68c99f0ab 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mclag/mclag.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mclag/mclag.py
@@ -14,16 +14,21 @@ created
from __future__ import absolute_import, division, print_function
__metaclass__ = type
+
+import re
+from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_empties,
to_list
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
update_states,
get_diff,
+ get_ranges_in_list,
get_normalize_interface_name,
normalize_interface_name
)
@@ -57,6 +62,17 @@ class Mclag(ConfigBase):
'mclag',
]
+ mclag_simple_attrs = set({
+ 'peer_address',
+ 'source_address',
+ 'peer_link',
+ 'system_mac',
+ 'keepalive',
+ 'session_timeout',
+ 'delay_restore',
+ 'gateway_mac'
+ })
+
def __init__(self, module):
super(Mclag, self).__init__(module)
@@ -123,6 +139,11 @@ class Mclag(ConfigBase):
vlans_list = unique_ip['vlans']
if vlans_list:
normalize_interface_name(vlans_list, self._module, 'vlan')
+ peer_gateway = want.get('peer_gateway', None)
+ if peer_gateway:
+ vlans_list = peer_gateway['vlans']
+ if vlans_list:
+ normalize_interface_name(vlans_list, self._module, 'vlan')
members = want.get('members', None)
if members:
portchannels_list = members['portchannels']
@@ -143,11 +164,13 @@ class Mclag(ConfigBase):
"""
state = self._module.params['state']
if state == 'deleted':
- commands = self._state_deleted(want, have)
+ commands, requests = self._state_deleted(want, have)
elif state == 'merged':
diff = get_diff(want, have, TEST_KEYS)
- commands = self._state_merged(want, have, diff)
- return commands
+ commands, requests = self._state_merged(want, have, diff)
+ elif state in ('replaced', 'overridden'):
+ commands, requests = self._state_replaced_overridden(want, have, state)
+ return commands, requests
def _state_merged(self, want, have, diff):
""" The command generator when state is merged
@@ -159,7 +182,21 @@ class Mclag(ConfigBase):
requests = []
commands = []
if diff:
- requests = self.get_create_mclag_request(want, diff)
+ # Obtain diff for VLAN ranges in unique_ip
+ if 'unique_ip' in diff and diff['unique_ip'] is not None and diff['unique_ip'].get('vlans'):
+ if 'unique_ip' in have and have['unique_ip'] is not None and have['unique_ip'].get('vlans'):
+ diff['unique_ip']['vlans'] = self.get_vlan_range_diff(diff['unique_ip']['vlans'], have['unique_ip']['vlans'])
+ if not diff['unique_ip']['vlans']:
+ diff.pop('unique_ip')
+
+ # Obtain diff for VLAN ranges in peer_gateway
+ if 'peer_gateway' in diff and diff['peer_gateway'] is not None and diff['peer_gateway'].get('vlans'):
+ if 'peer_gateway' in have and have['peer_gateway'] is not None and have['peer_gateway'].get('vlans'):
+ diff['peer_gateway']['vlans'] = self.get_vlan_range_diff(diff['peer_gateway']['vlans'], have['peer_gateway']['vlans'])
+ if not diff['peer_gateway']['vlans']:
+ diff.pop('peer_gateway')
+
+ requests = self.get_create_mclag_requests(want, diff)
if len(requests) > 0:
commands = update_states(diff, "merged")
return commands, requests
@@ -175,19 +212,159 @@ class Mclag(ConfigBase):
requests = []
if not want:
if have:
- requests = self.get_delete_all_mclag_domain_request()
+ requests = self.get_delete_all_mclag_domain_requests(have)
if len(requests) > 0:
commands = update_states(have, "deleted")
else:
+ del_unique_ip_vlans = []
+ del_peer_gateway_vlans = []
+ # Create list of VLANs to be deleted based on VLAN ranges in unique_ip
+ if 'unique_ip' in want and want['unique_ip'] is not None and want['unique_ip'].get('vlans'):
+ want_unique_ip = want.pop('unique_ip')
+ if 'unique_ip' in have and have['unique_ip'] is not None and have['unique_ip'].get('vlans'):
+ del_unique_ip_vlans = self.get_vlan_range_common(want_unique_ip['vlans'], have['unique_ip']['vlans'])
+
+ # Create list of VLANs to be deleted based on VLAN ranges in peer_gateway
+ if 'peer_gateway' in want and want['peer_gateway'] is not None and want['peer_gateway'].get('vlans'):
+ want_peer_gateway = want.pop('peer_gateway')
+ if 'peer_gateway' in have and have['peer_gateway'] is not None and have['peer_gateway'].get('vlans'):
+ del_peer_gateway_vlans = self.get_vlan_range_common(want_peer_gateway['vlans'], have['peer_gateway']['vlans'])
+
new_have = self.remove_default_entries(have)
d_diff = get_diff(want, new_have, TEST_KEYS, is_skeleton=True)
diff_want = get_diff(want, d_diff, TEST_KEYS, is_skeleton=True)
+
+ if del_unique_ip_vlans:
+ diff_want['unique_ip'] = {'vlans': del_unique_ip_vlans}
+ if del_peer_gateway_vlans:
+ diff_want['peer_gateway'] = {'vlans': del_peer_gateway_vlans}
+
if diff_want:
- requests = self.get_delete_mclag_attribute_request(want, diff_want)
+ requests = self.get_delete_mclag_attribute_requests(have['domain_id'], diff_want)
if len(requests) > 0:
commands = update_states(diff_want, "deleted")
return commands, requests
+ def _state_replaced_overridden(self, want, have, state):
+ """ The command generator when state is replaced/overridden
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ requests = []
+ if want and not have:
+ commands = [update_states(want, state)]
+ requests = self.get_create_mclag_requests(want, want)
+ elif not want and have:
+ commands = [update_states(have, 'deleted')]
+ requests = self.get_delete_all_mclag_domain_requests(have)
+ elif want and have:
+ add_command = {}
+ del_command = {}
+ delete_all = False
+
+ # If 'domain_id' is modified, delete all mclag configuration.
+ if want['domain_id'] != have['domain_id']:
+ del_command = have
+ add_command = want
+ delete_all = True
+ else:
+ have = have.copy()
+ want = want.copy()
+ delete_all_vlans = {
+ 'unique_ip': False,
+ 'peer_gateway': False
+ }
+
+ # Delete unspecified configurations when:
+ # 1) state is overridden.
+ # 2) state is replaced and configuration other than
+ # unique_ip, peer_gateway or members is specified.
+ delete_unspecified = True
+ if state == 'replaced' and not self.mclag_simple_attrs.intersection(remove_empties(want).keys()):
+ delete_unspecified = False
+
+ # Create lists of VLANs to be deleted and added based on VLAN ranges
+ for option in ('unique_ip', 'peer_gateway'):
+ have_cfg = {}
+ want_cfg = {}
+ # The options are removed from the dict to avoid
+ # comparing the VLAN ranges two more times using get_diff
+ if have.get(option) and have[option].get('vlans'):
+ have_cfg = have.pop(option)
+ if want.get(option) and 'vlans' in want[option]:
+ want_cfg = want.pop(option)
+
+ if want_cfg:
+ if have_cfg:
+ # Delete all VLANs if empty 'vlans' list is provided
+ if not want_cfg['vlans']:
+ delete_all_vlans[option] = True
+ del_command[option] = have_cfg
+ else:
+ have_vlans = set(self.get_vlan_id_list(have_cfg['vlans']))
+ want_vlans = set(self.get_vlan_id_list(want_cfg['vlans']))
+ if have_vlans.intersection(want_vlans):
+ del_command[option] = {'vlans': self.get_vlan_range_list(list(have_vlans - want_vlans))}
+ if not del_command[option]['vlans']:
+ del_command.pop(option)
+ add_command[option] = {'vlans': self.get_vlan_range_list(list(want_vlans - have_vlans))}
+ if not add_command[option]['vlans']:
+ add_command.pop(option)
+ else:
+ delete_all_vlans[option] = True
+ del_command[option] = have_cfg
+ add_command[option] = want_cfg
+ else:
+ if want_cfg['vlans']:
+ add_command[option] = want_cfg
+ else:
+ if have_cfg and delete_unspecified:
+ delete_all_vlans[option] = True
+ del_command[option] = have_cfg
+
+ del_diff = get_diff(self.remove_default_entries(have), want, TEST_KEYS)
+ for option in del_diff:
+ if not want.get(option):
+ if delete_unspecified:
+ del_command[option] = del_diff[option]
+ else:
+ # Delete portchannels that are not specified
+ if option == 'members' and want.get(option):
+ del_command[option] = del_diff[option]
+
+ # To update 'gateway_mac' configuration in the device,
+ # delete already configured value.
+ if option == 'gateway_mac' and want.get(option):
+ del_command[option] = del_diff[option]
+
+ diff = get_diff(want, have, TEST_KEYS)
+ add_command.update(diff)
+
+ if del_command:
+ del_command['domain_id'] = have['domain_id']
+ commands.extend(update_states(del_command, 'deleted'))
+ if delete_all:
+ requests = self.get_delete_all_mclag_domain_requests(del_command)
+ else:
+ if any(delete_all_vlans.values()):
+ del_command = deepcopy(del_command)
+
+ # Set 'vlans' to None to delete all VLANs
+ for option in delete_all_vlans:
+ if delete_all_vlans[option]:
+ del_command[option]['vlans'] = None
+ requests = self.get_delete_mclag_attribute_requests(del_command['domain_id'], del_command)
+
+ if add_command:
+ add_command['domain_id'] = want['domain_id']
+ commands.extend(update_states(add_command, state))
+ requests.extend(self.get_create_mclag_requests(add_command, add_command))
+
+ return commands, requests
+
def remove_default_entries(self, data):
new_data = {}
if not data:
@@ -196,6 +373,7 @@ class Mclag(ConfigBase):
default_val_dict = {
'keepalive': 1,
'session_timeout': 30,
+ 'delay_restore': 300
}
for key, val in data.items():
if not (val is None or (key in default_val_dict and val == default_val_dict[key])):
@@ -203,9 +381,9 @@ class Mclag(ConfigBase):
return new_data
- def get_delete_mclag_attribute_request(self, want, command):
+ def get_delete_mclag_attribute_requests(self, domain_id, command):
requests = []
- url_common = 'data/openconfig-mclag:mclag/mclag-domains/mclag-domain=%s/config' % (want["domain_id"])
+ url_common = 'data/openconfig-mclag:mclag/mclag-domains/mclag-domain=%s/config' % (domain_id)
method = DELETE
if 'source_address' in command and command["source_address"] is not None:
url = url_common + '/source-address'
@@ -231,16 +409,30 @@ class Mclag(ConfigBase):
url = url_common + '/mclag-system-mac'
request = {'path': url, 'method': method}
requests.append(request)
+ if 'delay_restore' in command and command['delay_restore'] is not None:
+ url = url_common + '/delay-restore'
+ request = {'path': url, 'method': method}
+ requests.append(request)
+ if 'peer_gateway' in command and command['peer_gateway'] is not None:
+ if command['peer_gateway']['vlans'] is None:
+ request = {'path': 'data/openconfig-mclag:mclag/vlan-ifs/vlan-if', 'method': method}
+ requests.append(request)
+ elif command['peer_gateway']['vlans'] is not None:
+ vlan_id_list = self.get_vlan_id_list(command['peer_gateway']['vlans'])
+ for vlan in vlan_id_list:
+ peer_gateway_url = 'data/openconfig-mclag:mclag/vlan-ifs/vlan-if=Vlan{0}'.format(vlan)
+ request = {'path': peer_gateway_url, 'method': method}
+ requests.append(request)
if 'unique_ip' in command and command['unique_ip'] is not None:
if command['unique_ip']['vlans'] is None:
request = {'path': 'data/openconfig-mclag:mclag/vlan-interfaces/vlan-interface', 'method': method}
requests.append(request)
elif command['unique_ip']['vlans'] is not None:
- for each in command['unique_ip']['vlans']:
- if each:
- unique_ip_url = 'data/openconfig-mclag:mclag/vlan-interfaces/vlan-interface=%s' % (each['vlan'])
- request = {'path': unique_ip_url, 'method': method}
- requests.append(request)
+ vlan_id_list = self.get_vlan_id_list(command['unique_ip']['vlans'])
+ for vlan in vlan_id_list:
+ unique_ip_url = 'data/openconfig-mclag:mclag/vlan-interfaces/vlan-interface=Vlan{0}'.format(vlan)
+ request = {'path': unique_ip_url, 'method': method}
+ requests.append(request)
if 'members' in command and command['members'] is not None:
if command['members']['portchannels'] is None:
request = {'path': 'data/openconfig-mclag:mclag/interfaces/interface', 'method': method}
@@ -251,17 +443,29 @@ class Mclag(ConfigBase):
portchannel_url = 'data/openconfig-mclag:mclag/interfaces/interface=%s' % (each['lag'])
request = {'path': portchannel_url, 'method': method}
requests.append(request)
+ if 'gateway_mac' in command and command['gateway_mac'] is not None:
+ request = {'path': 'data/openconfig-mclag:mclag/mclag-gateway-macs/mclag-gateway-mac', 'method': method}
+ requests.append(request)
return requests
- def get_delete_all_mclag_domain_request(self):
+ def get_delete_all_mclag_domain_requests(self, have):
requests = []
path = 'data/openconfig-mclag:mclag/mclag-domains'
method = DELETE
+ if have.get('peer_gateway'):
+ request = {'path': 'data/openconfig-mclag:mclag/vlan-ifs/vlan-if', 'method': method}
+ requests.append(request)
+ if have.get('unique_ip'):
+ request = {'path': 'data/openconfig-mclag:mclag/vlan-interfaces/vlan-interface', 'method': method}
+ requests.append(request)
+ if have.get('gateway_mac'):
+ request = {'path': 'data/openconfig-mclag:mclag/mclag-gateway-macs/mclag-gateway-mac', 'method': method}
+ requests.append(request)
request = {'path': path, 'method': method}
requests.append(request)
return requests
- def get_create_mclag_request(self, want, commands):
+ def get_create_mclag_requests(self, want, commands):
requests = []
path = 'data/openconfig-mclag:mclag/mclag-domains/mclag-domain'
method = PATCH
@@ -269,6 +473,17 @@ class Mclag(ConfigBase):
if payload:
request = {'path': path, 'method': method, 'data': payload}
requests.append(request)
+ if 'gateway_mac' in commands and commands['gateway_mac'] is not None:
+ gateway_mac_path = 'data/openconfig-mclag:mclag/mclag-gateway-macs/mclag-gateway-mac'
+ gateway_mac_method = PATCH
+ gateway_mac_payload = {
+ 'openconfig-mclag:mclag-gateway-mac': [{
+ 'gateway-mac': commands['gateway_mac'],
+ 'config': {'gateway-mac': commands['gateway_mac']}
+ }]
+ }
+ request = {'path': gateway_mac_path, 'method': gateway_mac_method, 'data': gateway_mac_payload}
+ requests.append(request)
if 'unique_ip' in commands and commands['unique_ip'] is not None:
if commands['unique_ip']['vlans'] and commands['unique_ip']['vlans'] is not None:
unique_ip_path = 'data/openconfig-mclag:mclag/vlan-interfaces/vlan-interface'
@@ -276,6 +491,13 @@ class Mclag(ConfigBase):
unique_ip_payload = self.build_create_unique_ip_payload(commands['unique_ip']['vlans'])
request = {'path': unique_ip_path, 'method': unique_ip_method, 'data': unique_ip_payload}
requests.append(request)
+ if 'peer_gateway' in commands and commands['peer_gateway'] is not None:
+ if commands['peer_gateway']['vlans'] and commands['peer_gateway']['vlans'] is not None:
+ peer_gateway_path = 'data/openconfig-mclag:mclag/vlan-ifs/vlan-if'
+ peer_gateway_method = PATCH
+ peer_gateway_payload = self.build_create_peer_gateway_payload(commands['peer_gateway']['vlans'])
+ request = {'path': peer_gateway_path, 'method': peer_gateway_method, 'data': peer_gateway_payload}
+ requests.append(request)
if 'members' in commands and commands['members'] is not None:
if commands['members']['portchannels'] and commands['members']['portchannels'] is not None:
portchannel_path = 'data/openconfig-mclag:mclag/interfaces/interface'
@@ -299,6 +521,8 @@ class Mclag(ConfigBase):
temp['peer-link'] = str(commands['peer_link'])
if 'system_mac' in commands and commands['system_mac'] is not None:
temp['openconfig-mclag:mclag-system-mac'] = str(commands['system_mac'])
+ if 'delay_restore' in commands and commands['delay_restore'] is not None:
+ temp['delay-restore'] = commands['delay_restore']
mclag_dict = {}
if temp:
domain_id = {"domain-id": want["domain_id"]}
@@ -312,8 +536,18 @@ class Mclag(ConfigBase):
def build_create_unique_ip_payload(self, commands):
payload = {"openconfig-mclag:vlan-interface": []}
- for each in commands:
- payload['openconfig-mclag:vlan-interface'].append({"name": each['vlan'], "config": {"name": each['vlan'], "unique-ip-enable": "ENABLE"}})
+ vlan_id_list = self.get_vlan_id_list(commands)
+ for vlan in vlan_id_list:
+ vlan_name = 'Vlan{0}'.format(vlan)
+ payload['openconfig-mclag:vlan-interface'].append({"name": vlan_name, "config": {"name": vlan_name, "unique-ip-enable": "ENABLE"}})
+ return payload
+
+ def build_create_peer_gateway_payload(self, commands):
+ payload = {"openconfig-mclag:vlan-if": []}
+ vlan_id_list = self.get_vlan_id_list(commands)
+ for vlan in vlan_id_list:
+ vlan_name = 'Vlan{0}'.format(vlan)
+ payload['openconfig-mclag:vlan-if'].append({"name": vlan_name, "config": {"name": vlan_name, "peer-gateway-enable": "ENABLE"}})
return payload
def build_create_portchannel_payload(self, want, commands):
@@ -321,3 +555,63 @@ class Mclag(ConfigBase):
for each in commands:
payload['openconfig-mclag:interface'].append({"name": each['lag'], "config": {"name": each['lag'], "mclag-domain-id": want['domain_id']}})
return payload
+
+ def get_vlan_range_common(self, config_vlans, match_vlans):
+ """Returns the vlan ranges present in both 'config_vlans'
+ and 'match_vlans' in vlans spec format
+ """
+ if not config_vlans:
+ return []
+
+ if not match_vlans:
+ return []
+
+ config_vlans = self.get_vlan_id_list(config_vlans)
+ match_vlans = self.get_vlan_id_list(match_vlans)
+ return self.get_vlan_range_list(list(set(config_vlans).intersection(set(match_vlans))))
+
+ def get_vlan_range_diff(self, config_vlans, match_vlans):
+ """Returns the vlan ranges present only in 'config_vlans'
+ and not in 'match_vlans' in vlans spec format
+ """
+ if not config_vlans:
+ return []
+
+ if not match_vlans:
+ return config_vlans
+
+ config_vlans = self.get_vlan_id_list(config_vlans)
+ match_vlans = self.get_vlan_id_list(match_vlans)
+ return self.get_vlan_range_list(list(set(config_vlans) - set(match_vlans)))
+
+ @staticmethod
+ def get_vlan_id_list(vlan_range_list):
+ """Returns a list of all VLAN IDs specified in VLAN range list"""
+ vlan_id_list = []
+ if vlan_range_list:
+ for vlan_range in vlan_range_list:
+ vlan_val = vlan_range['vlan']
+ if '-' in vlan_val:
+ match = re.match(r'Vlan(\d+)-(\d+)', vlan_val)
+ if match:
+ vlan_id_list.extend(range(int(match.group(1)), int(match.group(2)) + 1))
+ else:
+ # Single VLAN ID
+ match = re.match(r'Vlan(\d+)', vlan_val)
+ if match:
+ vlan_id_list.append(int(match.group(1)))
+
+ return vlan_id_list
+
+ @staticmethod
+ def get_vlan_range_list(vlan_id_list):
+ """Returns a list of VLAN ranges for given list of VLAN IDs
+ in vlans spec format"""
+ vlan_range_list = []
+
+ if vlan_id_list:
+ vlan_id_list.sort()
+ for vlan_range in get_ranges_in_list(vlan_id_list):
+ vlan_range_list.append({'vlan': 'Vlan{0}'.format('-'.join(map(str, (vlan_range[0], vlan_range[-1])[:len(vlan_range)])))})
+
+ return vlan_range_list
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/ntp/ntp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/ntp/ntp.py
index a4fdc7e0a..b48fa54f6 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/ntp/ntp.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/ntp/ntp.py
@@ -14,8 +14,6 @@ created
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
-
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -29,20 +27,30 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
get_diff,
+ get_replaced_config,
update_states,
- normalize_interface_name,
normalize_interface_name_list
)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF,
+ get_new_config,
+ get_formatted_config_diff
+)
+
from ansible.module_utils.connection import ConnectionError
PATCH = 'PATCH'
DELETE = 'DELETE'
TEST_KEYS = [
- {
- "vrf": "", "enable_ntp_auth": "", "source_interfaces": "", "trusted_keys": "",
- "servers": {"address": ""}, "ntp_keys": {"key_id": ""}
- }
+ {"servers": {"address": ""}},
+ {"ntp_keys": {"key_id": ""}}
+]
+TEST_KEYS_formatted_diff = [
+ {'__default_ops': {'__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}},
+ {"servers": {"address": "", '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+ {"ntp_keys": {"key_id": "", '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}}
]
@@ -106,6 +114,17 @@ class Ntp(ConfigBase):
if result['changed']:
result['after'] = changed_ntp_facts
+ new_config = changed_ntp_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_ntp_facts,
+ TEST_KEYS_formatted_diff)
+ result['after(generated)'] = new_config
+
+ if self._module._diff:
+ result['diff'] = get_formatted_config_diff(existing_ntp_facts,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
@@ -119,7 +138,7 @@ class Ntp(ConfigBase):
"""
want = self._module.params['config']
if want is None:
- want = []
+ want = {}
have = existing_ntp_facts
@@ -145,6 +164,10 @@ class Ntp(ConfigBase):
commands, requests = self._state_deleted(want, have)
elif state == 'merged':
commands, requests = self._state_merged(want, have)
+ elif state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have)
return commands, requests
@@ -160,6 +183,8 @@ class Ntp(ConfigBase):
commands = diff
requests = []
+
+ self.preprocess_merge_commands(commands, want)
if commands:
requests = self.get_merge_requests(commands, have)
@@ -207,6 +232,77 @@ class Ntp(ConfigBase):
return commands, requests
+ def _state_replaced(self, want, have):
+ """ The command generator when state is replaced
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ replaced_config = get_replaced_config(want, have, TEST_KEYS)
+
+ add_commands = []
+ if replaced_config:
+ self.sort_lists_in_config(replaced_config)
+ self.sort_lists_in_config(have)
+ delete_all = (replaced_config == have)
+ del_requests = self.get_delete_requests(replaced_config, delete_all)
+ requests.extend(del_requests)
+ commands.extend(update_states(replaced_config, "deleted"))
+
+ add_commands = want
+ else:
+ diff = get_diff(want, have, TEST_KEYS)
+ add_commands = diff
+
+ if add_commands:
+ self.preprocess_merge_commands(add_commands, want)
+ add_requests = self.get_merge_requests(add_commands, have)
+
+ if len(add_requests) > 0:
+ requests.extend(add_requests)
+ commands.extend(update_states(add_commands, "replaced"))
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ self.sort_lists_in_config(want)
+ self.sort_lists_in_config(have)
+
+ commands = []
+ requests = []
+
+ if have and have != want:
+ delete_all = True
+ del_requests = self.get_delete_requests(have, delete_all)
+ requests.extend(del_requests)
+ commands.extend(update_states(have, "deleted"))
+ have = []
+
+ if not have and want:
+ add_commands = want
+ add_requests = self.get_merge_requests(add_commands, have)
+
+ if len(add_requests) > 0:
+ requests.extend(add_requests)
+ commands.extend(update_states(add_commands, "overridden"))
+
+ return commands, requests
+
def validate_want(self, want, state):
if state == 'deleted':
@@ -215,7 +311,9 @@ class Ntp(ConfigBase):
key_id_config = server.get('key_id', None)
minpoll_config = server.get('minpoll', None)
maxpoll_config = server.get('maxpoll', None)
- if key_id_config or minpoll_config or maxpoll_config:
+ prefer_config = server.get('prefer', None)
+ if key_id_config or minpoll_config or maxpoll_config or \
+ prefer_config is not None:
err_msg = "NTP server parameter(s) can not be deleted."
self._module.fail_json(msg=err_msg, code=405)
@@ -247,6 +345,52 @@ class Ntp(ConfigBase):
server.pop('minpoll')
if 'maxpoll' in server and not server['maxpoll']:
server.pop('maxpoll')
+ if 'prefer' in server and server['prefer'] is None:
+ server.pop('prefer')
+
+ if state == 'replaced' or state == 'overridden':
+ enable_auth_want = want.get('enable_ntp_auth', None)
+ if enable_auth_want is None:
+ want['enable_ntp_auth'] = False
+ if 'servers' in want and want['servers'] is not None:
+ for server in want['servers']:
+ if 'prefer' in server and server['prefer'] is None:
+ server['prefer'] = False
+
+ def search_servers(self, svr_address, servers):
+
+ found_server = dict()
+ if servers is not None:
+ for server in servers:
+ if server['address'] == svr_address:
+ found_server = server
+ return found_server
+
+ def preprocess_merge_commands(self, commands, want):
+
+ if 'servers' in commands and commands['servers'] is not None:
+ for server in commands['servers']:
+ if 'minpoll' in server and 'maxpoll' not in server:
+ want_server = dict()
+ if 'servers' in want:
+ want_server = self.search_servers(server['address'], want['servers'])
+
+ if want_server:
+ server['maxpoll'] = want_server['maxpoll']
+ else:
+ err_msg = "Internal error with NTP server maxpoll configuration."
+ self._module.fail_json(msg=err_msg, code=500)
+
+ if 'maxpoll' in server and 'minpoll' not in server:
+ want_server = dict()
+ if 'servers' in want:
+ want_server = self.search_servers(server['address'], want['servers'])
+
+ if want_server:
+ server['minpoll'] = want_server['minpoll']
+ else:
+ err_msg = "Internal error with NTP server minpoll configuration."
+ self._module.fail_json(msg=err_msg, code=500)
def get_merge_requests(self, configs, have):
@@ -448,18 +592,23 @@ class Ntp(ConfigBase):
# Create URL and payload
method = DELETE
- servers_config = configs.get('servers', None)
src_intf_config = configs.get('source_interfaces', None)
vrf_config = configs.get('vrf', None)
enable_auth_config = configs.get('enable_ntp_auth', None)
trusted_key_config = configs.get('trusted_keys', None)
- if servers_config or src_intf_config or vrf_config or \
+ if src_intf_config or vrf_config or \
trusted_key_config or enable_auth_config is not None:
url = 'data/openconfig-system:system/ntp'
request = {"path": url, "method": method}
requests.append(request)
+ servers_config = configs.get('servers', None)
+ if servers_config:
+ url = 'data/openconfig-system:system/ntp/servers'
+ request = {"path": url, "method": method}
+ requests.append(request)
+
keys_config = configs.get('ntp_keys', None)
if keys_config:
url = 'data/openconfig-system:system/ntp/ntp-keys'
@@ -546,3 +695,20 @@ class Ntp(ConfigBase):
requests.append(request)
return requests
+
+ def get_server_address(self, ntp_server):
+ return ntp_server.get('address')
+
+ def get_ntp_key_id(self, ntp_key):
+ return ntp_key.get('key_id')
+
+ def sort_lists_in_config(self, config):
+
+ if 'source_interfaces' in config and config['source_interfaces'] is not None:
+ config['source_interfaces'].sort()
+ if 'servers' in config and config['servers'] is not None:
+ config['servers'].sort(key=self.get_server_address)
+ if 'trusted_keys' in config and config['trusted_keys'] is not None:
+ config['trusted_keys'].sort()
+ if 'ntp_keys' in config and config['ntp_keys'] is not None:
+ config['ntp_keys'].sort(key=self.get_ntp_key_id)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/pki/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/pki/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/pki/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/pki/pki.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/pki/pki.py
new file mode 100644
index 000000000..163e59023
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/pki/pki.py
@@ -0,0 +1,563 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell EMC
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_pki class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+ remove_empties,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import (
+ Facts,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ update_states,
+ get_diff,
+)
+
+from urllib.parse import quote
+
+
+TRUST_STORES_PATH = "data/openconfig-pki:pki/trust-stores"
+SECURITY_PROFILES_PATH = "data/openconfig-pki:pki/security-profiles"
+TRUST_STORE_PATH = "data/openconfig-pki:pki/trust-stores/trust-store"
+SECURITY_PROFILE_PATH = (
+ "data/openconfig-pki:pki/security-profiles/security-profile"
+)
+
+PATCH = "patch"
+DELETE = "delete"
+PUT = "put"
+TEST_KEYS = [
+ {"security_profiles": {"profile_name": ""}},
+ {"trust_stores": {"name": ""}},
+]
+
+
+class Pki(ConfigBase):
+ """
+ The sonic_pki class
+ """
+
+ gather_subset = [
+ "!all",
+ "!min",
+ ]
+
+ gather_network_resources = [
+ "pki",
+ ]
+
+ def get_pki_facts(self):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset, self.gather_network_resources
+ )
+ pki_facts = facts["ansible_network_resources"].get("pki")
+ if not pki_facts:
+ return {}
+ return pki_facts
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ warnings = list()
+ commands = list()
+
+ existing_pki_facts = self.get_pki_facts()
+ commands, requests = self.set_config(existing_pki_facts)
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ try:
+ edit_config(
+ self._module, to_request(self._module, requests)
+ )
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result["changed"] = True
+ result["commands"] = commands
+
+ changed_pki_facts = self.get_pki_facts()
+
+ result["before"] = existing_pki_facts
+ if result["changed"]:
+ result["after"] = changed_pki_facts
+
+ result["warnings"] = warnings
+
+ return result
+
+ def set_config(self, existing_pki_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params["config"]
+ have = existing_pki_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ state = self._module.params["state"]
+ if not want:
+ want = {}
+
+ diff = get_diff(want, have, list(TEST_KEYS))
+
+ if state == "overridden":
+ commands, requests = self._state_overridden(want, have, diff)
+ elif state == "deleted":
+ commands, requests = self._state_deleted(want, have, diff)
+ elif state == "merged":
+ commands, requests = self._state_merged(want, have, diff)
+ elif state == "replaced":
+ commands, requests = self._state_replaced(want, have)
+ return commands, requests
+
+ def _state_replaced(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ spdiff = sp_diff(want, have)
+ tsdiff = ts_diff(want, have)
+ commands = []
+ requests = []
+ have_dict = {
+ "security_profiles": {
+ sp.get("profile_name"): sp
+ for sp in (have.get("security_profiles") or [])
+ },
+ "trust_stores": {
+ ts.get("name"): ts for ts in (have.get("trust_stores") or [])
+ },
+ }
+ for ts in tsdiff:
+ requests.append(
+ {
+ "path": TRUST_STORE_PATH + "=" + ts.get("name"),
+ "method": PUT,
+ "data": mk_ts_config(ts),
+ }
+ )
+ commands.append(
+ update_states(
+ have_dict["trust_stores"][ts.get("name")], "replaced"
+ )
+ )
+ for sp in spdiff:
+ requests.append(
+ {
+ "path": SECURITY_PROFILE_PATH
+ + "="
+ + sp.get("profile_name"),
+ "method": PUT,
+ "data": mk_sp_config(sp),
+ }
+ )
+ commands.append(
+ update_states(
+ have_dict["security_profiles"][sp.get("profile_name")],
+ "replaced",
+ )
+ )
+
+ return commands, requests
+
+ def _state_overridden(self, want, have, diff):
+ """The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+
+ commands = []
+ requests = []
+ want_tss = [ts.get("name") for ts in (want.get("trust_stores") or [])]
+ want_sps = [
+ sp.get("profile_name")
+ for sp in (want.get("security_profiles") or [])
+ ]
+ have_tss = [ts.get("name") for ts in (have.get("trust_stores") or [])]
+ have_sps = [
+ sp.get("profile_name")
+ for sp in (have.get("security_profiles") or [])
+ ]
+
+ have_dict = {
+ "security_profiles": {
+ sp.get("profile_name"): sp
+ for sp in (have.get("security_profiles") or [])
+ },
+ "trust_stores": {
+ ts.get("name"): ts for ts in (have.get("trust_stores") or [])
+ },
+ }
+ used_ts = []
+ for sp in have_sps:
+ if sp not in want_sps:
+ requests.append(
+ {
+ "path": SECURITY_PROFILE_PATH + "=" + sp,
+ "method": DELETE,
+ }
+ )
+ commands.append(
+ update_states(
+ have_dict["security_profiles"][sp], "deleted"
+ )
+ )
+ else:
+ ts_name = have_dict.get("security_profiles", {}).get(sp, {}).get("trust_store")
+ if ts_name and ts_name not in used_ts:
+ used_ts.append(ts_name)
+
+ for ts in have_tss:
+ if ts not in want_tss and ts not in used_ts:
+ requests.append(
+ {"path": TRUST_STORE_PATH + "=" + ts, "method": DELETE}
+ )
+ commands.append(
+ update_states(have_dict["trust_stores"][ts], "deleted")
+ )
+
+ for ts in want.get("trust_stores") or []:
+ if ts != have_dict["trust_stores"].get(ts.get("name")):
+ requests.append(
+ {
+ "path": TRUST_STORE_PATH + "=" + ts.get("name"),
+ "method": PUT,
+ "data": mk_ts_config(ts),
+ }
+ )
+ commands.append(update_states(ts, "overridden"))
+ for sp in want.get("security_profiles") or []:
+ if sp != have_dict["security_profiles"].get(
+ sp.get("profile_name")
+ ):
+ requests.append(
+ {
+ "path": SECURITY_PROFILE_PATH
+ + "="
+ + sp.get("profile_name"),
+ "method": PUT,
+ "data": mk_sp_config(sp),
+ }
+ )
+ commands.append(update_states(sp, "overridden"))
+
+ return commands, requests
+
+ def _state_merged(self, want, have, diff):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = diff or {}
+ requests = []
+
+ for ts in commands.get("trust_stores") or []:
+ requests.append(
+ {
+ "path": TRUST_STORE_PATH,
+ "method": PATCH,
+ "data": mk_ts_config(ts),
+ }
+ )
+
+ for sp in commands.get("security_profiles") or []:
+ requests.append(
+ {
+ "path": SECURITY_PROFILE_PATH,
+ "method": PATCH,
+ "data": mk_sp_config(sp),
+ }
+ )
+
+ if commands and requests:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have, diff):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ requests = []
+ current_ts = [
+ ts.get("name")
+ for ts in (have.get("trust_stores") or [])
+ if ts.get("name")
+ ]
+ current_sp = [
+ sp.get("profile_name")
+ for sp in (have.get("security_profiles") or [])
+ if sp.get("profile_name")
+ ]
+ if not want:
+ commands = have
+ for sp in current_sp:
+ requests.append(
+ {
+ "path": SECURITY_PROFILE_PATH + "=" + sp,
+ "method": DELETE,
+ }
+ )
+ for ts in current_ts:
+ requests.append(
+ {"path": TRUST_STORE_PATH + "=" + ts, "method": DELETE}
+ )
+ else:
+ commands = remove_empties(want)
+
+ for sp in commands.get("security_profiles") or []:
+ if sp.get("profile_name") in current_sp:
+ requests.extend(mk_sp_delete(sp, have))
+ for ts in commands.get("trust_stores") or []:
+ if ts.get("name") in current_ts:
+ requests.extend(mk_ts_delete(ts, have))
+
+ if commands and requests:
+ commands = update_states([commands], "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+
+def sp_diff(want, have):
+ hsps = {}
+ wsps = {}
+ dsps = []
+ for hsp in have.get("security_profiles") or []:
+ hsps[hsp.get("profile_name")] = hsp
+ for wsp in want.get("security_profiles") or []:
+ wsps[wsp.get("profile_name")] = wsp
+
+ for spn, sp in wsps.items():
+ dsp = dict(hsps.get(spn))
+ # Pop each leaf from dsp that is not in sp
+ for k, v in dsp.items():
+ if not isinstance(dsp.get(k), list) and not isinstance(
+ dsp.get(k), dict
+ ):
+ if k not in sp:
+ dsp.pop(k)
+ for k, v in sp.items():
+ if not isinstance(dsp.get(k), list) and not isinstance(
+ dsp.get(k), dict
+ ):
+ if dsp.get(k) != v:
+ dsp[k] = v
+ else:
+ if v is not None:
+ dsp[k] = v
+ if dsp != hsps.get(spn):
+ dsps.append(dsp)
+ return dsps
+
+
+def ts_diff(want, have):
+ htss = {}
+ wtss = {}
+ dtss = []
+ for hts in have.get("trust_stores") or []:
+ htss[hts.get("name")] = hts
+ for wts in want.get("trust_stores") or []:
+ wtss[wts.get("name")] = wts
+
+ for tsn, ts in wtss.items():
+ dts = dict(htss.get(tsn))
+ for k, v in ts.items():
+ if not isinstance(dts.get(k), list) and not isinstance(
+ dts.get(k), dict
+ ):
+ if dts.get(k) != v:
+ dts[k] = v
+ else:
+ if v is not None:
+ dts[k] = v
+ if dts != htss.get(tsn):
+ dtss.append(dts)
+ return dtss
+
+
+def mk_sp_config(indata):
+ outdata = {
+ k.replace("_", "-"): v for k, v in indata.items() if v is not None
+ }
+ output = {
+ "openconfig-pki:security-profile": [
+ {"profile-name": outdata.get("profile-name"), "config": outdata}
+ ]
+ }
+ return output
+
+
+def mk_ts_config(indata):
+ outdata = {
+ k.replace("_", "-"): v for k, v in indata.items() if v is not None
+ }
+ output = {
+ "openconfig-pki:trust-store": [
+ {"name": outdata.get("name"), "config": outdata}
+ ]
+ }
+ return output
+
+
+def mk_sp_delete(want_sp, have):
+ requests = []
+ cur_sp = None
+ del_sp = {}
+ for csp in have.get("security_profiles") or []:
+ if csp.get("profile_name") == want_sp.get("profile_name"):
+ cur_sp = csp
+ break
+ if cur_sp:
+ for k, v in want_sp.items():
+ if v is not None and k != "profile_name":
+ if v == cur_sp.get(k) or isinstance(v, list):
+ del_sp[k] = v
+ if len(del_sp) == 0 and len(want_sp) <= 1:
+ requests = [
+ {
+ "path": SECURITY_PROFILE_PATH
+ + "="
+ + want_sp.get("profile_name"),
+ "method": DELETE,
+ }
+ ]
+ else:
+ for k, v in del_sp.items():
+ if isinstance(v, list):
+ for li in v:
+ if li in (cur_sp.get(k) or []):
+ requests.append(
+ {
+ "path": SECURITY_PROFILE_PATH
+ + "="
+ + want_sp.get("profile_name")
+ + "/config/"
+ + k.replace("_", "-")
+ + "="
+ + quote(li, safe=""),
+ "method": DELETE,
+ }
+ )
+ else:
+ requests.append(
+ {
+ "path": SECURITY_PROFILE_PATH
+ + "="
+ + want_sp.get("profile_name")
+ + "/config/"
+ + k.replace("_", "-"),
+ "method": DELETE,
+ }
+ )
+ return requests
+
+
+def mk_ts_delete(want_ts, have):
+ requests = []
+ cur_ts = None
+ del_ts = {}
+ for cts in have.get("trust_stores") or []:
+ if cts.get("name") == want_ts.get("name"):
+ cur_ts = cts
+ break
+ if cur_ts:
+ for k, v in want_ts.items():
+ if v is not None and k != "name":
+ if v == cur_ts.get(k) or isinstance(v, list):
+ del_ts[k] = v
+ if len(del_ts) == 0 and len(want_ts) <= 1:
+ requests = [
+ {
+ "path": TRUST_STORE_PATH + "=" + want_ts.get("name"),
+ "method": DELETE,
+ }
+ ]
+ else:
+ for k, v in del_ts.items():
+ if isinstance(v, list):
+ for li in v:
+ if li in (cur_ts.get(k) or []):
+ requests.append(
+ {
+ "path": TRUST_STORE_PATH
+ + "="
+ + want_ts.get("name")
+ + "/config/"
+ + k.replace("_", "-")
+ + "="
+ + quote(li, safe=""),
+ "method": DELETE,
+ }
+ )
+ else:
+ requests.append(
+ {
+ "path": TRUST_STORE_PATH
+ + "="
+ + want_ts.get("name")
+ + "/config/"
+ + k.replace("_", "-"),
+ "method": DELETE,
+ }
+ )
+ return requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/port_breakout/port_breakout.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/port_breakout/port_breakout.py
index 371019d04..654e34dee 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/port_breakout/port_breakout.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/port_breakout/port_breakout.py
@@ -17,6 +17,7 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.c
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
to_list,
+ search_obj_in_list
)
from ansible.module_utils.connection import ConnectionError
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
@@ -33,7 +34,6 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
PATCH = 'patch'
DELETE = 'delete'
-POST = 'post'
class Port_breakout(ConfigBase):
@@ -152,6 +152,52 @@ class Port_breakout(ConfigBase):
return commands, requests
+ def _state_replaced(self, want, have, diff):
+ """ The command generator when state is replaced
+
+ :param want: the additive configuration as a dictionary
+ :param obj_in_have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = diff
+ requests = self.get_modify_port_breakout_requests(commands, have)
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "replaced")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_overridden(self, want, have, diff):
+ """ The command generator when state is merged
+
+ :param want: the additive configuration as a dictionary
+ :param obj_in_have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = []
+ requests = []
+
+ # Delete port-breakout configuration for interfaces that are not specified
+ for cfg in have:
+ if not search_obj_in_list(cfg['name'], want, 'name'):
+ commands.append(cfg)
+ requests.append(self.get_delete_single_port_breakout(cfg['name'], cfg))
+
+ if commands:
+ commands = update_states(commands, "deleted")
+
+ add_requests = self.get_modify_port_breakout_requests(diff, have)
+ if len(add_requests) > 0:
+ commands.extend(update_states(diff, "overridden"))
+ requests.extend(add_requests)
+
+ return commands, requests
+
def _state_deleted(self, want, have, diff):
""" The command generator when state is deleted
@@ -161,7 +207,7 @@ class Port_breakout(ConfigBase):
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
- # if want is none, then delete all the port_breakouti except admin
+ # if want is none, then delete all the port_breakout except admin
if not want:
commands = have
else:
@@ -215,27 +261,6 @@ class Port_breakout(ConfigBase):
requests.append(req)
return requests
- def get_default_port_breakout_modes(self):
- def_port_breakout_modes = []
- request = [{"path": "operations/sonic-port-breakout:breakout_capabilities", "method": POST}]
- try:
- response = edit_config(self._module, to_request(self._module, request))
- except ConnectionError as exc:
- self._module.fail_json(msg=str(exc), code=exc.code)
-
- raw_port_breakout_list = []
- if "sonic-port-breakout:output" in response[0][1]:
- raw_port_breakout_list = response[0][1].get("sonic-port-breakout:output", {}).get('caps', [])
-
- for port_breakout in raw_port_breakout_list:
- name = port_breakout.get('port', None)
- mode = port_breakout.get('defmode', None)
- if name and mode:
- if '[' in mode:
- mode = mode[:mode.index('[')]
- def_port_breakout_modes.append({'name': name, 'mode': mode})
- return def_port_breakout_modes
-
def get_delete_port_breakout_requests(self, commands, have):
requests = []
if not commands:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/port_group/port_group.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/port_group/port_group.py
new file mode 100644
index 000000000..37281403e
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/port_group/port_group.py
@@ -0,0 +1,380 @@
+#
+# -*- coding: utf-8 -*-
+# © Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_port_group class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+"""
+The use of natsort causes sanity error due to it is not available in python version currently used.
+When natsort becomes available, the code here and below using it will be applied.
+from natsort import (
+ natsorted,
+ ns
+)
+"""
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import (
+ Facts,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_diff,
+ update_states,
+ remove_empties_from_list
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ get_new_config,
+ get_formatted_config_diff
+)
+from ansible.module_utils.connection import ConnectionError
+
+GET = "get"
+PATCH = 'patch'
+DELETE = 'delete'
+url = 'data/openconfig-port-group:port-groups/port-group'
+
+TEST_KEYS = [
+ {
+ 'config': {'id': ''}
+ }
+]
+TEST_KEYS_formatted_diff = [
+ {'config': {'id': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}}
+]
+
+
+class Port_group(ConfigBase):
+ """
+ The sonic_port_group class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'port_group',
+ ]
+
+ pg_default_speeds_ready = False
+ pg_default_speeds = []
+
+ def __init__(self, module):
+ super(Port_group, self).__init__(module)
+
+ if not Port_group.pg_default_speeds_ready:
+ Port_group.pg_default_speeds = self.get_port_group_default_speed()
+ Port_group.pg_default_speeds_ready = True
+
+ def get_port_group_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ port_group_facts = facts['ansible_network_resources'].get('port_group')
+ if not port_group_facts:
+ return []
+ return port_group_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+
+ existing_port_group_facts = self.get_port_group_facts()
+ commands, requests = self.set_config(existing_port_group_facts)
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_port_group_facts = self.get_port_group_facts()
+
+ result['before'] = existing_port_group_facts
+ if result['changed']:
+ result['after'] = changed_port_group_facts
+
+ new_config = changed_port_group_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_port_group_facts,
+ TEST_KEYS_formatted_diff)
+ # See the above comment about natsort module
+ # new_config = natsorted(new_config, key=lambda x: x['id'])
+ result['after(generated)'] = new_config
+
+ if self._module._diff:
+ result['diff'] = get_formatted_config_diff(existing_port_group_facts,
+ new_config,
+ self._module._verbosity)
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_port_group_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ have = existing_port_group_facts
+
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+
+ state = self._module.params['state']
+
+ diff = get_diff(want, have, TEST_KEYS)
+
+ tmp_want = remove_empties_from_list(want)
+ new_want = self.remove_empty_dict_from_list(tmp_want)
+
+ new_diff = self.remove_empty_dict_from_list(diff)
+
+ if state == 'overridden':
+ commands, requests = self._state_overridden(new_want, have, new_diff)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(new_want, have, new_diff)
+ elif state == 'merged':
+ commands, requests = self._state_merged(new_want, have, new_diff)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(new_want, have, new_diff)
+
+ return commands, requests
+
+ def _state_replaced(self, want, have, diff):
+ """ The command generator when state is replaced
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = diff
+ requests = []
+ if commands:
+ requests = self.build_merge_requests(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_overridden(self, want, have, diff):
+ """ The command generator when state is overridden
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ new_want = self.patch_want_with_default(want)
+ commands = get_diff(new_want, have, TEST_KEYS)
+ requests = []
+ if commands:
+ requests = self.build_merge_requests(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_merged(self, want, have, diff):
+ """ The command generator when state is merged
+
+ :param want: the additive configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = diff
+ requests = []
+ if commands:
+ requests = self.build_merge_requests(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have, diff):
+ """ The command generator when state is deleted
+
+ :param want: the objects from which the configuration should be removed
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ # if want is none, then delete all the port groups
+
+ if not want:
+ tmp_commands = have
+ else:
+ tmp_commands = want
+ tmp_commands = self.preprocess_delete_commands(tmp_commands, have)
+
+ commands = get_diff(tmp_commands, Port_group.pg_default_speeds, TEST_KEYS)
+
+ requests = []
+ if commands:
+ requests = self.build_delete_requests(commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def search_port_groups(self, id, pgs):
+
+ found_pg = dict()
+ if pgs is not None:
+ for pg in pgs:
+ if pg['id'] == id:
+ found_pg = pg
+ return found_pg
+
+ def preprocess_delete_commands(self, commands, have):
+ new_commands = []
+ for cmd in commands:
+ pg_id = cmd['id']
+ pg = self.search_port_groups(pg_id, have)
+ if pg:
+ new_cmd = {'id': pg_id, 'speed': pg['speed']}
+ new_commands.append(new_cmd)
+
+ return new_commands
+
+ def remove_empty_dict_from_list(self, dict_list):
+ new_dict_list = []
+ if dict_list:
+ for dictt in dict_list:
+ if dictt:
+ new_dict_list.append(dictt)
+
+ return new_dict_list
+
+ def build_delete_requests(self, confs):
+ requests = []
+
+ for conf in confs:
+ pg_id = conf['id']
+ method = DELETE
+ pg_url = (url + '=%s/config/speed') % (pg_id)
+ request = {"path": pg_url, "method": method}
+ requests.append(request)
+
+ return requests
+
+ def build_merge_requests(self, confs):
+ requests = []
+ pgs = []
+ for conf in confs:
+ pg_id = conf['id']
+ if 'speed' in conf:
+ pg_conf = {'id': pg_id, 'speed': 'openconfig-if-ethernet:' + conf['speed']}
+ pg = {'id': pg_id, 'config': pg_conf}
+ pgs.append(pg)
+
+ if pgs:
+ payload = {"openconfig-port-group:port-group": pgs}
+ method = PATCH
+ pg_url = url
+ request = {"path": pg_url, "method": method, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def patch_want_with_default(self, want):
+ new_want = list()
+ for dpg in Port_group.pg_default_speeds:
+ pg_id = dpg['id']
+ pg = self.search_port_groups(pg_id, want)
+ if pg:
+ new_pg = {'id': pg_id, 'speed': pg['speed']}
+ else:
+ new_pg = {'id': pg_id, 'speed': dpg['speed']}
+
+ new_want.append(new_pg)
+ return new_want
+
+ def get_port_group_default_speed(self):
+ """Get all the port group default speeds"""
+
+ pgs_request = [{"path": "data/openconfig-port-group:port-groups/port-group", "method": GET}]
+ try:
+ pgs_response = edit_config(self._module, to_request(self._module, pgs_request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ pgs_config = []
+ if "openconfig-port-group:port-group" in pgs_response[0][1]:
+ pgs_config = pgs_response[0][1].get("openconfig-port-group:port-group", [])
+
+ pgs_dft_speeds = []
+ for pg_config in pgs_config:
+ pg_state = dict()
+ if 'state' in pg_config:
+ pg_state['id'] = pg_config['id']
+ dft_speed_str = pg_config['state'].get('default-speed', None)
+ if dft_speed_str:
+ pg_state['speed'] = dft_speed_str.split(":", 1)[-1]
+ pgs_dft_speeds.append(pg_state)
+
+ return pgs_dft_speeds
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/prefix_lists/prefix_lists.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/prefix_lists/prefix_lists.py
index d5c36d3e2..f4dc71214 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/prefix_lists/prefix_lists.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/prefix_lists/prefix_lists.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2019 Red Hat
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -40,7 +40,7 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
TEST_KEYS = [
{"config": {"afi": "", "name": ""}},
- {"prefixes": {"action": "", "ge": "", "le": "", "prefix": "", "sequence": ""}}
+ {"prefixes": {"ge": "", "le": "", "prefix": "", "sequence": ""}}
]
DELETE = "delete"
@@ -149,6 +149,10 @@ openconfig-routing-policy-ext:extended-prefixes/extended-prefix={},{},{}'
commands, requests = self._state_deleted(want, have)
elif state == 'merged':
commands, requests = self._state_merged(diff)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(diff)
+ elif state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
ret_commands = commands
return ret_commands, requests
@@ -188,6 +192,51 @@ openconfig-routing-policy-ext:extended-prefixes/extended-prefix={},{},{}'
commands = []
return commands, requests
+ def _state_replaced(self, diff):
+ """ The command generator when state is replaced
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = diff
+ requests = self.get_modify_prefix_lists_requests(commands)
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "replaced")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ self.sort_lists_in_config(want)
+ self.sort_lists_in_config(have)
+
+ if have and have != want:
+ del_requests = self.get_delete_all_prefix_list_cfg_requests()
+ requests.extend(del_requests)
+ commands.extend(update_states(have, "deleted"))
+ have = []
+
+ if not have and want:
+ mod_commands = want
+ mod_requests = self.get_modify_prefix_lists_requests(mod_commands)
+
+ if len(mod_requests) > 0:
+ requests.extend(mod_requests)
+ commands.extend(update_states(mod_commands, "overridden"))
+
+ return commands, requests
+
def get_modify_prefix_lists_requests(self, commands):
'''Traverse the input list of configuration "modify" commands obtained
from parsing the input playbook parameters. For each command,
@@ -456,3 +505,13 @@ openconfig-routing-policy-ext:extended-prefixes/extended-prefix={},{},{}'
prefix_net['prefixlen'] = int(prefix_val.split("/")[1])
return prefix_net
+
+ def sort_lists_in_config(self, config):
+ if config:
+ config.sort(key=self.get_name)
+ for cfg in config:
+ if 'prefixes' in cfg and cfg['prefixes']:
+ cfg['prefixes'].sort(key=lambda x: (x['sequence'], x['action'], x['prefix']))
+
+ def get_name(self, name):
+ return name.get('name')
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/radius_server/radius_server.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/radius_server/radius_server.py
index dfa65482f..264ffa014 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/radius_server/radius_server.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/radius_server/radius_server.py
@@ -27,14 +27,25 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
update_states,
get_diff,
+ get_replaced_config,
normalize_interface_name,
)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF,
+ get_new_config,
+ get_formatted_config_diff
+)
PATCH = 'patch'
DELETE = 'delete'
TEST_KEYS = [
{'host': {'name': ''}},
]
+TEST_KEYS_formatted_diff = [
+ {'__default_ops': {'__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}},
+ {'host': {'name': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+]
class Radius_server(ConfigBase):
@@ -91,6 +102,17 @@ class Radius_server(ConfigBase):
if result['changed']:
result['after'] = changed_radius_server_facts
+ new_config = changed_radius_server_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_radius_server_facts,
+ TEST_KEYS_formatted_diff)
+ result['after(generated)'] = new_config
+
+ if self._module._diff:
+ result['diff'] = get_formatted_config_diff(existing_radius_server_facts,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
@@ -180,6 +202,67 @@ class Radius_server(ConfigBase):
return commands, requests
+ def _state_replaced(self, want, have, diff):
+ """ The command generator when state is replaced
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ replaced_config = get_replaced_config(want, have, TEST_KEYS)
+
+ add_commands = []
+ if replaced_config:
+ del_requests = self.get_delete_radius_server_requests(replaced_config, have)
+ requests.extend(del_requests)
+ commands.extend(update_states(replaced_config, "deleted"))
+ add_commands = want
+ else:
+ add_commands = diff
+
+ if add_commands:
+ add_requests = self.get_modify_radius_server_requests(add_commands, have)
+ if len(add_requests) > 0:
+ requests.extend(add_requests)
+ commands.extend(update_states(add_commands, "replaced"))
+
+ return commands, requests
+
+ def _state_overridden(self, want, have, diff):
+ """ The command generator when state is overridden
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ r_diff = get_diff(have, want, TEST_KEYS)
+ if have and (diff or r_diff):
+ del_requests = self.get_delete_radius_server_requests(have, have)
+ requests.extend(del_requests)
+ commands.extend(update_states(have, "deleted"))
+ have = []
+
+ if not have and want:
+ want_commands = want
+ want_requests = self.get_modify_radius_server_requests(want_commands, have)
+
+ if len(want_requests) > 0:
+ requests.extend(want_requests)
+ commands.extend(update_states(want_commands, "overridden"))
+
+ return commands, requests
+
def get_radius_global_payload(self, conf):
payload = {}
global_cfg = {}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/route_maps/route_maps.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/route_maps/route_maps.py
new file mode 100644
index 000000000..0b40c30f2
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/route_maps/route_maps.py
@@ -0,0 +1,2354 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_route_maps class
+The code in this file compares the current configuration (as a dict)
+to the configuration provided (as a dict) based on the contents of the
+currently executing playbook. The result of the comparison and the end state
+requested by the executing playbook are used to to determine the command set
+necessary to bring the current configuration to it's desired end-state.
+The resulting commands are then transmitted to the target device.
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+ validate_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts \
+ import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils \
+ import (
+ get_diff,
+ update_states,
+ remove_empties_from_list,
+ get_normalize_interface_name,
+ check_required
+ )
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+
+
+TEST_KEYS = [
+ {"config": {"map_name": "", "sequence_num": ""}}
+]
+
+DELETE = "delete"
+PATCH = "patch"
+
+
+class Route_maps(ConfigBase):
+ """
+ The sonic_route_maps class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'route_maps',
+ ]
+
+ route_maps_uri = 'data/openconfig-routing-policy:routing-policy/policy-definitions'
+ route_map_uri = route_maps_uri + '/policy-definition={0}'
+ route_map_stmt_uri = route_map_uri + '/statements/statement={1}'
+ route_map_stmt_base_uri = route_map_uri + '/statements/statement={1}/'
+ route_maps_data_path = 'openconfig-routing-policy:policy-definitions'
+
+ set_community_rest_names = {
+ 'additive': 'openconfig-routing-policy-ext:ADDITIVE',
+ 'local_as': 'openconfig-bgp-types:NO_EXPORT_SUBCONFED',
+ 'no_advertise': 'openconfig-bgp-types:NO_ADVERTISE',
+ 'no_export': 'openconfig-bgp-types:NO_EXPORT',
+ 'no_peer': 'openconfig-bgp-types:NOPEER',
+ 'none': 'openconfig-bgp-types:NONE'
+ }
+
+ set_extcomm_rest_names = {
+ 'rt': 'route-target:',
+ 'soo': 'route-origin:'
+ }
+
+ def __init__(self, module):
+ super(Route_maps, self).__init__(module)
+
+ def get_route_maps_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset,
+ self.gather_network_resources)
+ route_maps_facts = facts['ansible_network_resources'].get('route_maps')
+ if not route_maps_facts:
+ return []
+ return route_maps_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+
+ existing_route_maps_facts = self.get_route_maps_facts()
+ commands, requests = self.set_config(existing_route_maps_facts)
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.errno)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_route_maps_facts = self.get_route_maps_facts()
+
+ result['before'] = existing_route_maps_facts
+ if result['changed']:
+ result['after'] = changed_route_maps_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_route_maps_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ if want:
+ want = self.validate_and_normalize_config(want)
+ else:
+ want = []
+
+ have = existing_route_maps_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ state = self._module.params['state']
+ if state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'merged':
+ commands, requests = self._state_merged(want, have)
+ elif state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have)
+ return commands, requests
+
+ def _state_replaced(self, want, have):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ # Delete replaced groupings
+ commands = deepcopy(want)
+ requests = self.get_delete_replaced_groupings(commands, have)
+ if not requests:
+ commands = []
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+
+ if requests:
+ modify_have = []
+ else:
+ modify_have = have
+
+ # Apply the commands from the playbook
+ diff = get_diff(want, modify_have, TEST_KEYS)
+ merged_commands = diff
+
+ replaced_requests = self.get_modify_route_maps_requests(merged_commands, want, modify_have)
+ requests.extend(replaced_requests)
+ if merged_commands and len(replaced_requests) > 0:
+ merged_commands = update_states(merged_commands, "replaced")
+ commands.extend(merged_commands)
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ if not want:
+ return commands, requests
+
+ # Determine if there is any configuration specified in the playbook
+ # that is not contained in the current configuration.
+ diff_requested = get_diff(want, have, TEST_KEYS)
+
+ # Determine if there is anything already configured that is not
+ # specified in the playbook.
+ diff_unwanted = get_diff(have, want, TEST_KEYS)
+
+ # Idempotency check: If the configuration already matches the
+ # requested configuration with no extra attributes, no
+ # commands should be executed on the device.
+ if not diff_requested and not diff_unwanted:
+ return commands, requests
+
+ # Delete all current route map configuration
+ commands = have
+ requests = self.get_delete_all_route_map_cfg_request()
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+
+ # Apply the commands from the playbook
+ merged_commands = want
+ overridden_requests = self.get_modify_route_maps_requests(merged_commands, want, [])
+ requests.extend(overridden_requests)
+ if merged_commands and len(overridden_requests) > 0:
+ merged_commands = update_states(merged_commands, "overridden")
+ commands.extend(merged_commands)
+ return commands, requests
+
+ def _state_merged(self, want, have):
+ """ The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ diff = get_diff(want, have, TEST_KEYS)
+ commands = diff
+ requests = self.get_modify_route_maps_requests(commands, want, have)
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ requests = []
+ if not have or have == []:
+ commands = []
+ elif not want or want == []:
+ commands = have
+ requests = self.get_delete_all_route_map_cfg_request()
+ else:
+ commands = want
+ requests = self.get_delete_route_maps_requests(have, commands)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def get_modify_route_maps_requests(self, commands, want, have):
+ '''Traverse the input list of configuration "modify" commands
+ obtained from parsing the input playbook parameters. For each
+ command, create a route map configuration REST API to modify the route
+ map specified by the current command.'''
+
+ requests = []
+ if not commands:
+ return requests
+
+ # Create URL and payload
+ route_maps_payload_list = []
+ route_maps_payload_dict = {'policy-definition': route_maps_payload_list}
+ for command in commands:
+ if command.get('action') is None:
+ self.insert_route_map_cmd_action(command, want)
+ route_map_payload = self.get_modify_single_route_map_request(command, have)
+ if route_map_payload:
+ route_maps_payload_list.append(route_map_payload)
+
+ # Note: This is consistent with current CLI behavior, but should be
+ # revisited if and when the SONiC REST implementation is enhanced
+ # for the "match peer" attribute.
+ self.route_map_remove_configured_match_peer(route_map_payload, have, requests)
+
+ route_maps_data = {self.route_maps_data_path: route_maps_payload_dict}
+ request = {'path': self.route_maps_uri, 'method': PATCH, 'data': route_maps_data}
+ requests.append(request)
+ return requests
+
+ def insert_route_map_cmd_action(self, command, want):
+ '''Insert the "action" value into the specified "command" if it is not
+ already present. This dictionary member will not be present in the
+ command obtained from the "diff" utility if it is unchanged from its
+ currently configured value because it is not a "difference" in the
+ configuration requested by the playbook versus the current
+ configuration. It is, however, needed in order to create the
+ appropriate REST API for modifying other attributes in the route map.'''
+
+ conf_map_name = command.get('map_name', None)
+ conf_seq_num = command.get('sequence_num', None)
+ if not conf_map_name or not conf_seq_num:
+ return
+
+ conf_action = command.get('action', None)
+ if conf_action:
+ return
+
+ # Find the corresponding route map statement in the "want" dict
+ # list and insert it into the current "command" dict.
+ matching_map_in_want = self.get_matching_map(conf_map_name, conf_seq_num, want)
+ if matching_map_in_want:
+ conf_action = matching_map_in_want.get('action')
+ if conf_action is not None:
+ command['action'] = conf_action
+
+ def get_modify_single_route_map_request(self, command, have):
+ '''Create and return the appropriate set of route map REST API attributes
+ to modify the route map configuration specified by the current "command".'''
+
+ request = {}
+ if not command:
+ return request
+
+ conf_map_name = command.get('map_name', None)
+ conf_action = command.get('action', None)
+ conf_seq_num = command.get('sequence_num', None)
+ if not conf_map_name or not conf_action or not conf_seq_num:
+ return request
+
+ req_seq_num = str(conf_seq_num)
+
+ if conf_action == 'permit':
+ req_action = 'ACCEPT_ROUTE'
+ elif conf_action == 'deny':
+ req_action = 'REJECT_ROUTE'
+ else:
+ return request
+
+ # Create a "blank" template for the request
+ route_map_request = {
+ 'name': conf_map_name,
+ 'config': {'name': conf_map_name},
+ 'statements': {
+ 'statement': [
+ {
+ 'name': req_seq_num,
+ 'config': {
+ 'name': req_seq_num
+ },
+ 'actions': {
+ 'config': {
+ 'policy-result': req_action
+ }
+ }
+ }
+ ]
+ }
+ }
+
+ route_map_statement = route_map_request['statements']['statement'][0]
+
+ self.get_route_map_modify_match_attr(command, route_map_statement)
+ self.get_route_map_modify_set_attr(command, route_map_statement, have)
+ self.get_route_map_modify_call_attr(command, route_map_statement)
+
+ return route_map_request
+
+ def get_route_map_modify_match_attr(self, command, route_map_statement):
+ '''In the dict specified by the input route_map_statement paramenter,
+ provide REST API definitions of all "match" attributes contained in the
+ user input command dict specified by the "command" input parameter
+ to this function.'''
+
+ match_top = command.get('match')
+ if not match_top:
+ return
+
+ route_map_statement['conditions'] = {}
+
+ #
+ # Handle configuration for BGP policy "match" conditions
+ # ------------------------------------------------------
+ route_map_statement['conditions']['openconfig-bgp-policy:bgp-conditions'] = {}
+ route_map_match_bgp_policy = \
+ route_map_statement['conditions']['openconfig-bgp-policy:bgp-conditions']
+
+ # Handle match as_path
+ if match_top.get('as_path'):
+ route_map_match_bgp_policy['match-as-path-set'] = {
+ 'config': {
+ 'as-path-set': match_top['as_path'],
+ 'match-set-options': 'ANY'
+ }
+ }
+ # Handle match evpn
+ if match_top.get('evpn'):
+ route_map_match_bgp_policy['openconfig-policy-ext:match-evpn-set'] = \
+ {'config': {}}
+ route_map_match_bgp_evpn = \
+ route_map_match_bgp_policy[
+ 'openconfig-policy-ext:match-evpn-set']['config']
+ if match_top['evpn'].get('default_route') is not None:
+ boolval = self.yaml_bool_to_python_bool(match_top['evpn']['default_route'])
+ route_map_match_bgp_evpn['default-type5-route'] = boolval
+ if match_top['evpn'].get('route_type'):
+ route_type_rest_name = ('openconfig-bgp-policy-ext:' +
+ match_top['evpn']['route_type'].upper())
+ route_map_match_bgp_evpn['route-type'] = route_type_rest_name
+ if match_top['evpn'].get('vni'):
+ route_map_match_bgp_evpn['vni-number'] = match_top['evpn']['vni']
+ if not route_map_match_bgp_evpn:
+ route_map_match_bgp_policy.pop('openconfig-policy-ext:match-evpn-set')
+
+ # Handle BGP policy match configuration under the "config" dictionary
+ route_map_match_bgp_policy['config'] = {}
+ if match_top.get('local_preference'):
+ route_map_match_bgp_policy['config']['local-pref-eq'] = \
+ match_top['local_preference']
+ if match_top.get('metric'):
+ route_map_match_bgp_policy['config']['med-eq'] = match_top['metric']
+ if match_top.get('origin'):
+ route_map_match_bgp_policy['config']['origin-eq'] = match_top['origin'].upper()
+ if match_top.get('community'):
+ route_map_match_bgp_policy['config']['community-set'] = match_top['community']
+ if match_top.get('ext_comm'):
+ route_map_match_bgp_policy['config']['ext-community-set'] = match_top['ext_comm']
+ if match_top.get('ip') and match_top['ip'].get('next_hop'):
+ route_map_match_bgp_policy[
+ 'config']['openconfig-bgp-policy-ext:next-hop-set'] = match_top['ip']['next_hop']
+ if not route_map_match_bgp_policy['config']:
+ route_map_match_bgp_policy.pop('config')
+
+ if not route_map_match_bgp_policy:
+ route_map_statement['conditions'].pop('openconfig-bgp-policy:bgp-conditions')
+
+ # Handle match interface
+ if match_top.get('interface'):
+ route_map_statement['conditions']['match-interface'] = {
+ 'config': {'interface': match_top['interface']}
+ }
+
+ # Handle match IP address/prefix
+ if match_top.get('ip') and match_top['ip'].get('address'):
+ route_map_statement['conditions']['match-prefix-set'] = {
+ 'config': {
+ 'prefix-set': match_top['ip']['address'],
+ 'match-set-options': 'ANY'
+ }
+ }
+
+ # Handle match IPv6 address/prefix
+ if match_top.get('ipv6') and match_top['ipv6'].get('address'):
+ if not route_map_statement['conditions'].get('match-prefix-set'):
+ route_map_statement['conditions']['match-prefix-set'] = {
+ 'config': {
+ 'openconfig-routing-policy-ext:ipv6-prefix-set': match_top[
+ 'ipv6']['address'], 'match-set-options': 'ANY'
+ }
+ }
+ else:
+ route_map_statement[
+ 'conditions']['match-prefix-set']['config'][
+ 'openconfig-routing-policy-ext:ipv6-prefix-set'] = \
+ match_top['ipv6']['address']
+
+ # Handle match peer
+ if match_top.get('peer'):
+ peer_list = list(match_top['peer'].values())
+ route_map_statement['conditions']['match-neighbor-set'] = {
+ 'config': {
+ 'openconfig-routing-policy-ext:address': peer_list
+ }
+ }
+
+ # Handle match source protocol
+ if match_top.get('source_protocol'):
+ rest_protocol_name = ''
+ if match_top['source_protocol'] in ('bgp', 'ospf', 'static'):
+ rest_protocol_name = ('openconfig-policy-types:' +
+ match_top['source_protocol'].upper())
+ elif match_top['source_protocol'] == 'connected':
+ rest_protocol_name = 'openconfig-policy-types:DIRECTLY_CONNECTED'
+
+ route_map_statement['conditions']['config'] = \
+ {'install-protocol-eq': rest_protocol_name}
+
+ # Handle match source VRF
+ if match_top.get('source_vrf'):
+ route_map_statement[
+ 'conditions'][
+ 'openconfig-routing-policy-ext:match-src-network-instance'
+ ] = {'config': {'name': match_top['source_vrf']}}
+
+ # Handle match tag
+ if match_top.get('tag'):
+ route_map_statement['conditions']['match-tag-set'] = {
+ 'config': {
+ 'openconfig-routing-policy-ext:tag-value': [match_top['tag']]
+ }
+ }
+
+ def get_route_map_modify_set_attr(self, command, route_map_statement, have):
+ '''In the dict specified by the input route_map_statement paramenter,
+ provide REST API definitions of all "set" attributes contained in the
+ user input command dict specified by the "command" input parameter
+ to this function.'''
+
+ cmd_set_top = command.get('set')
+ if not cmd_set_top:
+ return
+
+ # Get the current configuration (if any) for this route map statement
+ cfg_set_top = {}
+ conf_map_name = command.get('map_name')
+ conf_seq_num = command.get('sequence_num')
+ cmd_rmap_have = self.get_matching_map(conf_map_name, conf_seq_num, have)
+ if cmd_rmap_have:
+ cfg_set_top = cmd_rmap_have.get('set')
+
+ route_map_actions = route_map_statement['actions']
+
+ # Handle configuration for BGP policy "set" conditions
+ # ----------------------------------------------------
+ route_map_actions['openconfig-bgp-policy:bgp-actions'] = {}
+ route_map_bgp_actions = \
+ route_map_actions['openconfig-bgp-policy:bgp-actions'] = {}
+ # Handle 'set' AS path prepend
+ if cmd_set_top.get('as_path_prepend'):
+ route_map_bgp_actions['set-as-path-prepend'] = {
+ 'config': {
+ 'openconfig-routing-policy-ext:asn-list': cmd_set_top['as_path_prepend']
+ }
+ }
+
+ # Handle'set' community list delete
+ if cmd_set_top.get('comm_list_delete'):
+ route_map_bgp_actions['set-community-delete'] = {
+ 'config': {
+ 'community-set-delete': cmd_set_top['comm_list_delete']
+ }
+ }
+
+ # Handle 'set' community
+ if cmd_set_top.get('community'):
+ route_map_bgp_actions['set-community'] = {
+ 'config': {
+ 'method': 'INLINE',
+ 'options': 'ADD'
+ },
+ 'inline': {
+ 'config': {
+ 'communities': []
+ }
+ }
+ }
+
+ rmap_set_communities_cfg = \
+ route_map_bgp_actions['set-community']['inline']['config']['communities']
+
+ if cmd_set_top['community'].get('community_number'):
+
+ # Abort the playbook if the Community "none' attribute is configured.
+ if cfg_set_top:
+ if (cfg_set_top.get('community') and
+ cfg_set_top['community'].get('community_attributes') and
+ 'none' in cfg_set_top['community']['community_attributes']):
+ self._module.fail_json(
+ msg='\nPlaybook aborted: The route map "set" community '
+ '"none" attribute is configured.\n\nPlease remove '
+ 'the conflicting configuration to configure other '
+ 'community "set" attributes.\n')
+
+ comm_num_list = cmd_set_top['community']['community_number']
+
+ for comm_num in comm_num_list:
+ rmap_set_communities_cfg.append(comm_num)
+
+ if cmd_set_top['community'].get('community_attributes'):
+ comm_attr_list = []
+ comm_attr_list = cmd_set_top['community']['community_attributes']
+ if 'none' in comm_attr_list:
+ # Verify that no other community attributes are being requested
+ # at the same time as the "none" attribute and that no
+ # community attributes are currently configured. Abort the
+ # playbook execution if these conditions are not met.
+ if len(comm_attr_list) > 1 or rmap_set_communities_cfg:
+ self._module.fail_json(
+ msg='\nPlaybook aborted: The route map "set" community "none"'
+ 'attribute cannot be configured when other "set" community '
+ 'attributes are requested or configured.\n\n'
+ 'Please revise the playbook to configure the "none"'
+ 'attribute.\n')
+
+ # Abort the playbook if other Community "set" attributes are
+ # currently configured.
+ if cfg_set_top:
+ if (cfg_set_top.get('community') and
+ (cfg_set_top['community'].get('community_number') or
+ (cfg_set_top['community'].get('community_attributes') and
+ 'none' not in cfg_set_top['community']['community_attributes']))):
+ self._module.fail_json(
+ msg='\nPlaybook aborted: The route map "set" community "none" '
+ ' attribute cannot be configured when other"set" community '
+ 'attributes are requested or configured.\n\n'
+ 'Please remove the conflicting configuration to '
+ 'configure the "none" attribue.\n')
+
+ # Proceed with configuring 'none' if the validity checks passed.
+ rmap_set_communities_cfg.append('openconfig-bgp-types:NONE')
+ else:
+
+ # Abort the playbook if the Community "none' attribute is configured.
+ if cfg_set_top:
+ if (cfg_set_top.get('community') and
+ cfg_set_top['community'].get('community_attributes') and
+ 'none' in cfg_set_top['community']['community_attributes']):
+ self._module.fail_json(
+ msg='\nPlaybook aborted: The route map "set"community "none" attribute is '
+ 'configured.\n\n'
+ 'Please remove the conflicting configuration to configure '
+ 'other community "set" attributes.\n')
+
+ comm_attr_rest_name = {
+ 'local_as': 'openconfig-bgp-types:NO_EXPORT_SUBCONFED',
+ 'no_advertise': 'openconfig-bgp-types:NO_ADVERTISE',
+ 'no_export': 'openconfig-bgp-types:NO_EXPORT',
+ 'no_peer': 'openconfig-bgp-types:NOPEER',
+ 'additive': 'openconfig-routing-policy-ext:ADDITIVE'
+ }
+
+ for comm_attr in comm_attr_list:
+ rmap_set_communities_cfg.append(comm_attr_rest_name[comm_attr])
+
+ # Handle set extcommunity
+ if cmd_set_top.get('extcommunity'):
+ route_map_bgp_actions['set-ext-community'] = {
+ 'config': {
+ 'method': 'INLINE',
+ 'options': 'ADD'
+ },
+ 'inline': {
+ 'config': {
+ 'communities': []
+ }
+ }
+ }
+
+ rmap_set_extcommunities_cfg = \
+ route_map_bgp_actions['set-ext-community']['inline']['config']['communities']
+
+ if cmd_set_top['extcommunity'].get('rt'):
+ rt_list = cmd_set_top['extcommunity']['rt']
+
+ for rt_val in rt_list:
+ rmap_set_extcommunities_cfg.append("route-target:" + rt_val)
+
+ if cmd_set_top['extcommunity'].get('soo'):
+ soo_list = cmd_set_top['extcommunity']['soo']
+
+ for soo in soo_list:
+ rmap_set_extcommunities_cfg.append("route-origin:" + soo)
+
+ #
+ # Handle configuration for BGP policy "set" conditions
+ # to be located within the "config" sub-dictionary
+ # ----------------------------------------------------
+ route_map_bgp_actions['config'] = {}
+ route_map_bgp_actions_cfg = \
+ route_map_actions['openconfig-bgp-policy:bgp-actions']['config']
+
+ # Handle set IP next hop.
+ if cmd_set_top.get('ip_next_hop'):
+ route_map_bgp_actions_cfg['set-next-hop'] = cmd_set_top['ip_next_hop']
+
+ # Handle set IPv6 next hop.
+ if cmd_set_top.get('ipv6_next_hop'):
+ if cmd_set_top['ipv6_next_hop'].get('global_addr'):
+ route_map_bgp_actions_cfg['set-ipv6-next-hop-global'] = \
+ cmd_set_top['ipv6_next_hop']['global_addr']
+ if cmd_set_top['ipv6_next_hop'].get('prefer_global') is not None:
+ boolval = \
+ self.yaml_bool_to_python_bool(cmd_set_top['ipv6_next_hop']['prefer_global'])
+ route_map_bgp_actions_cfg['set-ipv6-next-hop-prefer-global'] = boolval
+
+ # Handle set local preference.
+ if cmd_set_top.get('local_preference'):
+ route_map_bgp_actions_cfg['set-local-pref'] = cmd_set_top['local_preference']
+
+ # Handle set metric
+ if cmd_set_top.get('metric'):
+ route_map_actions['metric-action'] = {'config': {}}
+ route_map_metric_actions = route_map_actions['metric-action']['config']
+
+ if cmd_set_top['metric'].get('value'):
+ route_map_metric_actions['metric'] = cmd_set_top['metric']['value']
+ route_map_metric_actions['action'] = \
+ 'openconfig-routing-policy:METRIC_SET_VALUE'
+ route_map_bgp_actions_cfg['set-med'] = cmd_set_top['metric']['value']
+ elif cmd_set_top['metric'].get('rtt_action'):
+ if cmd_set_top['metric']['rtt_action'] == 'set':
+ route_map_metric_actions['action'] = \
+ 'openconfig-routing-policy:METRIC_SET_RTT'
+ elif cmd_set_top['metric']['rtt_action'] == 'add':
+ route_map_metric_actions['action'] = \
+ 'openconfig-routing-policy:METRIC_ADD_RTT'
+ elif cmd_set_top['metric']['rtt_action'] == 'subtract':
+ route_map_metric_actions['action'] = \
+ 'openconfig-routing-policy:METRIC_SUBTRACT_RTT'
+
+ if not route_map_metric_actions:
+ route_map_actions.pop('metric-action')
+
+ # Handle set origin
+ if cmd_set_top.get('origin'):
+ route_map_bgp_actions_cfg['set-route-origin'] = cmd_set_top['origin'].upper()
+
+ # Handle set weight
+ if cmd_set_top.get('weight'):
+ route_map_bgp_actions_cfg['set-weight'] = cmd_set_top['weight']
+
+ @staticmethod
+ def get_route_map_modify_call_attr(command, route_map_statement):
+ '''In the dict specified by the input route_map_statement paramenter,
+ provide REST API definitions of the "call" attribute (if present)
+ contained in the user input command dict specified by the "command"
+ input parameter to this function.'''
+
+ call_val = command.get('call')
+ if not call_val:
+ return
+
+ if not route_map_statement.get('conditions'):
+ route_map_statement['conditions'] = {'config': {}}
+ elif not route_map_statement['conditions'].get('config'):
+ route_map_statement['conditions']['config'] = {}
+ route_map_statement['conditions']['config']['call-policy'] = call_val
+
+ def get_delete_all_route_map_cfg_request(self):
+ '''Append to the input list of REST API requests the REST API to
+ Delete all route map configuration'''
+ requests = [{'path': self.route_maps_uri, 'method': DELETE}]
+ return requests
+
+ def get_delete_one_route_map_cfg(self, conf_map_name, requests):
+ '''Append to the input list of REST API requests the REST API to
+ delete all configuration for the specified route map.'''
+
+ delete_rmap_path = self.route_map_uri.format(conf_map_name)
+ request = {'path': delete_rmap_path, 'method': DELETE}
+ requests.append(request)
+
+ def get_delete_route_map_stmt_cfg(self, command, requests):
+ '''Append to the input list of REST API requests the REST API to
+ delete all configuration for the route map "statement" (route
+ map sub-section) specified by the combination of the route
+ map name and "statement" sequence number in the input
+ "command" dict.'''
+ conf_map_name = command.get('map_name')
+ conf_seq_num = command.get('sequence_num')
+ req_seq_num = str(conf_seq_num)
+
+ delete_rmap_stmt_path = self.route_map_stmt_uri.format(conf_map_name, req_seq_num)
+ request = {'path': delete_rmap_stmt_path, 'method': DELETE}
+ requests.append(request)
+
+ def get_delete_route_maps_requests(self, have, commands):
+ '''Traverse the input list of configuration "delete" commands obtained
+ from parsing the input playbook parameters. For each command,
+ create and return the appropriate set of REST API requests to delete
+ the appropriate elements from the route map specified by the current command.'''
+
+ requests = []
+ if commands:
+ for command in commands:
+ # Create requests for "eligible" attributes within the current route
+ # map statement. The content of the "command" object, on return from
+ # execution has only the subset of currently configured attributes
+ # within the full group of requested attributes for deletion from
+ # this route map statement.
+ self.get_delete_single_route_map_requests(have, command, requests)
+ return requests
+
+ def get_delete_single_route_map_requests(self, have, command, requests):
+ '''Create and return the appropriate set of route map REST APIs
+ to delete the eligible requestd attributes from the route map
+ configuration specified by the current "command".'''
+
+ if not command:
+ return
+
+ # Validate the current command.
+ conf_map_name = command.get('map_name', None)
+ if not conf_map_name:
+ command = {}
+ return
+ conf_seq_num = command.get('sequence_num', None)
+ if not conf_seq_num:
+ if self.any_rmap_inst_in_have(conf_map_name, have):
+ self.get_delete_one_route_map_cfg(conf_map_name, requests)
+ return
+
+ # Get the current configuration (if any) for this route map statement
+ cmd_rmap_have = self.get_matching_map(conf_map_name, conf_seq_num, have)
+ if not cmd_rmap_have:
+ command = {}
+ return
+
+ # Check for route map statement deletion before proceeding further.
+ cmd_match_top = command.get('match')
+ if cmd_match_top:
+ cmd_match_top = command['match']
+
+ cmd_set_top = command.get('set')
+ if cmd_set_top:
+ cmd_set_top = command['set']
+
+ if not cmd_match_top and not cmd_set_top:
+ self.get_delete_route_map_stmt_cfg(command, requests)
+ return
+
+ # Proceed with validity checking and execution
+ conf_action = command.get('action', None)
+ if not conf_action:
+ self._module.fail_json(
+ msg="\nThe 'action' attribute is required, but is absent"
+ "for route map {0} sequence number {1}\n".format(
+ conf_map_name, conf_seq_num))
+
+ if conf_action not in ('permit', 'deny'):
+ self._module.fail_json(
+ msg="\nInvalid 'action' attribute value {0} for"
+ "route map {1} sequence number {2}\n".format(
+ conf_action, conf_map_name, conf_seq_num))
+ command = {}
+ return
+
+ if cmd_match_top:
+ self.get_route_map_delete_match_attr(command, cmd_rmap_have, requests)
+ if cmd_set_top:
+ self.get_route_map_delete_set_attr(command, cmd_rmap_have, requests)
+ if command:
+ self.get_route_map_delete_call_attr(command, cmd_rmap_have, requests)
+
+ return
+
+ @staticmethod
+ def get_matching_map(conf_map_name, conf_seq_num, input_list):
+ '''In the input list of command or configuration dicts, find the route map
+ configuration "statement" (if it exists) for the specified map name
+ and sequence number.'''
+ for cfg_route_map in input_list:
+ if cfg_route_map.get('map_name') and cfg_route_map.get('sequence_num'):
+ if (cfg_route_map['map_name'] == conf_map_name and
+ cfg_route_map.get('sequence_num') == conf_seq_num):
+ return cfg_route_map
+
+ return {}
+
+ @staticmethod
+ def any_rmap_inst_in_have(conf_map_name, have):
+ '''In the current configuration on the target device, determine if there
+ is at least one configuration "statement" for the specified route map name
+ from the input playbook request.'''
+ for cfg_route_map in have:
+ if cfg_route_map.get('map_name'):
+ if cfg_route_map['map_name'] == conf_map_name:
+ return True
+
+ return False
+
+ def get_route_map_delete_match_attr(self, command, cmd_rmap_have, requests):
+ '''Append to the input list of REST API requests the REST APIs needed
+ for deletion of all eligible "match" attributes contained in the
+ user input command dict specified by the "command" input parameter
+ to this function. Modify the contents of the "command" object to
+ remove any attributes that are not currently configured. These
+ attributes are not "eligible" for deletion and no REST API "request"
+ is generated for them.'''
+
+ conf_map_name = command['map_name']
+ conf_seq_num = command['sequence_num']
+ req_seq_num = str(conf_seq_num)
+
+ match_top = command.get('match')
+ if not match_top:
+ return
+ match_keys = match_top.keys()
+
+ cfg_match_top = cmd_rmap_have.get('match')
+ if not cfg_match_top:
+ command.pop('match')
+ return
+ cfg_match_keys = cfg_match_top.keys()
+
+ match_both_keys = set(match_keys).intersection(cfg_match_keys)
+
+ # Remove any requested deletion items that aren't configured
+ match_pop_keys = set(match_keys).difference(match_both_keys)
+ for key in match_pop_keys:
+ match_top.pop(key)
+ if not match_top or not match_both_keys:
+ command.pop('match')
+ return
+
+ # Handle configuration for BGP policy "match" conditions
+ self.get_route_map_delete_match_bgp(command, match_both_keys, cmd_rmap_have, requests)
+ if not command.get('match'):
+ if 'match' in command:
+ command.pop('match')
+ return
+
+ # Handle generic top level match attributes.
+ generic_match_rest_attr = {
+ 'interface': 'match-interface',
+ 'source_vrf': 'openconfig-routing-policy-ext:match-src-network-instance',
+ 'tag': 'match-tag-set/config/openconfig-routing-policy-ext:tag-value',
+ 'source_protocol': 'config/install-protocol-eq'
+ }
+
+ match_delete_req_base = (self.route_map_stmt_base_uri.format(conf_map_name, req_seq_num) +
+ 'conditions/')
+
+ for key in generic_match_rest_attr:
+ if key in match_both_keys and match_top[key] == cfg_match_top[key]:
+ request_uri = match_delete_req_base + generic_match_rest_attr[key]
+ request = {'path': request_uri, 'method': DELETE}
+ requests.append(request)
+ elif key in match_top:
+ match_top.pop(key)
+ if not match_top:
+ command.pop('match')
+ return
+
+ # Handle match peer
+ peer_str = ''
+ if 'peer' in match_both_keys:
+ if (match_top['peer'].get('interface') and cfg_match_top['peer'].get('interface') and
+ match_top['peer']['interface'] == cfg_match_top['peer']['interface']):
+ peer_str = match_top['peer']['interface']
+ elif (match_top['peer'].get('ip') and cfg_match_top['peer'].get('ip') and
+ match_top['peer']['ip'] == cfg_match_top['peer']['ip']):
+ peer_str = match_top['peer']['ip']
+ elif (match_top['peer'].get('ipv6') and cfg_match_top['peer'].get('ipv6') and
+ match_top['peer']['ipv6'] == cfg_match_top['peer']['ipv6']):
+ peer_str = match_top['peer']['ipv6']
+ else:
+ match_top.pop('peer')
+ if not match_top:
+ command.pop('match')
+ return
+
+ if peer_str:
+ request_uri = (match_delete_req_base +
+ 'match-neighbor-set/config/'
+ 'openconfig-routing-policy-ext:address={0}'.format(peer_str))
+ request = {'path': request_uri, 'method': DELETE}
+ requests.append(request)
+
+ elif 'peer' in match_top:
+ match_top.pop('peer')
+ if not match_top:
+ command.pop('match')
+ return
+
+ # Handle match IP address/prefix
+ if ('ip' in match_both_keys and match_top['ip'].get('address') and
+ match_top['ip']['address'] == cfg_match_top['ip'].get('address')):
+ request_uri = match_delete_req_base + 'match-prefix-set/config/prefix-set'
+ request = {'path': request_uri, 'method': DELETE}
+ requests.append(request)
+ elif 'ip' in match_top:
+ match_top.pop('ip')
+ if not match_top:
+ command.pop('match')
+ return
+
+ # Handle match IPv6 address/prefix
+ if ('ipv6' in match_both_keys and match_top['ipv6'].get('address') and
+ match_top['ipv6']['address'] == cfg_match_top['ipv6'].get('address')):
+ ipv6_attr_name = \
+ 'match-prefix-set/config/openconfig-routing-policy-ext:ipv6-prefix-set'
+ request_uri = (match_delete_req_base + ipv6_attr_name)
+ request = {'path': request_uri, 'method': DELETE}
+ requests.append(request)
+ elif 'ipv6' in match_top:
+ match_top.pop('ipv6')
+ if not match_top:
+ command.pop('match')
+ return
+
+ def get_route_map_delete_match_bgp(self, command, match_both_keys, cmd_rmap_have, requests):
+ '''Append to the input list of REST API requests the REST APIs needed
+ for deletion of all eligible "match" attributes defined within the
+ BGP match conditions section of the openconfig routing-policy
+ definitions for "policy-definitions" (route maps).'''
+
+ conf_map_name = command.get('map_name', None)
+ conf_seq_num = command.get('sequence_num', None)
+ req_seq_num = str(conf_seq_num)
+ match_top = command['match']
+ cfg_match_top = cmd_rmap_have.get('match')
+ route_map_stmt_base_uri_fmt = self.route_map_stmt_base_uri.format(conf_map_name,
+ req_seq_num)
+ bgp_match_delete_req_base = (route_map_stmt_base_uri_fmt +
+ 'conditions/openconfig-bgp-policy:bgp-conditions/')
+
+ # Handle BGP match items within the "config" sub-tree in the openconfig REST API definitons.
+ self.get_route_map_delete_match_bgp_cfg(command, match_both_keys, cmd_rmap_have, requests)
+
+ # Handle as_path
+ if 'as_path' in match_both_keys and match_top['as_path'] == cfg_match_top['as_path']:
+ request_uri = bgp_match_delete_req_base + 'match-as-path-set'
+ request = {'path': request_uri, 'method': DELETE}
+ requests.append(request)
+ elif match_top.get('as_path'):
+ match_top.pop('as_path')
+
+ # Handle match evpn
+ if 'evpn' in match_both_keys:
+ evpn_cfg_delete_base = \
+ bgp_match_delete_req_base + 'openconfig-bgp-policy-ext:match-evpn-set/config/'
+ evpn_attrs = match_top['evpn']
+ evpn_match_keys = evpn_attrs.keys()
+ evpn_rest_attr = {
+ 'default_route': 'default-type5-route',
+ 'route_type': 'route-type',
+ 'vni': 'vni-number'
+ }
+ pop_list = []
+ for key in evpn_match_keys:
+ if (key not in cfg_match_top['evpn'] or
+ evpn_attrs[key] != cfg_match_top['evpn'][key]):
+ pop_list.append(key)
+ else:
+ request_uri = evpn_cfg_delete_base + evpn_rest_attr[key]
+ request = {'path': request_uri, 'method': DELETE}
+ requests.append(request)
+ for key in pop_list:
+ match_top['evpn'].pop(key)
+ if not match_top['evpn']:
+ match_top.pop('evpn')
+
+ def get_route_map_delete_match_bgp_cfg(self, command, match_both_keys, cmd_rmap_have, requests):
+ '''Append to the input list of REST API requests the REST APIs needed
+ for deletion of all eligible "match" attributes defined within the
+ BGP match conditions 'config' section of the openconfig routing-policy
+ definitions for "policy-definitions" (route maps).'''
+
+ match_top = command['match']
+ cfg_match_top = cmd_rmap_have.get('match')
+ conf_map_name = command['map_name']
+ conf_seq_num = command['sequence_num']
+ req_seq_num = str(conf_seq_num)
+ bgp_keys = {'metric', 'origin', 'local_preference', 'community', 'ext_comm', 'ip'}
+ delete_bgp_keys = bgp_keys.intersection(match_both_keys)
+ if not delete_bgp_keys:
+ return
+ delete_bgp_attrs = []
+ bgp_match_delete_req_base = (self.route_map_stmt_base_uri.format(conf_map_name,
+ req_seq_num) +
+ 'conditions/openconfig-bgp-policy:bgp-conditions/config/')
+
+ # Check for IP next hop deletion. This is a special case because "next_hop" is
+ # a level below "ip" in the argspec hierarchy. If 'ip' is the only key in
+ # delete_bgp_keys, and IP next hop deletion is not required, there is no
+ # BGP condition match attribute deletion required.
+ if 'ip' in delete_bgp_keys:
+ if not match_top['ip'].get('next_hop') or not cfg_match_top['ip'].get('next_hop'):
+ delete_bgp_keys.remove('ip')
+ if 'next_hop' in match_top['ip']:
+ match_top['ip'].pop('next_hop')
+ if not match_top['ip']:
+ match_top.pop('ip')
+ if not match_top:
+ command.pop('match')
+ return
+
+ if not delete_bgp_keys:
+ return
+ else:
+ if match_top['ip']['next_hop'] == cfg_match_top['ip']['next_hop']:
+ request_uri = (bgp_match_delete_req_base +
+ 'openconfig-bgp-policy-ext:next-hop-set')
+ request = {'path': request_uri, 'method': DELETE}
+ requests.append(request)
+ else:
+ match_top['ip'].pop('next_hop')
+ if not match_top['ip']:
+ match_top.pop('ip')
+ if not match_top:
+ command.pop('match')
+ return
+
+ delete_bgp_keys.remove('ip')
+ if not delete_bgp_keys:
+ return
+
+ # Check for deletion of other BGP match attributes.
+ bgp_rest_attr = {
+ 'community': 'community-set',
+ 'ext_comm': 'ext-community-set',
+ 'local_preference': 'local-pref-eq',
+ 'metric': 'med-eq',
+ 'origin': 'origin-eq'
+ }
+ for key in delete_bgp_keys:
+ if match_top[key] == cfg_match_top[key]:
+ bgp_rest_attr_key = bgp_rest_attr[key]
+ delete_bgp_attrs.append(bgp_rest_attr_key)
+ else:
+ match_top.pop(key)
+ if not match_top:
+ command.pop('match')
+ return
+
+ if not delete_bgp_attrs:
+ return
+
+ # Create requests for deletion of the eligible BGP match attributes.
+ for attr in delete_bgp_attrs:
+ request_uri = bgp_match_delete_req_base + attr
+ request = {'path': request_uri, 'method': DELETE}
+ requests.append(request)
+
+ def get_route_map_delete_set_attr(self, command, cmd_rmap_have, requests):
+ '''Append to the input list of REST API requests the REST APIs needed
+ for deletion of all eligible "set" attributes contained in the
+ user input command dict specified by the "command" input parameter
+ to this function. Modify the contents of the "command" object to
+ remove any attributes that are not currently configured. These
+ attributes are not "eligible" for deletion and no REST API "request"
+ is generated for them.'''
+
+ cmd_set_top = command.get('set')
+ if not cmd_set_top:
+ return
+ set_keys = cmd_set_top.keys()
+
+ cfg_set_top = cmd_rmap_have.get('set')
+ if not cfg_set_top:
+ command.pop('set')
+ return
+ cfg_set_keys = cfg_set_top.keys()
+
+ set_both_keys = set(set_keys).intersection(cfg_set_keys)
+ if not set_both_keys:
+ command.pop('set')
+ return
+
+ conf_map_name = command['map_name']
+ conf_seq_num = command['sequence_num']
+ req_seq_num = str(conf_seq_num)
+ set_delete_base = (self.route_map_stmt_base_uri.format(conf_map_name,
+ req_seq_num) + 'actions/')
+
+ # Handle configuration for BGP policy "set" conditions
+ self.get_route_map_delete_set_bgp(command, set_both_keys, cmd_rmap_have, requests)
+ cmd_set_top = command.get('set')
+ if not cmd_set_top:
+ command.pop('set')
+ return
+
+ # Handle metric "set" attributes.
+ if 'metric' in set_both_keys:
+ set_delete_metric_base = set_delete_base + 'metric-action/config'
+ if cmd_set_top['metric'].get('rtt_action'):
+ if cmd_set_top['metric']['rtt_action'] == cfg_set_top['metric'].get('rtt_action'):
+ request_uri = set_delete_metric_base
+ request = {'path': request_uri, 'method': DELETE}
+ requests.append(request)
+ else:
+ cmd_set_top.pop('metric')
+ if not cmd_set_top:
+ command.pop('set')
+ elif cmd_set_top['metric'].get('value'):
+ set_delete_bgp_base = set_delete_base + 'openconfig-bgp-policy:bgp-actions/'
+ if cmd_set_top['metric']['value'] == cfg_set_top['metric'].get('value'):
+ request = {'path': set_delete_metric_base, 'method': DELETE}
+ requests.append(request)
+ request = {
+ 'path': set_delete_bgp_base + 'config/set-med',
+ 'method': DELETE
+ }
+ requests.append(request)
+
+ else:
+ cmd_set_top.pop('metric')
+ if not cmd_set_top:
+ command.pop('set')
+ else:
+ # 'metric' is not in set_both_keys
+ if cmd_set_top.get('metric'):
+ cmd_set_top.pop('metric')
+ if not cmd_set_top:
+ command.pop('set')
+ return
+
+ def get_route_map_delete_set_bgp(self, command, set_both_keys, cmd_rmap_have, requests):
+ '''Append to the input list of REST API requests the REST APIs needed
+ for deletion of all eligible "set" attributes defined within the
+ BGP "set" conditions section of the openconfig routing-policy
+ definitions for "policy-definitions" (route maps).'''
+
+ cmd_set_top = command['set']
+ cfg_set_top = cmd_rmap_have.get('set')
+ conf_map_name = command['map_name']
+ conf_seq_num = command['sequence_num']
+ req_seq_num = str(conf_seq_num)
+ bgp_set_delete_req_base = (self.route_map_stmt_base_uri.format(conf_map_name, req_seq_num) +
+ 'actions/openconfig-bgp-policy:bgp-actions/')
+
+ # Handle BGP "set" items within the "config" sub-tree in the openconfig REST API definitons.
+ self.get_route_map_delete_set_bgp_cfg(command, set_both_keys, cmd_rmap_have, requests)
+
+ # Handle as_path_prepend
+ if ('as_path_prepend' in set_both_keys and
+ cmd_set_top['as_path_prepend'] == cfg_set_top['as_path_prepend']):
+ request_uri = bgp_set_delete_req_base + 'set-as-path-prepend'
+ request = {'path': request_uri, 'method': DELETE}
+ requests.append(request)
+ else:
+ if cmd_set_top.get('as_path_prepend'):
+ cmd_set_top.pop('as_path_prepend')
+ if not cmd_set_top:
+ return
+
+ # Handle the "community list delete" (comm_list_delete) attribute
+ if ('comm_list_delete' in set_both_keys and
+ cmd_set_top['comm_list_delete'] == cfg_set_top['comm_list_delete']):
+ request_uri = bgp_set_delete_req_base + 'set-community-delete'
+ request = {'path': request_uri, 'method': DELETE}
+ requests.append(request)
+ else:
+ if cmd_set_top.get('comm_list_delete'):
+ cmd_set_top.pop('comm_list_delete')
+ if not cmd_set_top:
+ return
+
+ # Handle "set community": Handle named attributes first, then handle community numbers
+ if 'community' not in set_both_keys:
+ if cmd_set_top.get('community'):
+ cmd_set_top.pop('community')
+ if not cmd_set_top:
+ return
+ else:
+ community_attr_remove_list = []
+ set_community_delete_attrs = []
+ if cmd_set_top['community'].get('community_attributes'):
+ if cfg_set_top['community'].get('community_attributes'):
+ # Append eligible entries to the delete list. Remember which entries
+ # are ineligible.
+ for community_attr in cmd_set_top['community']['community_attributes']:
+ if community_attr in cfg_set_top['community']['community_attributes']:
+ community_rest_name = self.set_community_rest_names[community_attr]
+ set_community_delete_attrs.append(community_rest_name)
+ else:
+ community_attr_remove_list.append(community_attr)
+
+ # Delete ineligible entries from the command list.
+ for community_attr in community_attr_remove_list:
+ cmd_set_top['community']['community_attributes'].remove(community_attr)
+ if not cmd_set_top['community']['community_attributes']:
+ cmd_set_top['community'].pop('community_attributes')
+ else:
+ # No community attribute entries are configured. Pop the corresponding
+ # commands from the command list.
+ cmd_set_top['community'].pop('community_attributes')
+
+ if not cmd_set_top['community']:
+ cmd_set_top.pop('community')
+ if not cmd_set_top:
+ return
+
+ # Handle deletion of "set" community numbers.
+ if cmd_set_top.get('community') and cmd_set_top['community'].get('community_number'):
+ community_number_remove_list = []
+ if cfg_set_top['community'].get('community_number'):
+ # Append eligible entries to the delete list. Remember which entries
+ # are ineligible.
+ for community_number in cmd_set_top['community']['community_number']:
+ if community_number in cfg_set_top['community']['community_number']:
+ set_community_delete_attrs.append(community_number)
+ else:
+ community_number_remove_list.append(community_number)
+
+ # Delete ineligible entries from the command list.
+ for community_number in community_number_remove_list:
+ cmd_set_top['community']['community_number'].remove(community_number)
+ if not cmd_set_top['community']['community_number']:
+ cmd_set_top['community'].pop('community_number')
+ else:
+ # If no community number entries are configured, pop the entire
+ # community number command dict.
+ cmd_set_top['community'].pop('community_number')
+
+ if not cmd_set_top['community']:
+ cmd_set_top.pop('community')
+ if not cmd_set_top:
+ return
+
+ # Format and enqueue a request to delete eligible community attributes
+ if set_community_delete_attrs:
+ bgp_set_delete_community_uri = bgp_set_delete_req_base + 'set-community'
+ bgp_set_delete_comm_payload = \
+ {'openconfig-bgp-policy:set-community': {}}
+ bgp_set_delete_comm_payload_contents = \
+ bgp_set_delete_comm_payload['openconfig-bgp-policy:set-community']
+ bgp_set_delete_comm_payload_contents['config'] = \
+ {'method': 'INLINE', 'options': 'REMOVE'}
+ bgp_set_delete_comm_payload_contents['inline'] = \
+ {'config': {'communities': set_community_delete_attrs}}
+
+ request = {
+ 'path': bgp_set_delete_community_uri,
+ 'method': PATCH,
+ 'data': bgp_set_delete_comm_payload
+ }
+ requests.append(request)
+
+ # Handle set "extended community" deletion
+ if 'extcommunity' not in set_both_keys:
+ if cmd_set_top.get('extcommunity'):
+ cmd_set_top.pop('extcommunity')
+ if not cmd_set_top:
+ return
+ else:
+ set_extcommunity_delete_attrs = []
+
+ for extcomm_type in self.set_extcomm_rest_names:
+ ext_comm_number_remove_list = []
+ if cmd_set_top['extcommunity'].get(extcomm_type):
+ if cfg_set_top['extcommunity'].get(extcomm_type):
+ # Append eligible entries to the delete list. Remember which entries
+ # are ineligible.
+ for extcomm_number in cmd_set_top['extcommunity'][extcomm_type]:
+ if extcomm_number in cfg_set_top['extcommunity'][extcomm_type]:
+ set_extcommunity_delete_attrs.append(
+ self.set_extcomm_rest_names[extcomm_type] + extcomm_number)
+ else:
+ ext_comm_number_remove_list.append(extcomm_number)
+
+ # Delete ineligible entries from the command list.
+ for extcomm_number in ext_comm_number_remove_list:
+ cmd_set_top['extcommunity'][extcomm_type].remove(extcomm_number)
+ if not cmd_set_top['extcommunity'][extcomm_type]:
+ cmd_set_top['extcommunity'].pop(extcomm_type)
+ else:
+ # If no extcommunity entries of this type are configured,
+ # pop the entire extcommunity command sub-dict for this type.
+ cmd_set_top['extcommunity'].pop(extcomm_type)
+
+ if not cmd_set_top['extcommunity']:
+ cmd_set_top.pop('extcommunity')
+ if not cmd_set_top:
+ return
+
+ # Format and enqueue a request to delete eligible extcommunity attributes
+ if set_extcommunity_delete_attrs:
+ bgp_set_delete_extcomm_uri = bgp_set_delete_req_base + 'set-ext-community'
+ bgp_set_delete_extcomm_payload = \
+ {'openconfig-bgp-policy:set-ext-community': {}}
+ bgp_set_delete_comm_payload_contents = \
+ bgp_set_delete_extcomm_payload['openconfig-bgp-policy:set-ext-community']
+ bgp_set_delete_comm_payload_contents['config'] = \
+ {'method': 'INLINE', 'options': 'REMOVE'}
+ bgp_set_delete_comm_payload_contents['inline'] = \
+ {'config': {'communities': set_extcommunity_delete_attrs}}
+
+ request = {
+ 'path': bgp_set_delete_extcomm_uri,
+ 'method': PATCH,
+ 'data': bgp_set_delete_extcomm_payload
+ }
+ requests.append(request)
+
+ def get_route_map_delete_set_bgp_cfg(self, command, set_both_keys, cmd_rmap_have, requests):
+ '''Append to the input list of REST API requests the REST APIs needed
+ for deletion of all eligible "set" attributes defined within the
+ BGP set conditions 'config' section of the openconfig routing-policy
+ definitions for "policy-definitions" (route maps).'''
+
+ cmd_set_top = command['set']
+
+ cfg_set_top = cmd_rmap_have.get('set')
+ conf_map_name = command['map_name']
+ conf_seq_num = command['sequence_num']
+ req_seq_num = str(conf_seq_num)
+ bgp_set_delete_req_base = (self.route_map_stmt_base_uri.format(conf_map_name, req_seq_num) +
+ 'actions/openconfig-bgp-policy:bgp-actions/config/')
+ # Note: Although 'metric' (REST API 'set-med') is in this REST API configuration
+ # group, it is handled separately as part of deleting the top level, functionally
+ # related 'metric-action' attribute.
+ bgp_cfg_keys = {'ip_next_hop', 'origin', 'local_preference', 'ipv6_next_hop', 'weight'}
+ delete_bgp_keys = bgp_cfg_keys.intersection(set_both_keys)
+ if not delete_bgp_keys:
+ for bgp_key in bgp_cfg_keys:
+ if bgp_key in cmd_set_top:
+ cmd_set_top.pop(bgp_key)
+ return
+
+ delete_bgp_attrs = []
+
+ # Handle the special case of ipv6_next_hop
+ if 'ipv6_next_hop' in delete_bgp_keys:
+ delete_bgp_keys.remove('ipv6_next_hop')
+ ipv6_next_hop_rest_names = {
+ 'global_addr': 'set-ipv6-next-hop-global',
+ 'prefer_global': 'set-ipv6-next-hop-prefer-global'
+ }
+ for ipv6_next_hop_key in ipv6_next_hop_rest_names:
+ if cmd_set_top['ipv6_next_hop'].get(ipv6_next_hop_key) is not None:
+ if (cmd_set_top['ipv6_next_hop'][ipv6_next_hop_key] ==
+ cfg_set_top['ipv6_next_hop'].get(ipv6_next_hop_key)):
+ delete_bgp_attrs.append(ipv6_next_hop_rest_names[ipv6_next_hop_key])
+ else:
+ cmd_set_top['ipv6_next_hop'].pop(ipv6_next_hop_key)
+ if not cmd_set_top['ipv6_next_hop']:
+ cmd_set_top.pop('ipv6_next_hop')
+ if not cmd_set_top:
+ return
+
+ if not delete_bgp_keys and not delete_bgp_attrs:
+ return
+
+ # Handle other BGP "config" attributes
+ bgp_cfg_rest_names = {
+ 'ip_next_hop': 'set-next-hop',
+ 'local_preference': 'set-local-pref',
+ 'origin': 'set-route-origin',
+ 'weight': 'set-weight'
+ }
+
+ for bgp_cfg_key in bgp_cfg_rest_names:
+ if bgp_cfg_key in delete_bgp_keys:
+ if cmd_set_top[bgp_cfg_key] == cfg_set_top[bgp_cfg_key]:
+ delete_bgp_attrs.append(bgp_cfg_rest_names[bgp_cfg_key])
+ else:
+ cmd_set_top.pop(bgp_cfg_key)
+
+ if not cmd_set_top:
+ command.pop('set')
+ return
+
+ for delete_bgp_attr in delete_bgp_attrs:
+ del_set_bgp_cfg_uri = bgp_set_delete_req_base + delete_bgp_attr
+ request = {'path': del_set_bgp_cfg_uri, 'method': DELETE}
+ requests.append(request)
+
+ def get_route_map_delete_call_attr(self, command, cmd_rmap_have, requests):
+ '''Append to the input list of REST API requests the REST API needed
+ for deletion of the "call" attribute if this attribute it contained in the
+ user input command dict specified by the "command" input parameter
+ to this function and it is currently configured. Modify the contents of
+ the "command" object to remove the "call" attribute if it is not currently
+ configured.'''
+
+ if not command.get('call'):
+ return
+
+ if not command['call'] == cmd_rmap_have.get('call'):
+ command.pop('call')
+ return
+
+ conf_map_name = command['map_name']
+ req_seq_num = str(command['sequence_num'])
+
+ call_delete_req_uri = \
+ (self.route_map_stmt_base_uri.format(
+ conf_map_name, req_seq_num) + 'conditions/config/call-policy')
+ request = {'path': call_delete_req_uri, 'method': DELETE}
+ requests.append(request)
+
+ @staticmethod
+ def yaml_bool_to_python_bool(yaml_bool):
+ '''Convert the input YAML bool value to a Python bool value'''
+ boolval = False
+ if yaml_bool is None:
+ boolval = False
+ elif yaml_bool:
+ boolval = True
+
+ return boolval
+
+ def route_map_remove_configured_match_peer(self, route_map_payload, have, requests):
+ '''If a route map "match peer" condition is configured in the route map
+ statement corresponding to the incoming route map update request
+ specified by the "route_map_payload" input parameter, equeue a REST API request
+ to delete it.'''
+
+ if (route_map_payload['statements']['statement'][0].get('conditions') and
+ route_map_payload['statements']['statement'][0]
+ ['conditions'].get('match-neighbor-set')):
+ peer = self.match_peer_configured(route_map_payload, have)
+ if peer:
+ request = self.create_match_peer_delete_request(route_map_payload, peer)
+ if request:
+ requests.append(request)
+
+ def match_peer_configured(self, route_map_payload, have):
+ '''Determine if the "match peer ..." condition is already configured for the
+ route map statement corresponding to the incoming route map update request
+ specified by the "route_map_payload" input parameter. Return the peer string
+ if a "match peer" condition is already configured. Otherwise, return an empty
+ string'''
+
+ if not route_map_payload or not have:
+ return ''
+
+ conf_map_name = route_map_payload.get('name')
+ conf_seq_num = (route_map_payload['statements']['statement'][0]['name'])
+ if not conf_map_name or not conf_seq_num:
+ return ''
+
+ # Get the current configuration (if any) for this route map statement
+ cmd_rmap_have = self.get_matching_map(conf_map_name, int(conf_seq_num), have)
+ if (not cmd_rmap_have or not cmd_rmap_have.get('match') or
+ not cmd_rmap_have['match'].get('peer')):
+ return ''
+
+ peer_dict = cmd_rmap_have['match']['peer']
+ if peer_dict.get('interface'):
+ peer_str = peer_dict['interface']
+ elif peer_dict.get('ip'):
+ peer_str = peer_dict['ip']
+ elif peer_dict.get('ipv6'):
+ peer_str = peer_dict['ipv6']
+ else:
+ return ''
+
+ return peer_str
+
+ def create_match_peer_delete_request(self, route_map_payload, peer_str):
+ '''Create a request to delete the current "match peer" configuration for the
+ route map statement corresponding to the incoming route map update request
+ specified by the "route_map_payload," input parameter. Return the created request.'''
+
+ if not route_map_payload:
+ return {}
+
+ conf_map_name = route_map_payload.get('name')
+ conf_seq_num = route_map_payload['statements']['statement'][0]['name']
+ if not conf_map_name or not conf_seq_num:
+ return {}
+ match_delete_req_base = (self.route_map_stmt_base_uri.format(conf_map_name, conf_seq_num) +
+ 'conditions/')
+
+ request_uri = (match_delete_req_base +
+ 'match-neighbor-set/config/'
+ 'openconfig-routing-policy-ext:address={0}'.format(peer_str))
+ request = {'path': request_uri, 'method': DELETE}
+ return request
+
+ def get_delete_replaced_groupings(self, commands, have):
+ '''For each of the route maps specified in the "commands" input list,
+ create requests to delete any existing route map configuration
+ groupings for which modified attribute requests are specified.'''
+
+ requests = []
+ for command in commands:
+ self.get_delete_one_map_replaced_groupings(command, have, requests)
+ return requests
+
+ def get_delete_one_map_replaced_groupings(self, command, have, requests):
+ '''For the route map specified by the input "command", create requests
+ to delete any existing route map configuration groupings for which
+ modified attribute requests are specified'''
+
+ if not command:
+ return {}
+
+ conf_map_name = command.get('map_name', None)
+ conf_seq_num = command.get('sequence_num', None)
+ if not conf_map_name or not conf_seq_num:
+ return {}
+
+ # Get the current configuration (if any) for this route map
+ cmd_rmap_have = self.get_matching_map(conf_map_name, conf_seq_num, have)
+
+ # If there's nothing configured for this route map, there's nothing
+ # to delete.
+ if not cmd_rmap_have:
+ command = {}
+ return command
+
+ self.get_delete_route_map_replaced_match_groupings(command, cmd_rmap_have, requests)
+ replaced_set_group_requests = []
+ self.get_delete_route_map_replaced_set_groupings(command, cmd_rmap_have,
+ replaced_set_group_requests)
+ if replaced_set_group_requests:
+ requests.extend(replaced_set_group_requests)
+
+ # Note: Because the "call" route map attribute is a "flat" attribute, not
+ # a dictionary, no "pre-delete" is required for this branch of the route map
+ # argspec for handling of "replaced" state
+
+ return command
+
+ def get_delete_route_map_replaced_match_groupings(self, command, cmd_rmap_have, requests):
+ '''For the route map specified by the input "command", create requests
+ to delete any existing route map "match" configuration groupings for which
+ modified attribute requests are specified'''
+
+ if not command.get('match'):
+ return
+
+ conf_map_name = command.get('map_name', None)
+ conf_seq_num = command.get('sequence_num', None)
+ req_seq_num = str(conf_seq_num)
+
+ cmd_match_top = command['match']
+ cfg_match_top = cmd_rmap_have.get('match')
+
+ # If there are no 'match' attributes configured for this route map,
+ # there's nothing to delete.
+ if not cfg_match_top:
+ command.pop('match')
+ return
+
+ match_delete_req_base = (self.route_map_stmt_base_uri.format(conf_map_name, req_seq_num) +
+ 'conditions/')
+
+ # Obtain the set of "match" keys for which changes have been requested and
+ # the subset of those keys for which configuration currently exists.
+ cmd_match_keys = cmd_match_top.keys()
+ cfg_match_keys = cfg_match_top.keys()
+
+ peer_str = ''
+ if 'peer' in cfg_match_keys:
+ peer_dict = cfg_match_top['peer']
+ # Only one peer key at a time can be configured.
+ peer_key = list(peer_dict.keys())[0]
+ peer_str = peer_dict[peer_key]
+
+ bgp_match_delete_req_base = match_delete_req_base + 'openconfig-bgp-policy:bgp-conditions/'
+ match_top_level_keys = [
+ 'as_path',
+ 'community',
+ 'ext_comm',
+ 'interface',
+ 'ipv6',
+ 'local_preference',
+ 'metric',
+ 'origin',
+ 'peer',
+ 'source_protocol',
+ 'source_vrf',
+ 'tag'
+ ]
+
+ match_multi_level_keys = [
+ 'evpn',
+ 'ip',
+ ]
+
+ match_uri_attr = {
+ 'as_path': bgp_match_delete_req_base + 'match-as-path-set',
+ 'community': bgp_match_delete_req_base + 'config/community-set',
+ 'evpn': bgp_match_delete_req_base + 'openconfig-bgp-policy-ext:match-evpn-set/config/',
+ 'ext_comm': bgp_match_delete_req_base + 'config/ext-community-set',
+ 'interface': match_delete_req_base + 'match-interface',
+ 'ip': {
+ 'address': match_delete_req_base + 'match-prefix-set/config/prefix-set',
+ 'next_hop': (bgp_match_delete_req_base +
+ 'config/openconfig-bgp-policy-ext:next-hop-set')
+ },
+ 'ipv6': (match_delete_req_base +
+ 'match-prefix-set/config/openconfig-routing-policy-ext:ipv6-prefix-set'),
+ 'local_preference': bgp_match_delete_req_base + 'config/local-pref-eq',
+ 'metric': bgp_match_delete_req_base + 'config/med-eq',
+ 'origin': bgp_match_delete_req_base + 'config/origin-eq',
+ 'peer': (match_delete_req_base +
+ 'match-neighbor-set/config/'
+ 'openconfig-routing-policy-ext:address={0}'.format(peer_str)),
+ 'source_protocol': match_delete_req_base + 'config/install-protocol-eq',
+ 'source_vrf': (match_delete_req_base +
+ 'openconfig-routing-policy-ext:match-src-network-instance'),
+ 'tag': (match_delete_req_base +
+ 'match-tag-set/config/openconfig-routing-policy-ext:tag-value')
+ }
+
+ # Remove all appropriate "match" configuration for this route map if any of the
+ # following criteria are met: (See the note below regarding what configuration
+ # is "appropriate"for deletion.)
+ #
+ # 1) Any top level attribute is specified with a value different from its current
+ # configured value.
+ # 2) Any top level attribute is specified that is not currently configured.
+ # 3) The set of top level attributes specified does not include all currently
+ # configured attributes (regardless of whether the specified values for
+ # these attributes are the same as the ones courrently configured).
+ # (Note: Although the IPv6 attribute is defined as a nested dictionary
+ # to allow for future expansion, it is handled here as a top level
+ # attrbute because it currently has only one member.)
+ #
+ # When deletion has been triggered, an attribute is deleted only if it is
+ # not present at all in the requested configuration. (If it is present in
+ # the requested configuration, the "merge" phase of the "replaced" state
+ # operation will modify it as needed, so it doesn't need to be explicitly
+ # deleted during the "deletion" phase.)
+ #
+ cfg_top_level_key_set = set(cfg_match_keys).intersection(set(match_top_level_keys))
+ cmd_top_level_key_set = set(cmd_match_keys).intersection(set(match_top_level_keys))
+ symmetric_diff_set = cmd_top_level_key_set.symmetric_difference(cfg_top_level_key_set)
+ intersection_diff_set = cmd_top_level_key_set.intersection(cfg_top_level_key_set)
+ cmd_delete_dict = {}
+ if (cmd_top_level_key_set and symmetric_diff_set or
+ (any(keyname for keyname in intersection_diff_set if
+ cmd_match_top[keyname] != cfg_match_top[keyname]))):
+
+ # Deletion has been triggered. First, delete all approriate top level
+ # attributes
+ self.delete_replaced_dict_config(
+ cfg_key_set=cfg_top_level_key_set,
+ cmd_key_set=cmd_top_level_key_set,
+ cfg_parent_dict=cfg_match_top,
+ uri_attr=match_uri_attr,
+ uri_dict_key='cfg_dict_member_key',
+ deletion_dict=cmd_delete_dict,
+ requests=requests)
+
+ # Next, delete all appropriate sub dictionary attributes.
+ match_dict_deletions = {}
+ for match_key in match_multi_level_keys:
+ cfg_key_set = {}
+ cmd_key_set = {}
+ if match_key in cfg_match_top:
+ cfg_key_set = set(cfg_match_top[match_key].keys())
+ if match_key in cfg_match_top:
+ cmd_key_set = ([])
+ if cmd_match_top.get(match_key):
+ cmd_key_set = set(cmd_match_top[match_key].keys())
+ match_dict_deletions[match_key] = {}
+ match_dict_deletions_subdict = match_dict_deletions[match_key]
+ self.delete_replaced_dict_config(
+ cfg_key_set=cfg_key_set,
+ cmd_key_set=cmd_key_set,
+ cfg_parent_dict=cfg_match_top[match_key],
+ uri_attr=match_uri_attr,
+ uri_dict_key=match_key,
+ deletion_dict=match_dict_deletions_subdict,
+ requests=requests)
+
+ # Update the dict specifying deleted commands
+ command.pop('match')
+ if cmd_delete_dict:
+ command['match'] = cmd_delete_dict
+ command['match'].update(match_dict_deletions)
+ return
+
+ # If no top level attribute changes were requested, check for changes in
+ # dictionaries nested below the top level.
+ # -----------------------------------------------------------------------
+ match_key_deletions = {}
+ for match_key in match_multi_level_keys:
+ if match_key in cmd_match_top:
+ if match_key in cfg_match_top:
+ cmd_key_set = set((cmd_match_top[match_key].keys()))
+ cfg_key_set = set(cfg_match_top[match_key].keys())
+ symmetric_diff_set = cmd_key_set.symmetric_difference(cfg_key_set)
+ intersection_diff_set = cmd_key_set.intersection(cfg_key_set)
+ if (symmetric_diff_set or
+ (any(keyname for keyname in intersection_diff_set if
+ cmd_match_top[match_key][keyname] !=
+ cfg_match_top[match_key][keyname]))):
+
+ match_key_deletions[match_key] = {}
+ match_key_deletions_subdict = match_key_deletions[match_key]
+ self.delete_replaced_dict_config(
+ cfg_key_set=cfg_key_set,
+ cmd_key_set=cmd_key_set,
+ cfg_parent_dict=cfg_match_top[match_key],
+ uri_attr=match_uri_attr,
+ uri_dict_key=match_key,
+ deletion_dict=match_key_deletions_subdict,
+ requests=requests)
+
+ command.pop('match')
+ if match_key_deletions:
+ command['match'] = match_key_deletions
+
+ @staticmethod
+ def delete_replaced_dict_config(**in_args):
+ ''' Create and enqueue deletion requests for the appropriate attributes in the dictionary
+ specified by "dict_key". Update the input deletion_dict with the deleted attributes.
+ The input 'inargs' is assumed to contain the following keyword arguments:
+
+ cfg_key_set: The set of currently configured keys for the target dict
+
+ cmd_key_set: The set of currently requested update keys for the target dict
+
+ cfg_parent_dict: The configured dictionary containing the input key set
+
+ uri_attr: a dictionary specifying REST URIs keyed by argspec keys
+
+ uri_dict_key: The key for top level attribue to be used for uri lookup. If set
+ to the string value 'cfg_dict_member_key', the current value of 'cfg_dict_member_key'
+ is used. Otherwise, the specified value is used directly.
+
+ deletion_dict: a dictionary containing attributes deleted from the parent dict
+
+ requests: The list of REST API requests for the executing playbook section
+ '''
+
+ # Set the default uri_key value.
+ uri_key = in_args['uri_dict_key']
+
+ # Iterate through members of the parent dict.
+ for cfg_dict_member_key in in_args['cfg_key_set'].difference(in_args['cmd_key_set']):
+ cfg_dict_member_val = in_args['cfg_parent_dict'][cfg_dict_member_key]
+ if in_args['uri_dict_key'] == 'cfg_dict_member_key':
+ uri_key = cfg_dict_member_key
+ uri = in_args['uri_attr'][uri_key]
+ in_args['deletion_dict'].update(
+ {cfg_dict_member_key: cfg_dict_member_val})
+ if isinstance(uri, dict):
+ for member_key in uri:
+ if in_args['cfg_parent_dict'].get(member_key) is not None:
+ request = {'path': uri[member_key],
+ 'method': DELETE}
+ in_args['requests'].append(request)
+ elif isinstance(uri, list):
+ for set_uri_item in uri:
+ request = {'path': set_uri_item, 'method': DELETE}
+ else:
+ request = {'path': uri, 'method': DELETE}
+ in_args['requests'].append(request)
+
+ def get_delete_route_map_replaced_set_groupings(self, command, cmd_rmap_have,
+ requests):
+ '''For the route map specified by the input "command", create requests
+ to delete any existing route map "set" configuration groupings for which
+ modified attribute requests are specified'''
+
+ if not command.get('set'):
+ return
+
+ conf_map_name = command.get('map_name', None)
+ conf_seq_num = command.get('sequence_num', None)
+ req_seq_num = str(conf_seq_num)
+
+ cmd_set_top = command['set']
+ cfg_set_top = cmd_rmap_have.get('set')
+
+ # If there are no 'set' attributes configured for this route map,
+ # there's nothing to delete.
+ if not cfg_set_top:
+ command.pop('set')
+ return
+
+ set_delete_req_base = (self.route_map_stmt_base_uri.format(conf_map_name, req_seq_num) +
+ 'actions/')
+ bgp_set_delete_req_base = set_delete_req_base + 'openconfig-bgp-policy:bgp-actions/'
+
+ # Obtain the set of "set" keys for which changes have been requested and the set
+ # of keys currently configured.
+ cmd_set_keys = cmd_set_top.keys()
+ cfg_set_keys = cfg_set_top.keys()
+
+ metric_uri = ''
+ if 'metric' in cfg_set_top:
+ if cfg_set_top['metric'].get('rtt_action'):
+ metric_uri = set_delete_req_base + 'metric-action/config'
+ elif cfg_set_top['metric'].get('value'):
+ metric_uri = [set_delete_req_base + 'metric-action/config',
+ bgp_set_delete_req_base + 'config/set-med']
+ # Top level keys: Note: Although "metric" is defined as a dictionary, it
+ # is handled as a "top level" attribute because it can contain
+ # only one configured member (either an rtt_action or a "value").
+ set_top_level_keys = [
+ 'as_path_prepend',
+ 'comm_list_delete',
+ 'ip_next_hop',
+ 'local_preference',
+ 'metric',
+ 'origin',
+ 'weight',
+ ]
+
+ set_uri_attr = {
+ 'as_path_prepend': bgp_set_delete_req_base + 'set-as-path-prepend',
+ 'comm_list_delete': bgp_set_delete_req_base + 'set-community-delete',
+ 'community': bgp_set_delete_req_base + 'set-community',
+ 'extcommunity': bgp_set_delete_req_base + 'set-ext-community',
+ 'ip_next_hop': bgp_set_delete_req_base + 'config/set-next-hop',
+ 'ipv6_next_hop': {
+ 'global_addr': bgp_set_delete_req_base + 'config/set-ipv6-next-hop-global',
+ 'prefer_global': bgp_set_delete_req_base + 'config/set-ipv6-next-hop-prefer-global'
+ },
+ 'local_preference': bgp_set_delete_req_base + 'config/set-local-pref',
+ 'metric': metric_uri,
+ 'origin': bgp_set_delete_req_base + 'config/set-route-origin',
+ 'weight': bgp_set_delete_req_base + 'config/set-weight'
+ }
+
+ # Remove all appropriate "set" configuration for this route map if any of the
+ # following criteria are met: (See the note below regarding what configuration
+ # is "appropriate"for deletion.)
+ #
+ # 1) Any top level attribute is specified with a value different from its current
+ # configured value.
+ # 2) Any top level attribute is specified that is not currently configured.
+ # 3) The set of top level attributes specified does not include all currently
+ # configured attributes (regardless of whether the specified values for
+ # these attributes are the same as the ones courrently configured).
+ # (Note: Although the IPv6 attribute is defined as a nested dictionary
+ # to allow for future expansion, it is handled here as a top level
+ # attrbute because it currently has only one member.)
+ #
+ # When deletion has been triggered, an attribute is deleted only if it is
+ # not present at all in the requested configuration. (If it is present in
+ # the requested configuration, the "merge" phase of the "replaced" state
+ # operation will modify it as needed, so it doesn't need to be explicitly
+ # deleted during the "deletion" phase.)
+ #
+ # Handle top level attributes first. If top level attribute deletion is
+ # triggered, proceed with deletion of dictionaries and lists below the
+ # top level.
+ cfg_top_level_key_set = set(cfg_set_keys).intersection(set(set_top_level_keys))
+ cmd_top_level_key_set = set(cmd_set_keys).intersection(set(set_top_level_keys))
+ cmd_nested_level_key_set = set(cmd_set_keys).difference(set_top_level_keys)
+ symmetric_diff_set = cmd_top_level_key_set.symmetric_difference(cfg_top_level_key_set)
+ intersection_diff_set = cmd_top_level_key_set.intersection(cfg_top_level_key_set)
+ cmd_delete_dict = {}
+ if (cmd_top_level_key_set and symmetric_diff_set or
+ (any(keyname for keyname in intersection_diff_set if
+ cmd_set_top[keyname] != cfg_set_top[keyname]))):
+ # Deletion has been triggered. First, delete all approriate top level
+ # attributes
+ self.delete_replaced_dict_config(
+ cfg_key_set=cfg_top_level_key_set,
+ cmd_key_set=cmd_top_level_key_set,
+ cfg_parent_dict=cfg_set_top,
+ uri_attr=set_uri_attr,
+ uri_dict_key='cfg_dict_member_key',
+ deletion_dict=cmd_delete_dict,
+ requests=requests)
+
+ # Save nested command "set" items and refresh top level command "set" items.
+ cmd_set_nested = {}
+ for nested_key in cmd_nested_level_key_set:
+ if command['set'].get(nested_key) is not None:
+ cmd_set_nested[nested_key] = command['set'][nested_key]
+
+ command.pop('set')
+ if cmd_delete_dict:
+ command['set'] = cmd_delete_dict
+ if cmd_set_nested:
+ if not command.get('set'):
+ command['set'] = {}
+ command['set'].update(cmd_set_nested)
+ if not command.get('set'):
+ command['set'] = {}
+ cmd_set_top = command['set']
+
+ # Proceed with deletion of dictionaries and lists below the top level.
+ # ---------------------------------------------------------------------
+
+ dict_delete_requests = []
+
+ # Check for deletion of set "community" lists. Delete the items in
+ # the currently configured list if it exists. As an optimization,
+ # avoid deleting list items that will be replaced by the received
+ # command.
+
+ set_community_delete_attrs = []
+ if 'community' not in cfg_set_top:
+ if command['set'].get('community'):
+ command['set'].pop('community')
+ if command['set'] is None:
+ command.pop('set')
+ return
+ else:
+ set_community_number_deletions = []
+ if 'community_number' in cfg_set_top['community']:
+
+ # Delete eligible configured community numbers.
+ cfg_community_number_set = set(cfg_set_top['community']['community_number'])
+ cmd_community_number_set = ([])
+ if cmd_set_top.get('community') and 'community_number' in cmd_set_top['community']:
+ cmd_community_number_set = set(cmd_set_top['community']['community_number'])
+ command['set']['community'].pop('community_number')
+
+ for cfg_community_number in cfg_community_number_set.difference(cmd_community_number_set):
+ set_community_delete_attrs.append(cfg_community_number)
+ set_community_number_deletions.append(cfg_community_number)
+
+ if set_community_number_deletions:
+ # Update the list of deleted community numbers in the "command" dict.
+ if not cmd_set_top.get('community'):
+ command['set']['community'] = {}
+ command['set']['community']['community_number'] = set_community_number_deletions
+
+ set_community_attributes_deletions = []
+ if 'community_attributes' in cfg_set_top['community']:
+
+ # Delete eligible configured community attributes.
+ cfg_community_attributes_set = set(cfg_set_top['community']['community_attributes'])
+ cmd_community_attributes_set = ([])
+ if cmd_set_top.get('community') and 'community_attributes' in cmd_set_top['community']:
+ cmd_community_attributes_set = set(cmd_set_top['community']['community_attributes'])
+ command['set']['community'].pop('community_attributes')
+
+ for cfg_community_attribute in cfg_community_attributes_set.difference(cmd_community_attributes_set):
+ set_community_delete_attrs.append(self.set_community_rest_names[cfg_community_attribute])
+ set_community_attributes_deletions.append(cfg_community_attribute)
+
+ if set_community_attributes_deletions:
+ # Update the list of deleted community attributes in the "command" dict.
+ if not cmd_set_top.get('community'):
+ command['set']['community'] = {}
+ command['set']['community']['community_attributes'] = set_community_attributes_deletions
+
+ if command['set'].get('community') is not None and not command['set']['community']:
+ command['set'].pop('community')
+
+ # Format and enqueue a request to delete eligible community attributes
+ if set_community_delete_attrs:
+ bgp_set_delete_community_uri = bgp_set_delete_req_base + 'set-community'
+ bgp_set_delete_comm_payload = \
+ {'openconfig-bgp-policy:set-community': {}}
+ bgp_set_delete_comm_payload_contents = \
+ bgp_set_delete_comm_payload['openconfig-bgp-policy:set-community']
+ bgp_set_delete_comm_payload_contents['config'] = \
+ {'method': 'INLINE', 'options': 'REMOVE'}
+ bgp_set_delete_comm_payload_contents['inline'] = \
+ {'config': {'communities': set_community_delete_attrs}}
+
+ request = {
+ 'path': bgp_set_delete_community_uri,
+ 'method': PATCH,
+ 'data': bgp_set_delete_comm_payload
+ }
+ dict_delete_requests.append(request)
+
+ # Check for deletion of set "extcommunity" lists. Delete the items in
+ # the currently configured list if it exists. As an optimization,
+ # avoid deleting list items that will be replaced by the received
+ # command.
+ set_extcommunity_delete_attrs = []
+
+ if 'extcommunity' not in cfg_set_top:
+ if command['set'].get('extcommunity'):
+ command['set'].pop('extcommunity')
+ if command['set'] is None:
+ command.pop('set')
+ return
+ else:
+ for extcomm_type in self.set_extcomm_rest_names:
+ set_extcommunity_delete_attrs_type = []
+ if extcomm_type in cfg_set_top['extcommunity']:
+ # Delete eligible configured extcommunity list items for this
+ # extcommunity list
+ cfg_extcommunity_list_set = set(cfg_set_top['extcommunity'][extcomm_type])
+ cmd_extcommunity_list_set = ([])
+ if cmd_set_top.get('extcommunity') and extcomm_type in cmd_set_top['extcommunity']:
+ cmd_extcommunity_list_set = set(cmd_set_top['extcommunity'][extcomm_type])
+ command['set']['extcommunity'].pop(extcomm_type)
+ for extcomm_number in cfg_extcommunity_list_set.difference(cmd_extcommunity_list_set):
+ set_extcommunity_delete_attrs.append(
+ self.set_extcomm_rest_names[extcomm_type] +
+ extcomm_number)
+ set_extcommunity_delete_attrs_type.append(extcomm_number)
+
+ if set_extcommunity_delete_attrs_type:
+ # Update the list of deleted extcommunity list items of this type
+ # in the "command" dict.
+ if not cmd_set_top.get('extcommunity'):
+ command['set']['extcommunity'] = {}
+ command['set']['extcommunity'][extcomm_type] = set_extcommunity_delete_attrs_type
+
+ if command['set'].get('extcommunity') is not None and not command['set']['extcommunity']:
+ command['set'].pop('extcommunity')
+
+ # Format and enqueue a request to delete eligible extcommunity attributes
+ if set_extcommunity_delete_attrs:
+ bgp_set_delete_extcomm_uri = bgp_set_delete_req_base + 'set-ext-community'
+ bgp_set_delete_extcomm_payload = \
+ {'openconfig-bgp-policy:set-ext-community': {}}
+ bgp_set_delete_comm_payload_contents = \
+ bgp_set_delete_extcomm_payload[
+ 'openconfig-bgp-policy:set-ext-community']
+ bgp_set_delete_comm_payload_contents['config'] = \
+ {'method': 'INLINE', 'options': 'REMOVE'}
+ bgp_set_delete_comm_payload_contents['inline'] = \
+ {'config': {'communities': set_extcommunity_delete_attrs}}
+
+ request = {
+ 'path': bgp_set_delete_extcomm_uri,
+ 'method': PATCH,
+ 'data': bgp_set_delete_extcomm_payload
+ }
+ dict_delete_requests.append(request)
+
+ # Check for deletion of ipv6_next_hop attributes. Delete the attributes
+ # in the currently configured ipv6_next_hop dict list if they exist.
+ # As an optimization, avoid deleting attributes that will be replaced
+ # by the received command.
+ ipv6_next_hop_deleted_members = {}
+ if 'ipv6_next_hop' not in cfg_set_top:
+ if command['set'].get('ipv6_next_hop'):
+ command['set'].pop('ipv6_next_hop')
+ if command['set'] is None:
+ command.pop('set')
+ return
+ else:
+ # Delete eligible configured ipv6_next_hop members.
+ cfg_ipv6_next_hop_key_set = set(cfg_set_top['ipv6_next_hop'].keys())
+ cmd_ipv6_next_hop_key_set = ([])
+ if cmd_set_top.get('ipv6_next_hop'):
+ cmd_ipv6_next_hop_key_set = set(cfg_set_top['ipv6_next_hop'].keys())
+ command['set'].pop('ipv6_next_hop')
+
+ set_uri = set_uri_attr['ipv6_next_hop']
+ for ipv6_next_hop_key in cfg_ipv6_next_hop_key_set.difference(cmd_ipv6_next_hop_key_set):
+ ipv6_next_hop_deleted_members[ipv6_next_hop_key] = \
+ cfg_set_top['ipv6_next_hop'][ipv6_next_hop_key]
+ request = {'path': set_uri[ipv6_next_hop_key], 'method': DELETE}
+ dict_delete_requests.append(request)
+
+ if ipv6_next_hop_deleted_members:
+ # Update the list of deleted ipv6_next_hop attributes in the "command" dict.
+ if not cmd_set_top.get('ipv6_next_hop'):
+ command['set']['ipv6_next_hop'] = {}
+ command['set']['ipv6_next_hop'] = ipv6_next_hop_deleted_members
+
+ if dict_delete_requests:
+ requests.extend(dict_delete_requests)
+
+ return
+
+ # If no top level attribute changes were requested, check for changes in
+ # dictionaries nested below the top level.
+ # -----------------------------------------------------------------------
+
+ # Check for replacement of set "community" lists. Delete the items in
+ # the currently configured list if it exists and any items for that
+ # list are specified in the received command.
+ dict_delete_requests = []
+ set_community_delete_attrs = []
+ if 'community' in cmd_set_top:
+ if 'community' not in cfg_set_top:
+ command['set'].pop('community')
+ if command['set'] is None:
+ command.pop('set')
+ return
+ else:
+ if 'community_number' in cmd_set_top['community']:
+ set_community_number_deletions = []
+ if 'community_number' in cfg_set_top['community']:
+ symmetric_diff_set = \
+ (set(cmd_set_top['community']['community_number']).symmetric_difference(
+ set(cfg_set_top['community']['community_number'])))
+ if symmetric_diff_set:
+ for community_number in cfg_set_top['community']['community_number']:
+ if (community_number not in cmd_set_top['community']
+ ['community_number']):
+ set_community_delete_attrs.append(community_number)
+ set_community_number_deletions.append(community_number)
+ command['set']['community'].pop('community_number')
+ if set_community_delete_attrs:
+ command['set']['community']['community_number'] = \
+ set_community_number_deletions
+
+ if 'community_attributes' in cmd_set_top['community']:
+ set_community_named_attr_deletions = []
+ if 'community_attributes' in cfg_set_top['community']:
+ symmetric_diff_set = \
+ (set(cmd_set_top[
+ 'community']['community_attributes']).symmetric_difference(
+ set(cfg_set_top['community']['community_attributes'])))
+ if symmetric_diff_set:
+ cfg_set_top_comm_attr = cfg_set_top['community']['community_attributes']
+ for community_attr in cfg_set_top_comm_attr:
+ if (community_attr not in cmd_set_top['community']
+ ['community_attributes']):
+ set_community_delete_attrs.append(
+ self.set_community_rest_names[community_attr])
+ set_community_named_attr_deletions.append(community_attr)
+ command['set']['community'].pop('community_attributes')
+ if set_community_named_attr_deletions:
+ command['set']['community']['community_attributes'] = \
+ set_community_named_attr_deletions
+ if command['set']['community'] is None:
+ command['set'].pop('community')
+
+ # Format and enqueue a request to delete eligible community attributes
+ if set_community_delete_attrs:
+ bgp_set_delete_community_uri = bgp_set_delete_req_base + 'set-community'
+ bgp_set_delete_comm_payload = \
+ {'openconfig-bgp-policy:set-community': {}}
+ bgp_set_delete_comm_payload_contents = \
+ bgp_set_delete_comm_payload['openconfig-bgp-policy:set-community']
+ bgp_set_delete_comm_payload_contents['config'] = \
+ {'method': 'INLINE', 'options': 'REMOVE'}
+ bgp_set_delete_comm_payload_contents['inline'] = \
+ {'config': {'communities': set_community_delete_attrs}}
+
+ request = {
+ 'path': bgp_set_delete_community_uri,
+ 'method': PATCH,
+ 'data': bgp_set_delete_comm_payload
+ }
+ dict_delete_requests.append(request)
+
+ # Check for replacement of set "extcommunity" lists. Delete any items in
+ # the currently configured list if the corresponding item is not
+ # specified in the received command.
+ set_extcommunity_delete_attrs = []
+ if 'extcommunity' in cmd_set_top:
+ if 'extcommunity' not in cfg_set_top:
+ command['set'].pop('extcommunity')
+ else:
+ for extcomm_type in self.set_extcomm_rest_names:
+ set_extcommunity_delete_attrs_type = []
+ if cmd_set_top['extcommunity'].get(extcomm_type):
+ if extcomm_type in cfg_set_top['extcommunity']:
+ symmetric_diff_set = \
+ (set(
+ cmd_set_top['extcommunity'][extcomm_type]).symmetric_difference(
+ set(cfg_set_top['extcommunity'][extcomm_type])))
+ if symmetric_diff_set:
+ # Append eligible entries to the delete list.
+ for extcomm_number in cfg_set_top['extcommunity'][extcomm_type]:
+ if (extcomm_number not in
+ cmd_set_top['extcommunity'][extcomm_type]):
+ set_extcommunity_delete_attrs.append(
+ self.set_extcomm_rest_names[extcomm_type] +
+ extcomm_number)
+ set_extcommunity_delete_attrs_type.append(extcomm_number)
+ # Replace the requested extcommunity numbers for this type with the list of
+ # deleted extcommunity numbers (if any) for this type.
+ command['set']['extcommunity'].pop(extcomm_type)
+ if set_extcommunity_delete_attrs_type:
+ command['set']['extcommunity'][extcomm_type] = \
+ set_extcommunity_delete_attrs_type
+
+ if command['set']['extcommunity'] is None:
+ command['set'].pop('extcommunity')
+
+ # Format and enqueue a request to delete eligible extcommunity attributes
+ if set_extcommunity_delete_attrs:
+ bgp_set_delete_extcomm_uri = bgp_set_delete_req_base + 'set-ext-community'
+ bgp_set_delete_extcomm_payload = \
+ {'openconfig-bgp-policy:set-ext-community': {}}
+ bgp_set_delete_comm_payload_contents = \
+ bgp_set_delete_extcomm_payload[
+ 'openconfig-bgp-policy:set-ext-community']
+ bgp_set_delete_comm_payload_contents['config'] = \
+ {'method': 'INLINE', 'options': 'REMOVE'}
+ bgp_set_delete_comm_payload_contents['inline'] = \
+ {'config': {'communities': set_extcommunity_delete_attrs}}
+
+ request = {
+ 'path': bgp_set_delete_extcomm_uri,
+ 'method': PATCH,
+ 'data': bgp_set_delete_extcomm_payload
+ }
+ dict_delete_requests.append(request)
+
+ # If the "replaced" command set includes ipv6_next_hop attributes that
+ # differ from the currently configured attributes, delete
+ # ipv6_next_hop configuration, if it exists, for any ipv6_next hop
+ # attributes that are not specified in the received command.
+ if 'ipv6_next_hop' in cmd_set_top:
+ ipv6_next_hop_deleted_members = {}
+ if 'ipv6_next_hop' in cfg_set_top:
+ symmetric_diff_set = \
+ (set(cmd_set_top['ipv6_next_hop'].keys()).symmetric_difference(
+ set(cfg_set_top['ipv6_next_hop'].keys())))
+ intersection_diff_set = \
+ (set(cmd_set_top['ipv6_next_hop'].keys()).intersection(
+ set(cfg_set_top['ipv6_next_hop'].keys())))
+ if (symmetric_diff_set or
+ (any(keyname for keyname in intersection_diff_set if
+ cmd_set_top['ipv6_next_hop'][keyname] !=
+ cfg_set_top['ipv6_next_hop'][keyname]))):
+ set_uri = set_uri_attr['ipv6_next_hop']
+ for member_key in set_uri:
+ if (cfg_set_top['ipv6_next_hop'].get(member_key) is not None and
+ cmd_set_top['ipv6_next_hop'].get(member_key) is None):
+ ipv6_next_hop_deleted_members[member_key] = \
+ cfg_set_top['ipv6_next_hop'][member_key]
+ request = {'path': set_uri[member_key], 'method': DELETE}
+ dict_delete_requests.append(request)
+ command['set'].pop('ipv6_next_hop')
+ if ipv6_next_hop_deleted_members:
+ command['set']['ipv6_next_hop'] = ipv6_next_hop_deleted_members
+
+ if dict_delete_requests:
+ requests.extend(dict_delete_requests)
+
+ def validate_and_normalize_config(self, input_config_list):
+ '''For each input route map dict in the input_config_list list,
+ remove empty entries, validate the contents of the dict against the
+ argspec constraints for route maps, and convert input interface names to
+ the format required for the currently configured interface naming
+ mode.'''
+ updated_config_list = remove_empties_from_list(input_config_list)
+ validate_config(self._module.argument_spec, {'config': updated_config_list})
+
+ # - Verify that parameters required for most "states" are present in
+ # each dict in the input list.
+ # - Check for interface names in the input configuration and
+ # perform any needed reformatting of the names.
+ for route_map in updated_config_list:
+
+ # Verify the presence of a "sequence number" and "action" value
+ # for all states other than "deleted"
+ if self._module.params['state'] != 'deleted':
+ check_required(self._module, ['action', 'sequence_num'], route_map, ['config'])
+
+ # Check for interface names requiring re-formatting.
+ if not route_map.get('match'):
+ continue
+
+ if route_map['match'].get('interface'):
+ intf_name = route_map['match']['interface']
+ updated_intf_name = get_normalize_interface_name(intf_name, self._module)
+ route_map['match']['interface'] = updated_intf_name
+
+ if route_map['match'].get('peer') and route_map['match']['peer'].get('interface'):
+ intf_name = route_map['match']['peer']['interface']
+ updated_intf_name = get_normalize_interface_name(intf_name, self._module)
+ route_map['match']['peer']['interface'] = updated_intf_name
+
+ return updated_config_list
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/static_routes/static_routes.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/static_routes/static_routes.py
index 047357470..c3d62d852 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/static_routes/static_routes.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/static_routes/static_routes.py
@@ -28,6 +28,12 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
update_states,
get_diff,
+ get_replaced_config,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ get_new_config,
+ get_formatted_config_diff
)
network_instance_path = '/data/openconfig-network-instance:network-instances/network-instance'
@@ -41,6 +47,61 @@ TEST_KEYS = [
{'next_hops': {'index': ''}},
]
+is_delete_all = False
+
+
+def __derive_static_route_next_hop_config_key_match_op(key_set, command, exist_conf):
+ bh = command['index'].get('blackhole', None)
+ itf = command['index'].get('interface', None)
+ nv = command['index'].get('nexthop_vrf', None)
+ nh = command['index'].get('next_hop', None)
+ conf_bh = exist_conf['index'].get('blackhole', None)
+ conf_itf = exist_conf['index'].get('interface', None)
+ conf_nv = exist_conf['index'].get('nexthop_vrf', None)
+ conf_nh = exist_conf['index'].get('next_hop', None)
+
+ if bh == conf_bh and itf == conf_itf and nv == conf_nv and nh == conf_nh:
+ return True
+ else:
+ return False
+
+
+def __derive_static_route_next_hop_config_delete_op(key_set, command, exist_conf):
+ new_conf = []
+
+ if is_delete_all:
+ return True, new_conf
+
+ metric = command.get('metric', None)
+ tag = command.get('tag', None)
+ track = command.get('track', None)
+
+ if metric is None and tag is None and track is None:
+ return True, new_conf
+
+ new_conf = exist_conf
+
+ conf_metric = new_conf.get('metric', None)
+ conf_tag = new_conf.get('tag', None)
+ conf_track = new_conf.get('track', None)
+
+ if metric == conf_metric:
+ new_conf['metric'] = None
+ if tag == conf_tag:
+ new_conf['tag'] = None
+ if track == conf_track:
+ new_conf['track'] = None
+
+ return True, new_conf
+
+
+TEST_KEYS_formatted_diff = [
+ {'config': {'vrf_name': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+ {'static_list': {'prefix': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+ {'next_hops': {'index': '', '__delete_op': __derive_static_route_next_hop_config_delete_op,
+ '__key_match_op': __derive_static_route_next_hop_config_key_match_op}}
+]
+
class Static_routes(ConfigBase):
"""
@@ -97,6 +158,21 @@ class Static_routes(ConfigBase):
if result['changed']:
result['after'] = changed_static_routes_facts
+ new_config = changed_static_routes_facts
+ old_config = existing_static_routes_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_static_routes_facts,
+ TEST_KEYS_formatted_diff)
+ self.post_process_generated_config(new_config)
+ result['after(generated)'] = new_config
+
+ if self._module._diff:
+ self.sort_lists_in_config(new_config)
+ self.sort_lists_in_config(old_config)
+ result['diff'] = get_formatted_config_diff(old_config,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
@@ -132,10 +208,14 @@ class Static_routes(ConfigBase):
if state == 'deleted':
commands, requests = self._state_deleted(want, have, diff)
elif state == 'merged':
- commands, requests = self._state_merged(want, have, diff)
+ commands, requests = self._state_merged(diff)
+ elif state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have, diff)
return commands, requests
- def _state_merged(self, want, have, diff):
+ def _state_merged(self, diff):
""" The command generator when state is merged
:rtype: A list
@@ -143,7 +223,7 @@ class Static_routes(ConfigBase):
the current configuration
"""
commands = diff
- requests = self.get_modify_static_routes_requests(commands, have)
+ requests = self.get_modify_static_routes_requests(commands)
if commands and len(requests) > 0:
commands = update_states(commands, "merged")
@@ -159,6 +239,7 @@ class Static_routes(ConfigBase):
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
+ global is_delete_all
is_delete_all = False
# if want is none, then delete ALL
if not want:
@@ -176,7 +257,73 @@ class Static_routes(ConfigBase):
return commands, requests
- def get_modify_static_routes_requests(self, commands, have):
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ global is_delete_all
+
+ commands = []
+ requests = []
+ self.sort_lists_in_config(want)
+ self.sort_lists_in_config(have)
+
+ if have and have != want:
+ is_delete_all = True
+ del_requests = self.get_delete_static_routes_requests(have, None, is_delete_all)
+ requests.extend(del_requests)
+ commands.extend(update_states(have, "deleted"))
+ have = []
+
+ if not have and want:
+ mod_commands = want
+ mod_requests = self.get_modify_static_routes_requests(mod_commands)
+
+ if len(mod_requests) > 0:
+ requests.extend(mod_requests)
+ commands.extend(update_states(mod_commands, "overridden"))
+
+ return commands, requests
+
+ def _state_replaced(self, want, have, diff):
+ """ The command generator when state is replaced
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ global is_delete_all
+
+ commands = []
+ requests = []
+ replaced_config = get_replaced_config(want, have, TEST_KEYS)
+
+ mod_commands = []
+ if replaced_config:
+ self.sort_lists_in_config(replaced_config)
+ self.sort_lists_in_config(have)
+ is_delete_all = (replaced_config == have)
+ del_requests = self.get_delete_static_routes_requests(replaced_config, have, is_delete_all)
+ requests.extend(del_requests)
+ commands.extend(update_states(replaced_config, "deleted"))
+ mod_commands = want
+ else:
+ mod_commands = diff
+
+ if mod_commands:
+ mod_requests = self.get_modify_static_routes_requests(mod_commands)
+
+ if len(mod_requests) > 0:
+ requests.extend(mod_requests)
+ commands.extend(update_states(mod_commands, "replaced"))
+
+ return commands, requests
+
+ def get_modify_static_routes_requests(self, commands):
requests = []
if not commands:
@@ -208,7 +355,7 @@ class Static_routes(ConfigBase):
idx = self.generate_index(index)
if idx:
next_hop_cfg['index'] = idx
- if blackhole:
+ if blackhole is not None:
next_hop_cfg['blackhole'] = blackhole
if nexthop_vrf:
next_hop_cfg['network-instance'] = nexthop_vrf
@@ -342,3 +489,33 @@ class Static_routes(ConfigBase):
request = {'path': url, 'method': DELETE}
return request
+
+ def sort_lists_in_config(self, config):
+ if config:
+ config.sort(key=self.get_vrf_name)
+ for cfg in config:
+ if 'static_list' in cfg and cfg['static_list']:
+ cfg['static_list'].sort(key=self.get_prefix)
+ for rt in cfg['static_list']:
+ if 'next_hops' in rt and rt['next_hops']:
+ rt['next_hops'].sort(key=lambda x: (x['index'].get('blackhole', None) is not None,
+ x['index'].get('interface', None) is not None,
+ x['index'].get('nexthop_vrf', None) is not None,
+ x['index'].get('next_hop', None) is not None))
+
+ def get_vrf_name(self, vrf_name):
+ return vrf_name.get('vrf_name')
+
+ def get_prefix(self, prefix):
+ return prefix.get('prefix')
+
+ def post_process_generated_config(self, configs):
+ for conf in configs[:]:
+ sls = conf.get('static_list', [])
+ if sls:
+ for sl in sls[:]:
+ if not sl.get('next_hops', []):
+ sls.remove(sl)
+
+ if not conf.get('static_list', []):
+ configs.remove(conf)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/stp/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/stp/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/stp/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/stp/stp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/stp/stp.py
new file mode 100644
index 000000000..031c794ae
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/stp/stp.py
@@ -0,0 +1,1404 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_stp class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import re
+from copy import deepcopy
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ update_states,
+ get_ranges_in_list,
+ get_diff,
+ remove_empties,
+)
+from ansible.module_utils.connection import ConnectionError
+
+
+PATCH = 'patch'
+DELETE = 'delete'
+TEST_KEYS = [
+ {'interfaces': {'intf_name': ''}},
+ {'mst_instances': {'mst_id': ''}},
+ {'pvst': {'vlan_id': ''}},
+ {'rapid_pvst': {'vlan_id': ''}},
+]
+STP_PATH = 'data/openconfig-spanning-tree:stp'
+stp_map = {
+ True: 'EDGE_ENABLE',
+ False: 'EDGE_DISABLE',
+ 'mst': 'MSTP',
+ 'pvst': 'PVST',
+ 'rapid_pvst': 'RAPID_PVST',
+ 'point-to-point': 'P2P',
+ 'shared': 'SHARED',
+ 'loop': 'LOOP',
+ 'root': 'ROOT',
+ 'none': 'NONE'
+}
+
+
+class Stp(ConfigBase):
+ """
+ The sonic_stp class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'stp',
+ ]
+
+ def __init__(self, module):
+ super(Stp, self).__init__(module)
+
+ def get_stp_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ stp_facts = facts['ansible_network_resources'].get('stp')
+ if not stp_facts:
+ return []
+ return stp_facts
+
+ def execute_module(self):
+ """ Execute the module
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = []
+ commands = []
+
+ existing_stp_facts = self.get_stp_facts()
+ commands, requests = self.set_config(existing_stp_facts)
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_stp_facts = self.get_stp_facts()
+
+ result['before'] = existing_stp_facts
+ if result['changed']:
+ result['after'] = changed_stp_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_stp_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params['config']
+ have = existing_stp_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ state = self._module.params['state']
+ diff = get_diff(want, have, TEST_KEYS)
+
+ if state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'merged':
+ commands, requests = self._state_merged(diff, have)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have, diff)
+ return commands, requests
+
+ def _state_replaced(self, want, have, diff):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ mod_commands = []
+ replaced_config, requests = self.get_replaced_config(want, have)
+
+ if replaced_config:
+ commands.extend(update_states(replaced_config, "deleted"))
+ mod_commands = want
+ else:
+ mod_commands = diff
+
+ if mod_commands:
+ mod_requests = self.get_modify_stp_requests(mod_commands, have)
+ if len(mod_requests) > 0:
+ requests.extend(mod_requests)
+ commands.extend(update_states(mod_commands, "replaced"))
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ del_commands = get_diff(have, want, TEST_KEYS)
+ self.remove_default_entries(del_commands)
+ del_commands = remove_empties(del_commands)
+
+ if del_commands:
+ is_delete_all = True
+ del_requests = self.get_delete_stp_requests(del_commands, have, is_delete_all)
+ requests.extend(del_requests)
+ commands.extend(update_states(have, "deleted"))
+ have = {}
+
+ if not have and want:
+ mod_commands = want
+ mod_requests = self.get_modify_stp_requests(mod_commands, have)
+
+ if len(mod_requests) > 0:
+ requests.extend(mod_requests)
+ commands.extend(update_states(mod_commands, "overridden"))
+ return commands, requests
+
+ def _state_merged(self, diff, have):
+ """ The command generator when state is merged
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = diff
+ requests = self.get_modify_stp_requests(commands, have)
+ commands = remove_empties(commands)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "merged")
+ else:
+ commands = []
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ is_delete_all = False
+ want = remove_empties(want)
+
+ if not want:
+ commands = deepcopy(have)
+ is_delete_all = True
+ else:
+ commands = deepcopy(want)
+
+ self.remove_default_entries(commands)
+ commands = remove_empties(commands)
+ requests = self.get_delete_stp_requests(commands, have, is_delete_all)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+ return commands, requests
+
+ def get_modify_stp_requests(self, commands, have):
+ requests = []
+
+ if not commands:
+ return requests
+
+ global_request = self.get_modify_stp_global_request(commands, have)
+ interfaces_request = self.get_modify_stp_interfaces_request(commands)
+ mstp_requests = self.get_modify_stp_mstp_request(commands, have)
+ pvst_request = self.get_modify_stp_pvst_request(commands)
+ rapid_pvst_request = self.get_modify_stp_rapid_pvst_request(commands)
+
+ if global_request:
+ requests.append(global_request)
+ if interfaces_request:
+ requests.append(interfaces_request)
+ if mstp_requests:
+ requests.append(mstp_requests)
+ if pvst_request:
+ requests.append(pvst_request)
+ if rapid_pvst_request:
+ requests.append(rapid_pvst_request)
+
+ return requests
+
+ def get_modify_stp_global_request(self, commands, have):
+ request = None
+
+ if not commands:
+ return request
+
+ stp_global = commands.get('global', None)
+ if stp_global:
+ global_dict = {}
+ config_dict = {}
+ enabled_protocol = stp_global.get('enabled_protocol', None)
+ loop_guard = stp_global.get('loop_guard', None)
+ bpdu_filter = stp_global.get('bpdu_filter', None)
+ disabled_vlans = stp_global.get('disabled_vlans', None)
+ root_guard_timeout = stp_global.get('root_guard_timeout', None)
+ portfast = stp_global.get('portfast', None)
+ hello_time = stp_global.get('hello_time', None)
+ max_age = stp_global.get('max_age', None)
+ fwd_delay = stp_global.get('fwd_delay', None)
+ bridge_priority = stp_global.get('bridge_priority', None)
+
+ if enabled_protocol:
+ config_dict['enabled-protocol'] = [stp_map[enabled_protocol]]
+ if loop_guard is not None:
+ config_dict['loop-guard'] = loop_guard
+ if bpdu_filter is not None:
+ config_dict['bpdu-filter'] = bpdu_filter
+ if disabled_vlans:
+ if have:
+ cfg_stp_global = have.get('global', None)
+ if cfg_stp_global:
+ cfg_disabled_vlans = cfg_stp_global.get('disabled_vlans', None)
+ if cfg_disabled_vlans:
+ disabled_vlans = self.get_vlans_diff(disabled_vlans, cfg_disabled_vlans)
+ if not disabled_vlans:
+ commands['global'].pop('disabled_vlans')
+ if disabled_vlans:
+ config_dict['openconfig-spanning-tree-ext:disabled-vlans'] = self.convert_vlans_list(disabled_vlans)
+ if root_guard_timeout:
+ config_dict['openconfig-spanning-tree-ext:rootguard-timeout'] = root_guard_timeout
+ if portfast is not None and enabled_protocol == 'pvst':
+ config_dict['openconfig-spanning-tree-ext:portfast'] = portfast
+ elif portfast:
+ self._module.fail_json(msg='Portfast only configurable for pvst protocol.')
+ if hello_time:
+ config_dict['openconfig-spanning-tree-ext:hello-time'] = hello_time
+ if max_age:
+ config_dict['openconfig-spanning-tree-ext:max-age'] = max_age
+ if fwd_delay:
+ config_dict['openconfig-spanning-tree-ext:forwarding-delay'] = fwd_delay
+ if bridge_priority:
+ config_dict['openconfig-spanning-tree-ext:bridge-priority'] = bridge_priority
+ if config_dict:
+ global_dict['config'] = config_dict
+ url = '%s/global' % (STP_PATH)
+ payload = {'openconfig-spanning-tree:global': global_dict}
+ request = {'path': url, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_modify_stp_interfaces_request(self, commands):
+ request = None
+ interfaces = commands.get('interfaces', None)
+
+ if interfaces:
+ intf_list = []
+ for intf in interfaces:
+ intf_dict = {}
+ config_dict = {}
+ intf_name = intf.get('intf_name', None)
+ edge_port = intf.get('edge_port', None)
+ link_type = intf.get('link_type', None)
+ guard = intf.get('guard', None)
+ bpdu_guard = intf.get('bpdu_guard', None)
+ bpdu_filter = intf.get('bpdu_filter', None)
+ portfast = intf.get('portfast', None)
+ uplink_fast = intf.get('uplink_fast', None)
+ shutdown = intf.get('shutdown', None)
+ cost = intf.get('cost', None)
+ port_priority = intf.get('port_priority', None)
+ stp_enable = intf.get('stp_enable', None)
+
+ if intf_name:
+ config_dict['name'] = intf_name
+ if edge_port is not None:
+ config_dict['edge-port'] = stp_map[edge_port]
+ if link_type:
+ config_dict['link-type'] = stp_map[link_type]
+ if guard:
+ config_dict['guard'] = stp_map[guard]
+ if bpdu_guard is not None:
+ config_dict['bpdu-guard'] = bpdu_guard
+ if bpdu_filter is not None:
+ config_dict['bpdu-filter'] = bpdu_filter
+ if portfast is not None:
+ config_dict['openconfig-spanning-tree-ext:portfast'] = portfast
+ if uplink_fast is not None:
+ config_dict['openconfig-spanning-tree-ext:uplink-fast'] = uplink_fast
+ if shutdown is not None:
+ config_dict['openconfig-spanning-tree-ext:bpdu-guard-port-shutdown'] = shutdown
+ if cost:
+ config_dict['openconfig-spanning-tree-ext:cost'] = cost
+ if port_priority:
+ config_dict['openconfig-spanning-tree-ext:port-priority'] = port_priority
+ if stp_enable is not None:
+ config_dict['openconfig-spanning-tree-ext:spanning-tree-enable'] = stp_enable
+ if config_dict:
+ intf_dict['name'] = intf_name
+ intf_dict['config'] = config_dict
+ intf_list.append(intf_dict)
+ if intf_list:
+ url = '%s/interfaces' % (STP_PATH)
+ payload = {'openconfig-spanning-tree:interfaces': {'interface': intf_list}}
+ request = {'path': url, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_modify_stp_mstp_request(self, commands, have):
+ request = None
+
+ if not commands:
+ return request
+
+ mstp = commands.get('mstp', None)
+
+ if mstp:
+ mstp_dict = {}
+ config_dict = {}
+ mst_name = mstp.get('mst_name', None)
+ revision = mstp.get('revision', None)
+ max_hop = mstp.get('max_hop', None)
+ hello_time = mstp.get('hello_time', None)
+ max_age = mstp.get('max_age', None)
+ fwd_delay = mstp.get('fwd_delay', None)
+ mst_instances = mstp.get('mst_instances', None)
+
+ if mst_name:
+ config_dict['name'] = mst_name
+ if revision:
+ config_dict['revision'] = revision
+ if max_hop:
+ config_dict['max-hop'] = max_hop
+ if hello_time:
+ config_dict['hello-time'] = hello_time
+ if max_age:
+ config_dict['max-age'] = max_age
+ if fwd_delay:
+ config_dict['forwarding-delay'] = fwd_delay
+ if mst_instances:
+ mst_inst_list = []
+ pop_list = []
+ for mst in mst_instances:
+ mst_inst_dict = {}
+ mst_cfg_dict = {}
+ mst_index = mst_instances.index(mst)
+ mst_id = mst.get('mst_id', None)
+ bridge_priority = mst.get('bridge_priority', None)
+ interfaces = mst.get('interfaces', None)
+ vlans = mst.get('vlans', None)
+
+ if mst_id:
+ mst_cfg_dict['mst-id'] = mst_id
+ if bridge_priority:
+ mst_cfg_dict['bridge-priority'] = bridge_priority
+ if interfaces:
+ intf_list = self.get_interfaces_list(interfaces)
+ if intf_list:
+ mst_inst_dict['interfaces'] = {'interface': intf_list}
+ if vlans:
+ if have:
+ cfg_mstp = have.get('mstp', None)
+ if cfg_mstp:
+ cfg_mst_instances = cfg_mstp.get('mst_instances', None)
+ if cfg_mst_instances:
+ for cfg_mst in cfg_mst_instances:
+ cfg_mst_id = cfg_mst.get('mst_id', None)
+ cfg_vlans = cfg_mst.get('vlans', None)
+
+ if mst_id == cfg_mst_id and cfg_vlans:
+ vlans = self.get_vlans_diff(vlans, cfg_vlans)
+ if not vlans:
+ pop_list.insert(0, mst_index)
+ if vlans:
+ mst_cfg_dict['vlan'] = self.convert_vlans_list(vlans)
+ if mst_cfg_dict:
+ mst_inst_dict['mst-id'] = mst_id
+ mst_inst_dict['config'] = mst_cfg_dict
+ if mst_inst_dict:
+ mst_inst_list.append(mst_inst_dict)
+ if pop_list:
+ for i in pop_list:
+ commands['mstp']['mst_instances'][i].pop('vlans')
+ if mst_inst_list:
+ mstp_dict['mst-instances'] = {'mst-instance': mst_inst_list}
+
+ if config_dict:
+ mstp_dict['config'] = config_dict
+
+ if mstp_dict:
+ url = '%s/mstp' % (STP_PATH)
+ payload = {'openconfig-spanning-tree:mstp': mstp_dict}
+ request = {'path': url, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_modify_stp_pvst_request(self, commands):
+ request = None
+ pvst = commands.get('pvst', None)
+
+ if pvst:
+ vlans_list = self.get_vlans_list(pvst)
+ if vlans_list:
+ url = '%s/openconfig-spanning-tree-ext:pvst' % (STP_PATH)
+ payload = {'openconfig-spanning-tree-ext:pvst': {'vlans': vlans_list}}
+ request = {'path': url, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_modify_stp_rapid_pvst_request(self, commands):
+ request = None
+ rapid_pvst = commands.get('rapid_pvst', None)
+
+ if rapid_pvst:
+ vlans_list = self.get_vlans_list(rapid_pvst)
+ if vlans_list:
+ url = '%s/rapid-pvst' % (STP_PATH)
+ payload = {'openconfig-spanning-tree:rapid-pvst': {'vlan': vlans_list}}
+ request = {'path': url, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_vlans_list(self, data):
+ vlans_list = []
+
+ for vlan in data:
+ vlans_dict = {}
+ config_dict = {}
+ vlan_id = vlan.get('vlan_id', None)
+ hello_time = vlan.get('hello_time', None)
+ max_age = vlan.get('max_age', None)
+ fwd_delay = vlan.get('fwd_delay', None)
+ bridge_priority = vlan.get('bridge_priority', None)
+ interfaces = vlan.get('interfaces', None)
+
+ if vlan_id:
+ config_dict['vlan-id'] = vlan_id
+ if hello_time:
+ config_dict['hello-time'] = hello_time
+ if max_age:
+ config_dict['max-age'] = max_age
+ if fwd_delay:
+ config_dict['forwarding-delay'] = fwd_delay
+ if bridge_priority:
+ config_dict['bridge-priority'] = bridge_priority
+ if interfaces:
+ intf_list = self.get_interfaces_list(interfaces)
+ if intf_list:
+ vlans_dict['interfaces'] = {'interface': intf_list}
+ if config_dict:
+ vlans_dict['vlan-id'] = vlan_id
+ vlans_dict['config'] = config_dict
+ if vlans_dict:
+ vlans_list.append(vlans_dict)
+
+ return vlans_list
+
+ def get_interfaces_list(self, interfaces):
+ intf_list = []
+ for intf in interfaces:
+ intf_dict = {}
+ intf_cfg_dict = {}
+ intf_name = intf.get('intf_name', None)
+ cost = intf.get('cost', None)
+ port_priority = intf.get('port_priority', None)
+
+ if intf_name:
+ intf_cfg_dict['name'] = intf_name
+ if cost:
+ intf_cfg_dict['cost'] = cost
+ if port_priority:
+ intf_cfg_dict['port-priority'] = port_priority
+ if intf_cfg_dict:
+ intf_dict['name'] = intf_name
+ intf_dict['config'] = intf_cfg_dict
+ intf_list.append(intf_dict)
+
+ return intf_list
+
+ def get_vlans_common(self, vlans, cfg_vlans):
+ """Returns the vlan ranges that are common in the want and have
+ vlans lists
+ """
+ vlans = self.get_vlan_id_list(vlans)
+ cfg_vlans = self.get_vlan_id_list(cfg_vlans)
+ return self.get_vlan_range_list(list(set(vlans).intersection(set(cfg_vlans))))
+
+ def get_vlans_diff(self, vlans, cfg_vlans):
+ """Returns the vlan ranges present only in the want vlans list
+ and not in the have vlans list
+ """
+ vlans = self.get_vlan_id_list(vlans)
+ cfg_vlans = self.get_vlan_id_list(cfg_vlans)
+ return self.get_vlan_range_list(list(set(vlans) - set(cfg_vlans)))
+
+ @staticmethod
+ def get_vlan_id_list(vlans):
+ """Returns a list of all VLAN IDs specified in a vlans list"""
+ vlan_id_list = []
+
+ if vlans:
+ for vlan_val in vlans:
+ if '-' in vlan_val or '..' in vlan_val:
+ start, end = re.split(r'-|\.\.', vlan_val)
+ vlan_id_list.extend(range(int(start), int(end) + 1))
+ else:
+ # Single VLAN ID
+ vlan_id_list.append(int(vlan_val))
+
+ return vlan_id_list
+
+ @staticmethod
+ def get_vlan_range_list(vlan_id_list):
+ """Returns the vlans list for a given list of VLAN IDs"""
+ vlan_range_list = []
+
+ if vlan_id_list:
+ vlan_id_list.sort()
+ for vlan_range in get_ranges_in_list(vlan_id_list):
+ vlan_range_list.append('-'.join(map(str, (vlan_range[0], vlan_range[-1])[:len(vlan_range)])))
+
+ return vlan_range_list
+
+ def convert_vlans_list(self, vlans):
+ converted_vlans = []
+
+ for vlan in vlans:
+ if len(vlan) == 1:
+ converted_vlans.append(int(vlan))
+ else:
+ converted_vlans.append(vlan.replace('-', '..'))
+
+ return converted_vlans
+
+ def get_delete_stp_requests(self, commands, have, is_delete_all):
+ requests = []
+
+ if not commands:
+ return requests
+
+ if is_delete_all:
+ requests.append(self.get_delete_all_stp_request())
+ else:
+ requests.extend(self.get_delete_stp_mstp_requests(commands, have))
+ requests.extend(self.get_delete_stp_pvst_requests(commands, have))
+ requests.extend(self.get_delete_stp_rapid_pvst_requests(commands, have))
+ requests.extend(self.get_delete_stp_interfaces_requests(commands, have))
+ requests.extend(self.get_delete_stp_global_requests(commands, have))
+
+ return requests
+
+ def get_delete_stp_global_requests(self, commands, have):
+ requests = []
+
+ stp_global = commands.get('global', None)
+ if stp_global:
+ enabled_protocol = stp_global.get('enabled_protocol', None)
+ loop_guard = stp_global.get('loop_guard', None)
+ bpdu_filter = stp_global.get('bpdu_filter', None)
+ disabled_vlans = stp_global.get('disabled_vlans', None)
+ root_guard_timeout = stp_global.get('root_guard_timeout', None)
+ portfast = stp_global.get('portfast', None)
+ hello_time = stp_global.get('hello_time', None)
+ max_age = stp_global.get('max_age', None)
+ fwd_delay = stp_global.get('fwd_delay', None)
+ bridge_priority = stp_global.get('bridge_priority', None)
+
+ cfg_stp_global = have.get('global', None)
+ if cfg_stp_global:
+ cfg_enabled_protocol = cfg_stp_global.get('enabled_protocol', None)
+ cfg_loop_guard = cfg_stp_global.get('loop_guard', None)
+ cfg_bpdu_filter = cfg_stp_global.get('bpdu_filter', None)
+ cfg_disabled_vlans = cfg_stp_global.get('disabled_vlans', None)
+ cfg_root_guard_timeout = cfg_stp_global.get('root_guard_timeout', None)
+ cfg_portfast = cfg_stp_global.get('portfast', None)
+ cfg_hello_time = cfg_stp_global.get('hello_time', None)
+ cfg_max_age = cfg_stp_global.get('max_age', None)
+ cfg_fwd_delay = cfg_stp_global.get('fwd_delay', None)
+ cfg_bridge_priority = cfg_stp_global.get('bridge_priority', None)
+
+ # Default loop_guard is false, don't delete if false
+ if loop_guard and loop_guard == cfg_loop_guard:
+ requests.append(self.get_delete_stp_global_attr('loop-guard'))
+ # Default bpdu_filter is false, don't delete if false
+ if bpdu_filter and bpdu_filter == cfg_bpdu_filter:
+ requests.append(self.get_delete_stp_global_attr('bpdu-filter'))
+ if disabled_vlans and cfg_disabled_vlans:
+ disabled_vlans_to_delete = self.get_vlans_common(disabled_vlans, cfg_disabled_vlans)
+ for i, vlan in enumerate(disabled_vlans_to_delete):
+ if '-' in vlan:
+ disabled_vlans_to_delete[i] = vlan.replace('-', '..')
+ if disabled_vlans_to_delete:
+ encoded_vlans = '%2C'.join(disabled_vlans_to_delete)
+ attr = 'openconfig-spanning-tree-ext:disabled-vlans=%s' % (encoded_vlans)
+ requests.append(self.get_delete_stp_global_attr(attr))
+ else:
+ commands['global'].pop('disabled_vlans')
+ if root_guard_timeout:
+ if root_guard_timeout == cfg_root_guard_timeout:
+ requests.append(self.get_delete_stp_global_attr('openconfig-spanning-tree-ext:rootguard-timeout'))
+ else:
+ commands['global'].pop('root_guard_timeout')
+ # Default portfast is false, don't delete if false
+ if portfast and portfast == cfg_portfast:
+ requests.append(self.get_delete_stp_global_attr('openconfig-spanning-tree-ext:portfast'))
+ if hello_time and hello_time == cfg_hello_time:
+ requests.append(self.get_delete_stp_global_attr('openconfig-spanning-tree-ext:hello-time'))
+ if max_age and max_age == cfg_max_age:
+ requests.append(self.get_delete_stp_global_attr('openconfig-spanning-tree-ext:max-age'))
+ if fwd_delay and fwd_delay == cfg_fwd_delay:
+ requests.append(self.get_delete_stp_global_attr('openconfig-spanning-tree-ext:forwarding-delay'))
+ if bridge_priority and bridge_priority == cfg_bridge_priority:
+ requests.append(self.get_delete_stp_global_attr('openconfig-spanning-tree-ext:bridge-priority'))
+ if enabled_protocol:
+ if enabled_protocol == cfg_enabled_protocol:
+ requests.append(self.get_delete_stp_global_attr('enabled-protocol'))
+ else:
+ commands['global'].pop('enabled_protocol')
+
+ return requests
+
+ def get_delete_stp_interfaces_requests(self, commands, have):
+ requests = []
+
+ interfaces = commands.get('interfaces', None)
+ if interfaces:
+ intf_list = []
+ for intf in interfaces:
+ intf_dict = {}
+ intf_name = intf.get('intf_name', None)
+ edge_port = intf.get('edge_port', None)
+ link_type = intf.get('link_type', None)
+ guard = intf.get('guard', None)
+ bpdu_guard = intf.get('bpdu_guard', None)
+ bpdu_filter = intf.get('bpdu_filter', None)
+ portfast = intf.get('portfast', None)
+ uplink_fast = intf.get('uplink_fast', None)
+ shutdown = intf.get('shutdown', None)
+ cost = intf.get('cost', None)
+ port_priority = intf.get('port_priority', None)
+ stp_enable = intf.get('stp_enable', None)
+
+ cfg_interfaces = have.get('interfaces', None)
+ if cfg_interfaces:
+ for cfg_intf in cfg_interfaces:
+ cfg_intf_name = cfg_intf.get('intf_name', None)
+ cfg_edge_port = cfg_intf.get('edge_port', None)
+ cfg_link_type = cfg_intf.get('link_type', None)
+ cfg_guard = cfg_intf.get('guard', None)
+ cfg_bpdu_guard = cfg_intf.get('bpdu_guard', None)
+ cfg_bpdu_filter = cfg_intf.get('bpdu_filter', None)
+ cfg_portfast = cfg_intf.get('portfast', None)
+ cfg_uplink_fast = cfg_intf.get('uplink_fast', None)
+ cfg_shutdown = cfg_intf.get('shutdown', None)
+ cfg_cost = cfg_intf.get('cost', None)
+ cfg_port_priority = cfg_intf.get('port_priority', None)
+ cfg_stp_enable = cfg_intf.get('stp_enable', None)
+
+ if intf_name and intf_name == cfg_intf_name:
+ # Default edge_port is false, don't delete if false
+ if edge_port and edge_port == cfg_edge_port:
+ requests.append(self.get_delete_stp_interface_attr(intf_name, 'edge-port'))
+ intf_dict.update({'intf_name': intf_name, 'edge_port': edge_port})
+ if link_type and link_type == cfg_link_type:
+ requests.append(self.get_delete_stp_interface_attr(intf_name, 'link-type'))
+ intf_dict.update({'intf_name': intf_name, 'link_type': link_type})
+ if guard and guard == cfg_guard:
+ requests.append(self.get_delete_stp_interface_attr(intf_name, 'guard'))
+ intf_dict.update({'intf_name': intf_name, 'guard': guard})
+ # Default bpdu_guard is false, don't delete if false
+ if bpdu_guard and bpdu_guard == cfg_bpdu_guard:
+ url = '%s/interfaces/interface=%s/config/bpdu-guard' % (STP_PATH, intf_name)
+ payload = {'openconfig-spanning-tree:bpdu-guard': False}
+ request = {'path': url, 'method': PATCH, 'data': payload}
+ requests.append(request)
+ intf_dict.update({'intf_name': intf_name, 'bpdu_guard': bpdu_guard})
+ # Default bpdu_filter is false, don't delete if false
+ if bpdu_filter and bpdu_filter == cfg_bpdu_filter:
+ requests.append(self.get_delete_stp_interface_attr(intf_name, 'bpdu-filter'))
+ intf_dict.update({'intf_name': intf_name, 'bpdu_filter': bpdu_filter})
+ # Default portfast is false, don't delete if false
+ if portfast and portfast == cfg_portfast:
+ requests.append(self.get_delete_stp_interface_attr(intf_name, 'openconfig-spanning-tree-ext:portfast'))
+ intf_dict.update({'intf_name': intf_name, 'portfast': portfast})
+ # Default uplink_fast is false, don't delete if false
+ if uplink_fast and uplink_fast == cfg_uplink_fast:
+ url = '%s/interfaces/interface=%s/config/openconfig-spanning-tree-ext:uplink-fast' % (STP_PATH, intf_name)
+ payload = {'openconfig-spanning-tree-ext:uplink-fast': False}
+ request = {'path': url, 'method': PATCH, 'data': payload}
+ requests.append(request)
+ intf_dict.update({'intf_name': intf_name, 'uplink_fast': uplink_fast})
+ # Default shutdown is false, don't delete if false
+ if shutdown and shutdown == cfg_shutdown:
+ url = '%s/interfaces/interface=%s/config/openconfig-spanning-tree-ext:bpdu-guard-port-shutdown' % (STP_PATH, intf_name)
+ payload = {'openconfig-spanning-tree-ext:bpdu-guard-port-shutdown': False}
+ request = {'path': url, 'method': PATCH, 'data': payload}
+ requests.append(request)
+ intf_dict.update({'intf_name': intf_name, 'shutdown': shutdown})
+ if cost and cost == cfg_cost:
+ requests.append(self.get_delete_stp_interface_attr(intf_name, 'openconfig-spanning-tree-ext:cost'))
+ intf_dict.update({'intf_name': intf_name, 'cost': cost})
+ if port_priority and port_priority == cfg_port_priority:
+ requests.append(self.get_delete_stp_interface_attr(intf_name, 'openconfig-spanning-tree-ext:port-priority'))
+ intf_dict.update({'intf_name': intf_name, 'port_priority': port_priority})
+ # Default stp_enable is true, don't delete if true
+ if stp_enable is False and stp_enable == cfg_stp_enable:
+ url = '%s/interfaces/interface=%s/config/openconfig-spanning-tree-ext:spanning-tree-enable' % (STP_PATH, intf_name)
+ payload = {'openconfig-spanning-tree-ext:spanning-tree-enable': True}
+ request = {'path': url, 'method': PATCH, 'data': payload}
+ requests.append(request)
+ intf_dict.update({'intf_name': intf_name, 'stp_enable': stp_enable})
+ if (edge_port is None and not link_type and not guard and bpdu_guard is None and bpdu_filter is None and portfast is None and
+ uplink_fast is None and shutdown is None and not cost and not port_priority and stp_enable is None):
+ requests.append(self.get_delete_stp_interface(intf_name))
+ intf_dict.update({'intf_name': intf_name})
+ if intf_dict:
+ intf_list.append(intf_dict)
+ if intf_list:
+ commands['interfaces'] = intf_list
+ else:
+ commands.pop('interfaces')
+
+ return requests
+
+ def get_delete_stp_mstp_requests(self, commands, have):
+ requests = []
+
+ mstp = commands.get('mstp', None)
+ if mstp:
+ mst_name = mstp.get('mst_name', None)
+ revision = mstp.get('revision', None)
+ max_hop = mstp.get('max_hop', None)
+ hello_time = mstp.get('hello_time', None)
+ max_age = mstp.get('max_age', None)
+ fwd_delay = mstp.get('fwd_delay', None)
+ mst_instances = mstp.get('mst_instances', None)
+
+ cfg_mstp = have.get('mstp', None)
+ if cfg_mstp:
+ cfg_mst_name = cfg_mstp.get('mst_name', None)
+ cfg_revision = cfg_mstp.get('revision', None)
+ cfg_max_hop = cfg_mstp.get('max_hop', None)
+ cfg_hello_time = cfg_mstp.get('hello_time', None)
+ cfg_max_age = cfg_mstp.get('max_age', None)
+ cfg_fwd_delay = cfg_mstp.get('fwd_delay', None)
+ cfg_mst_instances = cfg_mstp.get('mst_instances', None)
+
+ if mst_name:
+ if mst_name == cfg_mst_name:
+ requests.append(self.get_delete_stp_mstp_cfg_attr('name'))
+ else:
+ commands['mstp'].pop('mst_name')
+ if revision:
+ if revision == cfg_revision:
+ requests.append(self.get_delete_stp_mstp_cfg_attr('revision'))
+ else:
+ commands['mstp'].pop('revision')
+ if max_hop:
+ if max_hop == cfg_max_hop:
+ requests.append(self.get_delete_stp_mstp_cfg_attr('max-hop'))
+ else:
+ commands['mstp'].pop('max_hop')
+ if hello_time:
+ if hello_time == cfg_hello_time:
+ requests.append(self.get_delete_stp_mstp_cfg_attr('hello-time'))
+ else:
+ commands['mstp'].pop('hello_time')
+ if max_age:
+ if max_age == cfg_max_age:
+ requests.append(self.get_delete_stp_mstp_cfg_attr('max-age'))
+ else:
+ commands['mstp'].pop('max_age')
+ if fwd_delay:
+ if fwd_delay == cfg_fwd_delay:
+ requests.append(self.get_delete_stp_mstp_cfg_attr('forwarding-delay'))
+ else:
+ commands['mstp'].pop('fwd_delay')
+ if mst_instances:
+ mst_inst_list = []
+ for mst in mst_instances:
+ mst_inst_dict = {}
+ mst_id = mst.get('mst_id', None)
+ bridge_priority = mst.get('bridge_priority', None)
+ interfaces = mst.get('interfaces', None)
+ vlans = mst.get('vlans', None)
+ if cfg_mst_instances:
+ for cfg_mst in cfg_mst_instances:
+ cfg_mst_id = cfg_mst.get('mst_id', None)
+ cfg_bridge_priority = cfg_mst.get('bridge_priority', None)
+ cfg_interfaces = cfg_mst.get('interfaces', None)
+ cfg_vlans = cfg_mst.get('vlans', None)
+
+ if mst_id == cfg_mst_id:
+ if bridge_priority and bridge_priority == cfg_bridge_priority:
+ requests.append(self.get_delete_mst_inst_cfg_attr(mst_id, 'bridge-priority'))
+ mst_inst_dict.update({'mst_id': mst_id, 'bridge_priority': bridge_priority})
+ if interfaces:
+ intf_list = []
+ for intf in interfaces:
+ intf_dict = {}
+ intf_name = intf.get('intf_name', None)
+ cost = intf.get('cost', None)
+ port_priority = intf.get('port_priority', None)
+
+ if cfg_interfaces:
+ for cfg_intf in cfg_interfaces:
+ cfg_intf_name = cfg_intf.get('intf_name', None)
+ cfg_cost = cfg_intf.get('cost', None)
+ cfg_port_priority = cfg_intf.get('port_priority', None)
+
+ if intf_name == cfg_intf_name:
+ if cost and cost == cfg_cost:
+ requests.append(self.get_delete_mst_intf_cfg_attr(mst_id, intf_name, 'cost'))
+ intf_dict.update({'intf_name': intf_name, 'cost': cost})
+ if port_priority and port_priority == cfg_port_priority:
+ requests.append(self.get_delete_mst_intf_cfg_attr(mst_id, intf_name, 'port-priority'))
+ intf_dict.update({'intf_name': intf_name, 'port_priority': port_priority})
+ if not cost and not port_priority:
+ requests.append(self.get_delete_mst_intf(mst_id, intf_name))
+ intf_dict.update({'intf_name': intf_name})
+ if intf_dict:
+ intf_list.append(intf_dict)
+ if intf_list:
+ mst_inst_dict.update({'mst_id': mst_id, 'interfaces': intf_list})
+
+ if vlans and cfg_vlans:
+ vlans_to_delete = self.get_vlans_common(vlans, cfg_vlans)
+ cmd_vlans = deepcopy(vlans_to_delete)
+ for i, vlan in enumerate(vlans_to_delete):
+ if '-' in vlan:
+ vlans_to_delete[i] = vlan.replace('-', '..')
+ if vlans_to_delete:
+ encoded_vlans = '%2C'.join(vlans_to_delete)
+ attr = 'vlan=%s' % (encoded_vlans)
+ requests.append(self.get_delete_mst_inst_cfg_attr(mst_id, attr))
+ mst_inst_dict.update({'mst_id': mst_id, 'vlans': cmd_vlans})
+ if not bridge_priority and not vlans and not interfaces:
+ requests.append(self.get_delete_mst_inst(mst_id))
+ mst_inst_dict.update({'mst_id': mst_id})
+ if mst_inst_dict:
+ mst_inst_list.append(mst_inst_dict)
+ if mst_inst_list:
+ commands['mstp']['mst_instances'] = mst_inst_list
+ else:
+ commands['mstp'].pop('mst_instances')
+ if not commands['mstp']:
+ commands.pop('mstp')
+
+ return requests
+
+ def get_delete_stp_pvst_requests(self, commands, have):
+ requests = []
+
+ pvst = commands.get('pvst', None)
+ if pvst:
+ vlans_list = []
+ for vlan in pvst:
+ vlans_dict = {}
+ vlan_id = vlan.get('vlan_id', None)
+ hello_time = vlan.get('hello_time', None)
+ max_age = vlan.get('max_age', None)
+ fwd_delay = vlan.get('fwd_delay', None)
+ bridge_priority = vlan.get('bridge_priority', None)
+ interfaces = vlan.get('interfaces', [])
+
+ cfg_pvst = have.get('pvst', None)
+ if cfg_pvst:
+ for cfg_vlan in cfg_pvst:
+ cfg_vlan_id = cfg_vlan.get('vlan_id', None)
+ cfg_hello_time = cfg_vlan.get('hello_time', None)
+ cfg_max_age = cfg_vlan.get('max_age', None)
+ cfg_fwd_delay = cfg_vlan.get('fwd_delay', None)
+ cfg_bridge_priority = cfg_vlan.get('bridge_priority', None)
+ cfg_interfaces = cfg_vlan.get('interfaces', [])
+
+ if vlan_id == cfg_vlan_id:
+ if hello_time and hello_time == cfg_hello_time:
+ requests.append(self.get_delete_pvst_vlan_cfg_attr(vlan_id, 'hello-time'))
+ vlans_dict.update({'vlan_id': vlan_id, 'hello_time': hello_time})
+ if max_age and max_age == cfg_max_age:
+ requests.append(self.get_delete_pvst_vlan_cfg_attr(vlan_id, 'max-age'))
+ vlans_dict.update({'vlan_id': vlan_id, 'max_age': max_age})
+ if fwd_delay and fwd_delay == cfg_fwd_delay:
+ requests.append(self.get_delete_pvst_vlan_cfg_attr(vlan_id, 'forwarding-delay'))
+ vlans_dict.update({'vlan_id': vlan_id, 'fwd_delay': fwd_delay})
+ if bridge_priority and bridge_priority == cfg_bridge_priority:
+ requests.append(self.get_delete_pvst_vlan_cfg_attr(vlan_id, 'bridge-priority'))
+ vlans_dict.update({'vlan_id': vlan_id, 'bridge_priority': bridge_priority})
+
+ if interfaces:
+ intf_list = []
+ for intf in interfaces:
+ intf_dict = {}
+ intf_name = intf.get('intf_name', None)
+ cost = intf.get('cost', None)
+ port_priority = intf.get('port_priority', None)
+
+ if cfg_interfaces:
+ for cfg_intf in cfg_interfaces:
+ cfg_intf_name = cfg_intf.get('intf_name', None)
+ cfg_cost = cfg_intf.get('cost', None)
+ cfg_port_priority = cfg_intf.get('port_priority', None)
+
+ if intf_name == cfg_intf_name:
+ if cost and cost == cfg_cost:
+ requests.append(self.get_delete_pvst_intf_cfg_attr(vlan_id, intf_name, 'cost'))
+ intf_dict.update({'intf_name': intf_name, 'cost': cost})
+ if port_priority and port_priority == cfg_port_priority:
+ requests.append(self.get_delete_pvst_intf_cfg_attr(vlan_id, intf_name, 'port-priority'))
+ intf_dict.update({'intf_name': intf_name, 'port_priority': port_priority})
+ if not cost and not port_priority:
+ requests.append(self.get_delete_pvst_intf(vlan_id, intf_name))
+ intf_dict.update({'intf_name': intf_name})
+ if intf_dict:
+ intf_list.append(intf_dict)
+ if intf_list:
+ vlans_dict.update({'vlan_id': vlan_id, 'interfaces': intf_list})
+ if vlans_dict:
+ vlans_list.append(vlans_dict)
+ if vlans_list:
+ commands['pvst'] = vlans_list
+ else:
+ commands.pop('pvst')
+
+ return requests
+
+ def get_delete_stp_rapid_pvst_requests(self, commands, have):
+ requests = []
+
+ rapid_pvst = commands.get('rapid_pvst', None)
+ if rapid_pvst:
+ vlans_list = []
+ for vlan in rapid_pvst:
+ vlans_dict = {}
+ vlan_id = vlan.get('vlan_id', None)
+ hello_time = vlan.get('hello_time', None)
+ max_age = vlan.get('max_age', None)
+ fwd_delay = vlan.get('fwd_delay', None)
+ bridge_priority = vlan.get('bridge_priority', None)
+ interfaces = vlan.get('interfaces', [])
+
+ cfg_rapid_pvst = have.get('rapid_pvst', None)
+ if cfg_rapid_pvst:
+ for cfg_vlan in cfg_rapid_pvst:
+ cfg_vlan_id = cfg_vlan.get('vlan_id', None)
+ cfg_hello_time = cfg_vlan.get('hello_time', None)
+ cfg_max_age = cfg_vlan.get('max_age', None)
+ cfg_fwd_delay = cfg_vlan.get('fwd_delay', None)
+ cfg_bridge_priority = cfg_vlan.get('bridge_priority', None)
+ cfg_interfaces = cfg_vlan.get('interfaces', [])
+
+ if vlan_id == cfg_vlan_id:
+ if hello_time and hello_time == cfg_hello_time:
+ requests.append(self.get_delete_rapid_pvst_vlan_cfg_attr(vlan_id, 'hello-time'))
+ vlans_dict.update({'vlan_id': vlan_id, 'hello_time': hello_time})
+ if max_age and max_age == cfg_max_age:
+ requests.append(self.get_delete_rapid_pvst_vlan_cfg_attr(vlan_id, 'max-age'))
+ vlans_dict.update({'vlan_id': vlan_id, 'max_age': max_age})
+ if fwd_delay and fwd_delay == cfg_fwd_delay:
+ requests.append(self.get_delete_rapid_pvst_vlan_cfg_attr(vlan_id, 'forwarding-delay'))
+ vlans_dict.update({'vlan_id': vlan_id, 'fwd_delay': fwd_delay})
+ if bridge_priority and bridge_priority == cfg_bridge_priority:
+ requests.append(self.get_delete_rapid_pvst_vlan_cfg_attr(vlan_id, 'bridge-priority'))
+ vlans_dict.update({'vlan_id': vlan_id, 'bridge_priority': bridge_priority})
+
+ if interfaces:
+ intf_list = []
+ for intf in interfaces:
+ intf_dict = {}
+ intf_name = intf.get('intf_name', None)
+ cost = intf.get('cost', None)
+ port_priority = intf.get('port_priority', None)
+
+ if cfg_interfaces:
+ for cfg_intf in cfg_interfaces:
+ cfg_intf_name = cfg_intf.get('intf_name', None)
+ cfg_cost = cfg_intf.get('cost', None)
+ cfg_port_priority = cfg_intf.get('port_priority', None)
+
+ if intf_name == cfg_intf_name:
+ if cost and cost == cfg_cost:
+ requests.append(self.get_delete_rapid_pvst_intf_cfg_attr(vlan_id, intf_name, 'cost'))
+ intf_dict.update({'intf_name': intf_name, 'cost': cost})
+ if port_priority and port_priority == cfg_port_priority:
+ requests.append(self.get_delete_rapid_pvst_intf_cfg_attr(vlan_id, intf_name, 'port-priority'))
+ intf_dict.update({'intf_name': intf_name, 'port_priority': port_priority})
+ if not cost and not port_priority:
+ requests.append(self.get_delete_rapid_pvst_intf(vlan_id, intf_name))
+ intf_dict.update({'intf_name': intf_name})
+ if intf_dict:
+ intf_list.append(intf_dict)
+ if intf_list:
+ vlans_dict.update({'vlan_id': vlan_id, 'interfaces': intf_list})
+ if vlans_dict:
+ vlans_list.append(vlans_dict)
+ if vlans_list:
+ commands['rapid_pvst'] = vlans_list
+ else:
+ commands.pop('rapid_pvst')
+
+ return requests
+
+ def get_delete_all_stp_request(self):
+ request = {'path': STP_PATH, 'method': DELETE}
+
+ return request
+
+ def get_delete_stp_global_attr(self, attr):
+ url = '%s/global/config/%s' % (STP_PATH, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_stp_interface(self, intf_name):
+ url = '%s/interfaces/interface=%s' % (STP_PATH, intf_name)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_stp_interface_attr(self, intf_name, attr):
+ url = '%s/interfaces/interface=%s/config/%s' % (STP_PATH, intf_name, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_stp_mstp_cfg_attr(self, attr):
+ url = '%s/mstp/config/%s' % (STP_PATH, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_mst_inst(self, mst_id):
+ url = '%s/mstp/mst-instances/mst-instance=%s' % (STP_PATH, mst_id)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_mst_inst_cfg_attr(self, mst_id, attr):
+ url = '%s/mstp/mst-instances/mst-instance=%s/config/%s' % (STP_PATH, mst_id, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_mst_intf(self, mst_id, intf_name):
+ url = '%s/mstp/mst-instances/mst-instance=%s/interfaces/interface=%s' % (STP_PATH, mst_id, intf_name)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_mst_intf_cfg_attr(self, mst_id, intf_name, attr):
+ url = '%s/mstp/mst-instances/mst-instance=%s/interfaces/interface=%s/config/%s' % (STP_PATH, mst_id, intf_name, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_pvst_vlan_cfg_attr(self, vlan_id, attr):
+ url = '%s/openconfig-spanning-tree-ext:pvst/vlans=%s/config/%s' % (STP_PATH, vlan_id, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_pvst_intf(self, vlan_id, intf_name):
+ url = '%s/openconfig-spanning-tree-ext:pvst/vlans=%s/interfaces/interface=%s' % (STP_PATH, vlan_id, intf_name)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_pvst_intf_cfg_attr(self, vlan_id, intf_name, attr):
+ url = '%s/openconfig-spanning-tree-ext:pvst/vlans=%s/interfaces/interface=%s/config/%s' % (STP_PATH, vlan_id, intf_name, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_rapid_pvst_vlan_cfg_attr(self, vlan_id, attr):
+ url = '%s/rapid-pvst/vlan=%s/config/%s' % (STP_PATH, vlan_id, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_rapid_pvst_intf(self, vlan_id, intf_name):
+ url = '%s/rapid-pvst/vlan=%s/interfaces/interface=%s' % (STP_PATH, vlan_id, intf_name)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_rapid_pvst_intf_cfg_attr(self, vlan_id, intf_name, attr):
+ url = '%s/rapid-pvst/vlan=%s/interfaces/interface=%s/config/%s' % (STP_PATH, vlan_id, intf_name, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def remove_default_entries(self, data):
+ stp_global = data.get('global', None)
+ interfaces = data.get('interfaces', None)
+
+ if stp_global:
+ loop_guard = stp_global.get('loop_guard', None)
+ bpdu_filter = stp_global.get('bpdu_filter', None)
+ portfast = stp_global.get('portfast', None)
+ hello_time = stp_global.get('hello_time', None)
+ max_age = stp_global.get('max_age', None)
+ fwd_delay = stp_global.get('fwd_delay', None)
+ bridge_priority = stp_global.get('bridge_priority', None)
+
+ if loop_guard is False:
+ stp_global.pop('loop_guard')
+ if bpdu_filter is False:
+ stp_global.pop('bpdu_filter')
+ if portfast is False:
+ stp_global.pop('portfast')
+ if hello_time == 2:
+ stp_global.pop('hello_time')
+ if max_age == 20:
+ stp_global.pop('max_age')
+ if fwd_delay == 15:
+ stp_global.pop('fwd_delay')
+ if bridge_priority == 32768:
+ stp_global.pop('bridge_priority')
+ if not stp_global:
+ data.pop('global')
+
+ if interfaces:
+ for intf in interfaces:
+ edge_port = intf.get('edge_port', None)
+ bpdu_guard = intf.get('bpdu_guard', None)
+ bpdu_filter = intf.get('bpdu_filter', None)
+ portfast = intf.get('portfast', None)
+ uplink_fast = intf.get('uplink_fast', None)
+ shutdown = intf.get('shutdown', None)
+ stp_enable = intf.get('stp_enable', None)
+
+ if edge_port is False:
+ intf.pop('edge_port')
+ if bpdu_guard is False:
+ intf.pop('bpdu_guard')
+ if bpdu_filter is False:
+ intf.pop('bpdu_filter')
+ if portfast is False:
+ intf.pop('portfast')
+ if uplink_fast is False:
+ intf.pop('uplink_fast')
+ if shutdown is False:
+ intf.pop('shutdown')
+ if stp_enable:
+ intf.pop('stp_enable')
+
+ def get_replaced_config(self, want, have):
+ config_dict = {}
+ requests = []
+ stp_global = want.get('global', None)
+ new_have = self.remove_default_entries(deepcopy(have))
+ new_have = remove_empties(new_have)
+ cfg_stp_global = new_have.get('global', None)
+
+ if stp_global and cfg_stp_global and stp_global != cfg_stp_global:
+ requests.append(self.get_delete_all_stp_request())
+ return have, requests
+
+ interfaces = want.get('interfaces', None)
+ cfg_interfaces = have.get('interfaces', None)
+ if interfaces and cfg_interfaces:
+ intf_list = []
+ for intf in interfaces:
+ intf_name = intf.get('intf_name', None)
+ for cfg_intf in cfg_interfaces:
+ cfg_intf_name = cfg_intf.get('intf_name', None)
+ if intf_name == cfg_intf_name:
+ if intf != cfg_intf:
+ intf_list.append(cfg_intf)
+ requests.append(self.get_delete_stp_interface(cfg_intf_name))
+ if intf_list:
+ config_dict['interfaces'] = intf_list
+
+ mstp = want.get('mstp', None)
+ cfg_mstp = have.get('mstp', None)
+ if mstp and cfg_mstp:
+ mst_name = mstp.get('mst_name', None)
+ revision = mstp.get('revision', None)
+ max_hop = mstp.get('max_hop', None)
+ hello_time = mstp.get('hello_time', None)
+ max_age = mstp.get('max_age', None)
+ fwd_delay = mstp.get('fwd_delay', None)
+ mst_instances = mstp.get('mst_instances', None)
+
+ cfg_mst_name = cfg_mstp.get('mst_name', None)
+ cfg_revision = cfg_mstp.get('revision', None)
+ cfg_max_hop = cfg_mstp.get('max_hop', None)
+ cfg_hello_time = cfg_mstp.get('hello_time', None)
+ cfg_max_age = cfg_mstp.get('max_age', None)
+ cfg_fwd_delay = cfg_mstp.get('fwd_delay', None)
+ cfg_mst_instances = cfg_mstp.get('mst_instances', None)
+
+ if ((mst_name and mst_name != cfg_mst_name) or (revision and revision != cfg_revision) or (max_hop and max_hop != cfg_max_hop) or
+ (hello_time and hello_time != cfg_hello_time) or (max_age and max_age != cfg_max_age) or
+ (fwd_delay and fwd_delay != cfg_fwd_delay)):
+ config_dict['mstp'] = cfg_mstp
+ requests.append({'path': '%s/mstp/config' % STP_PATH, 'method': DELETE})
+ requests.append({'path': '%s/mstp/mst-instances' % STP_PATH, 'method': DELETE})
+ else:
+ if mst_instances and cfg_mst_instances:
+ mst_inst_list = []
+ for mst in mst_instances:
+ mst_id = mst.get('mst_id', None)
+ bridge_priority = mst.get('bridge_priority', None)
+ vlans = mst.get('vlans', None)
+ if vlans:
+ vlans.sort()
+ interfaces = mst.get('interfaces', None)
+ for cfg_mst in cfg_mst_instances:
+ cfg_mst_id = cfg_mst.get('mst_id', None)
+ cfg_bridge_priority = cfg_mst.get('bridge_priority', None)
+ cfg_vlans = cfg_mst.get('vlans', None)
+ if cfg_vlans:
+ cfg_vlans.sort()
+ cfg_interfaces = cfg_mst.get('interfaces', None)
+
+ if mst_id == cfg_mst_id:
+ if ((bridge_priority and bridge_priority != cfg_bridge_priority) or (vlans and vlans != cfg_vlans)):
+ mst_inst_list.append(cfg_mst)
+ requests.append(self.get_delete_mst_inst(cfg_mst_id))
+ else:
+ if interfaces and cfg_interfaces:
+ intf_list = []
+ for intf in interfaces:
+ intf_name = intf.get('intf_name', None)
+ for cfg_intf in cfg_interfaces:
+ cfg_intf_name = cfg_intf.get('intf_name', None)
+ if intf_name == cfg_intf_name:
+ if intf != cfg_intf:
+ intf_list.append(cfg_intf)
+ mst_inst_list.append({'mst_id': cfg_mst_id, 'interfaces': intf_list})
+ requests.append(self.get_delete_mst_intf(cfg_mst_id, cfg_intf_name))
+ if mst_inst_list:
+ config_dict['mstp'] = {'mst_instances': mst_inst_list}
+
+ pvst = want.get('pvst', None)
+ cfg_pvst = have.get('pvst', None)
+ if pvst and cfg_pvst:
+ vlans_list, vlans_requests = self.get_replaced_vlans_list(pvst, cfg_pvst, 'pvst')
+ if vlans_list:
+ config_dict['pvst'] = vlans_list
+ requests.extend(vlans_requests)
+
+ rapid_pvst = want.get('rapid_pvst', None)
+ cfg_rapid_pvst = have.get('rapid_pvst', None)
+ if rapid_pvst and cfg_rapid_pvst:
+ vlans_list, vlans_requests = self.get_replaced_vlans_list(rapid_pvst, cfg_rapid_pvst, 'rapid_pvst')
+ if vlans_list:
+ config_dict['rapid_pvst'] = vlans_list
+ requests.extend(vlans_requests)
+
+ return config_dict, requests
+
+ def get_replaced_vlans_list(self, want_data, have_data, protocol):
+ vlans_list = []
+ requests = []
+ for vlan in want_data:
+ vlan_id = vlan.get('vlan_id', None)
+ hello_time = vlan.get('hello_time', None)
+ max_age = vlan.get('max_age', None)
+ fwd_delay = vlan.get('fwd_delay', None)
+ bridge_priority = vlan.get('bridge_priority', None)
+ interfaces = vlan.get('interfaces', None)
+
+ for cfg_vlan in have_data:
+ cfg_vlan_id = cfg_vlan.get('vlan_id', None)
+ cfg_hello_time = cfg_vlan.get('hello_time', None)
+ cfg_max_age = cfg_vlan.get('max_age', None)
+ cfg_fwd_delay = cfg_vlan.get('fwd_delay', None)
+ cfg_bridge_priority = cfg_vlan.get('bridge_priority', None)
+ cfg_interfaces = cfg_vlan.get('interfaces', None)
+
+ if vlan_id == cfg_vlan_id:
+ if ((hello_time and hello_time != cfg_hello_time) or (max_age and max_age != cfg_max_age) or
+ (fwd_delay and fwd_delay != cfg_fwd_delay) or (bridge_priority and bridge_priority != cfg_bridge_priority)):
+ vlans_list.append(cfg_vlan)
+
+ if cfg_hello_time:
+ if protocol == 'pvst':
+ requests.append(self.get_delete_pvst_vlan_cfg_attr(cfg_vlan_id, 'hello-time'))
+ elif protocol == 'rapid_pvst':
+ requests.append(self.get_delete_rapid_pvst_vlan_cfg_attr(cfg_vlan_id, 'hello-time'))
+ if cfg_max_age:
+ if protocol == 'pvst':
+ requests.append(self.get_delete_pvst_vlan_cfg_attr(cfg_vlan_id, 'max-age'))
+ elif protocol == 'rapid_pvst':
+ requests.append(self.get_delete_rapid_pvst_vlan_cfg_attr(cfg_vlan_id, 'max-age'))
+ if cfg_fwd_delay:
+ if protocol == 'pvst':
+ requests.append(self.get_delete_pvst_vlan_cfg_attr(cfg_vlan_id, 'forwarding-delay'))
+ elif protocol == 'rapid_pvst':
+ requests.append(self.get_delete_rapid_pvst_vlan_cfg_attr(cfg_vlan_id, 'forwarding-delay'))
+ if cfg_bridge_priority:
+ if protocol == 'pvst':
+ requests.append(self.get_delete_pvst_vlan_cfg_attr(cfg_vlan_id, 'bridge-priority'))
+ elif protocol == 'rapid_pvst':
+ requests.append(self.get_delete_rapid_pvst_vlan_cfg_attr(cfg_vlan_id, 'bridge-priority'))
+ if cfg_interfaces:
+ for cfg_intf in cfg_interfaces:
+ cfg_intf_name = cfg_intf.get('intf_name', None)
+ if protocol == 'pvst':
+ requests.append(self.get_delete_pvst_intf(cfg_vlan_id, cfg_intf_name))
+ elif protocol == 'rapid_pvst':
+ requests.append(self.get_delete_rapid_pvst_intf(cfg_vlan_id, cfg_intf_name))
+
+ else:
+ if interfaces and cfg_interfaces:
+ intf_list = []
+ for intf in interfaces:
+ intf_name = intf.get('intf_name', None)
+ for cfg_intf in cfg_interfaces:
+ cfg_intf_name = cfg_intf.get('intf_name', None)
+ if intf_name == cfg_intf_name:
+ if intf != cfg_intf:
+ intf_list.append(cfg_intf)
+ vlans_list.append({'vlan_id': cfg_vlan_id, 'interfaces': intf_list})
+ if protocol == 'pvst':
+ requests.append(self.get_delete_pvst_intf(cfg_vlan_id, cfg_intf_name))
+ elif protocol == 'rapid_pvst':
+ requests.append(self.get_delete_rapid_pvst_intf(cfg_vlan_id, cfg_intf_name))
+
+ return vlans_list, requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/system/system.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/system/system.py
index 21d575a1f..50225718b 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/system/system.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/system/system.py
@@ -26,17 +26,45 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common i
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
update_states,
+ send_requests,
get_diff,
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
to_request,
edit_config
)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ get_new_config,
+ get_formatted_config_diff
+)
PATCH = 'patch'
DELETE = 'delete'
+def __derive_system_config_delete_op(key_set, command, exist_conf):
+ new_conf = exist_conf
+
+ if 'hostname' in command:
+ new_conf['hostname'] = 'sonic'
+ if 'interface_naming' in command:
+ new_conf['interface_naming'] = 'native'
+ if 'anycast_address' in command and 'anycast_address' in new_conf:
+ if 'ipv4' in command['anycast_address']:
+ new_conf['anycast_address']['ipv4'] = True
+ if 'ipv6' in command['anycast_address']:
+ new_conf['anycast_address']['ipv6'] = True
+ if 'mac_address' in command['anycast_address']:
+ new_conf['anycast_address']['mac_address'] = None
+
+ return True, new_conf
+
+
+TEST_KEYS_formatted_diff = [
+ {'__default_ops': {'__delete_op': __derive_system_config_delete_op}},
+]
+
+
class System(ConfigBase):
"""
The sonic_system class
@@ -90,6 +118,17 @@ class System(ConfigBase):
if result['changed']:
result['after'] = changed_system_facts
+ new_config = changed_system_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_system_facts,
+ TEST_KEYS_formatted_diff)
+ result['after(generated)'] = new_config
+
+ if self._module._diff:
+ result['diff'] = get_formatted_config_diff(existing_system_facts,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
@@ -127,6 +166,11 @@ class System(ConfigBase):
elif state == 'merged':
diff = get_diff(want, have)
commands = self._state_merged(want, have, diff)
+ elif state == 'overridden':
+ commands = self._state_overridden(want, have)
+ elif state == 'replaced':
+ commands = self._state_replaced(want, have)
+
return commands
def _state_merged(self, want, have, diff):
@@ -142,6 +186,7 @@ class System(ConfigBase):
requests = self.get_create_system_request(want, diff)
if len(requests) > 0:
commands = update_states(diff, "merged")
+
return commands, requests
def _state_deleted(self, want, have):
@@ -167,6 +212,70 @@ class System(ConfigBase):
requests = self.get_delete_all_system_request(diff_want)
if len(requests) > 0:
commands = update_states(diff_want, "deleted")
+
+ return commands, requests
+
+ def _state_replaced(self, want, have):
+ """ The command generator when state is replaced
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ new_want = self.patch_want_with_default(want, ac_address_only=True)
+ replaced_config = self.get_replaced_config(have, new_want)
+ if replaced_config:
+ requests = self.get_delete_all_system_request(replaced_config)
+ send_requests(self._module, requests)
+ commands = new_want
+ else:
+ diff = get_diff(new_want, have)
+ commands = diff
+ if not commands:
+ commands = []
+
+ requests = []
+
+ if commands:
+ requests = self.get_create_system_request(have, commands)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "replaced")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ new_want = self.patch_want_with_default(want)
+ if have and have != new_want:
+ requests = self.get_delete_all_system_request(have)
+ send_requests(self._module, requests)
+ have = []
+
+ commands = []
+ requests = []
+
+ if not have and new_want:
+ commands = new_want
+ requests = self.get_create_system_request(have, commands)
+ if len(requests) > 0:
+ commands = update_states(commands, "overridden")
+ else:
+ commands = []
+
return commands, requests
def get_create_system_request(self, want, commands):
@@ -190,8 +299,9 @@ class System(ConfigBase):
return requests
def build_create_hostname_payload(self, commands):
- payload = {"openconfig-system:config": {}}
+ payload = {}
if "hostname" in commands and commands["hostname"]:
+ payload = {"openconfig-system:config": {}}
payload['openconfig-system:config'].update({"hostname": commands["hostname"]})
return payload
@@ -221,6 +331,63 @@ class System(ConfigBase):
payload["sonic-sag:SAG_GLOBAL_LIST"].append(temp)
return payload
+ def patch_want_with_default(self, want, ac_address_only=False):
+ new_want = {}
+ if want is None:
+ if ac_address_only:
+ new_want = {'anycast_address': {'ipv4': True, 'ipv6': True, 'mac_address': None}}
+ else:
+ new_want = {'hostname': 'sonic', 'interface_naming': 'native',
+ 'anycast_address': {'ipv4': True, 'ipv6': True, 'mac_address': None}}
+ else:
+ new_want = want.copy()
+ new_anycast = {}
+ anycast = want.get('anycast_address', None)
+ if not anycast:
+ new_anycast = {'ipv4': True, 'ipv6': True, 'mac_address': None}
+ else:
+ new_anycast = anycast.copy()
+ ipv4 = anycast.get("ipv4", None)
+ if ipv4 is None:
+ new_anycast["ipv4"] = True
+ ipv6 = anycast.get("ipv6", None)
+ if ipv6 is None:
+ new_anycast["ipv6"] = True
+ mac = anycast.get("mac_address", None)
+ if mac is None:
+ new_anycast["mac_address"] = None
+ new_want["anycast_address"] = new_anycast
+
+ if not ac_address_only:
+ hostname = want.get('hostname', None)
+ if hostname is None:
+ new_want["hostname"] = 'sonic'
+ intf_name = want.get('interface_naming', None)
+ if intf_name is None:
+ new_want["interface_naming"] = 'native'
+ return new_want
+
+ def get_replaced_config(self, have, want):
+
+ replaced_config = dict()
+
+ h_hostname = have.get('hostname', None)
+ w_hostname = want.get('hostname', None)
+ if (h_hostname != w_hostname) and w_hostname:
+ replaced_config = have.copy()
+ return replaced_config
+ h_intf_name = have.get('interface_naming', None)
+ w_intf_name = want.get('interface_naming', None)
+ if (h_intf_name != w_intf_name) and w_intf_name:
+ replaced_config = have.copy()
+ return replaced_config
+ h_ac_addr = have.get('anycast_address', None)
+ w_ac_addr = want.get('anycast_address', None)
+ if (h_ac_addr != w_ac_addr) and w_ac_addr:
+ replaced_config['anycast_address'] = h_ac_addr
+ return replaced_config
+ return replaced_config
+
def remove_default_entries(self, data):
new_data = {}
if not data:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/tacacs_server/tacacs_server.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/tacacs_server/tacacs_server.py
index 498fcbe28..e376fc82a 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/tacacs_server/tacacs_server.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/tacacs_server/tacacs_server.py
@@ -27,14 +27,25 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
update_states,
get_diff,
+ get_replaced_config,
get_normalize_interface_name,
)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF,
+ get_new_config,
+ get_formatted_config_diff
+)
PATCH = 'patch'
DELETE = 'delete'
TEST_KEYS = [
{'host': {'name': ''}},
]
+TEST_KEYS_formatted_diff = [
+ {'__default_ops': {'__delete_op': __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF}},
+ {'host': {'name': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+]
class Tacacs_server(ConfigBase):
@@ -91,6 +102,17 @@ class Tacacs_server(ConfigBase):
if result['changed']:
result['after'] = changed_tacacs_server_facts
+ new_config = changed_tacacs_server_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_tacacs_server_facts,
+ TEST_KEYS_formatted_diff)
+ result['after(generated)'] = new_config
+
+ if self._module._diff:
+ result['diff'] = get_formatted_config_diff(existing_tacacs_server_facts,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
@@ -180,6 +202,67 @@ class Tacacs_server(ConfigBase):
return commands, requests
+ def _state_replaced(self, want, have, diff):
+ """ The command generator when state is replaced
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ replaced_config = get_replaced_config(want, have, TEST_KEYS)
+
+ add_commands = []
+ if replaced_config:
+ del_requests = self.get_delete_tacacs_server_requests(replaced_config, have)
+ requests.extend(del_requests)
+ commands.extend(update_states(replaced_config, "deleted"))
+ add_commands = want
+ else:
+ add_commands = diff
+
+ if add_commands:
+ add_requests = self.get_modify_tacacs_server_requests(add_commands, have)
+ if len(add_requests) > 0:
+ requests.extend(add_requests)
+ commands.extend(update_states(add_commands, "replaced"))
+
+ return commands, requests
+
+ def _state_overridden(self, want, have, diff):
+ """ The command generator when state is overridden
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ r_diff = get_diff(have, want, TEST_KEYS)
+ if have and (diff or r_diff):
+ del_requests = self.get_delete_tacacs_server_requests(have, have)
+ requests.extend(del_requests)
+ commands.extend(update_states(have, "deleted"))
+ have = []
+
+ if not have and want:
+ want_commands = want
+ want_requests = self.get_modify_tacacs_server_requests(want_commands, have)
+
+ if len(want_requests) > 0:
+ requests.extend(want_requests)
+ commands.extend(update_states(want_commands, "overridden"))
+
+ return commands, requests
+
def get_tacacs_global_payload(self, conf):
payload = {}
global_cfg = {}
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/users/users.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/users/users.py
index 73398cf74..9c79cd0e4 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/users/users.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/users/users.py
@@ -26,14 +26,21 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
edit_config
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
- dict_to_set,
update_states,
get_diff,
)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ get_new_config,
+ get_formatted_config_diff
+)
from ansible.module_utils.connection import ConnectionError
PATCH = 'patch'
DELETE = 'delete'
+TEST_KEYS_formatted_diff = [
+ {'config': {'name': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+]
class Users(ConfigBase):
@@ -83,7 +90,7 @@ class Users(ConfigBase):
except ConnectionError as exc:
try:
json_obj = json.loads(str(exc).replace("'", '"'))
- if json_obj and type(json_obj) is dict and 401 == json_obj['code']:
+ if json_obj and isinstance(json_obj, dict) and 401 == json_obj['code']:
auth_error = True
warnings.append("Unable to get after configs as password got changed for current user")
else:
@@ -101,6 +108,19 @@ class Users(ConfigBase):
if result['changed']:
result['after'] = changed_users_facts
+ new_config = changed_users_facts
+ old_config = existing_users_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_users_facts,
+ TEST_KEYS_formatted_diff)
+ result['after(generated)'] = new_config
+ if self._module._diff:
+ self.sort_lists_in_config(new_config)
+ self.sort_lists_in_config(old_config)
+ result['diff'] = get_formatted_config_diff(old_config,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
@@ -133,8 +153,7 @@ class Users(ConfigBase):
want = []
new_want = [{'name': conf['name'], 'role': conf['role']} for conf in want]
- new_have = [{'name': conf['name'], 'role': conf['role']} for conf in have]
- new_diff = get_diff(new_want, new_have)
+ new_diff = get_diff(new_want, have)
diff = []
for cfg in new_diff:
@@ -187,7 +206,7 @@ class Users(ConfigBase):
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
- # if want is none, then delete all the usersi except admin
+ # if want is none, then delete all the users except admin
if not want:
commands = have
else:
@@ -202,6 +221,65 @@ class Users(ConfigBase):
return commands, requests
+ def _state_replaced(self, want, have, diff):
+ """ The command generator when state is merged
+
+ :param want: the additive configuration as a dictionary
+ :param obj_in_have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to replace the current configuration
+ wit the provided configuration
+ """
+ self.validate_new_users(want, have)
+
+ commands = diff
+ requests = self.get_modify_users_requests(commands, have)
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "replaced")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_overridden(self, want, have, diff):
+ """ The command generator when state is overridden
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+ self.sort_lists_in_config(want)
+ self.sort_lists_in_config(have)
+ new_want = [{'name': conf['name'], 'role': conf['role']} for conf in want]
+ new_have = []
+ for conf in have:
+ # Exclude admin user from new_have if it isn't present in new_want
+ if conf['name'] == 'admin' and not any(cfg['name'] == 'admin' for cfg in new_want):
+ continue
+ else:
+ new_have.append({'name': conf['name'], 'role': conf['role']})
+
+ if diff or new_want != new_have:
+ # Delete all users except admin
+ del_requests = self.get_delete_users_requests(have, have)
+ requests.extend(del_requests)
+ commands.extend(update_states(have, "deleted"))
+ have = []
+
+ # Merge want configuration
+ mod_commands = want
+ mod_requests = self.get_modify_users_requests(mod_commands, have)
+
+ if mod_commands and len(mod_requests) > 0:
+ requests.extend(mod_requests)
+ commands.extend(update_states(mod_commands, "overridden"))
+
+ return commands, requests
+
def get_pwd(self, pw):
clear_pwd = hashed_pwd = ""
pwd = pw.replace("\\", "")
@@ -281,7 +359,7 @@ class Users(ConfigBase):
if not commands:
return requests
- # Skip the asmin user in 'deleted' state. we cannot delete all users
+ # Skip the admin user in 'deleted' state. we cannot delete all users
admin_usr = None
for conf in commands:
@@ -297,3 +375,7 @@ class Users(ConfigBase):
if admin_usr:
commands.remove(admin_usr)
return requests
+
+ def sort_lists_in_config(self, config):
+ if config:
+ config.sort(key=lambda x: x['name'])
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlan_mapping/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlan_mapping/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlan_mapping/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlan_mapping/vlan_mapping.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlan_mapping/vlan_mapping.py
new file mode 100644
index 000000000..acb72db17
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlan_mapping/vlan_mapping.py
@@ -0,0 +1,517 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_vlan_mapping class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_list,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_diff,
+ update_states,
+ remove_empties_from_list,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils.connection import ConnectionError
+
+
+TEST_KEYS = [
+ {'config': {'name': ''}},
+ {'mapping': {'service_vlan': '', 'dot1q_tunnel': ''}},
+]
+
+
+class Vlan_mapping(ConfigBase):
+ """
+ The sonic_vlan_mapping class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'vlan_mapping',
+ ]
+
+ def __init__(self, module):
+ super(Vlan_mapping, self).__init__(module)
+
+ def get_vlan_mapping_facts(self):
+ """ Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+ vlan_mapping_facts = facts['ansible_network_resources'].get('vlan_mapping')
+ if not vlan_mapping_facts:
+ return []
+ return vlan_mapping_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+
+ existing_vlan_mapping_facts = self.get_vlan_mapping_facts()
+ commands, requests = self.set_config(existing_vlan_mapping_facts)
+ if commands:
+ if not self._module.check_mode:
+ try:
+ edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_vlan_mapping_facts = self.get_vlan_mapping_facts()
+
+ result['before'] = existing_vlan_mapping_facts
+ if result['changed']:
+ result['after'] = changed_vlan_mapping_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_vlan_mapping_facts):
+ """ Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = remove_empties_from_list(self._module.params['config'])
+ have = existing_vlan_mapping_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """ Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params['state']
+ have = self.convert_vlan_ids_range(have)
+ want = self.convert_vlan_ids_range(want)
+ diff = get_diff(want, have, TEST_KEYS)
+
+ if state == 'overridden':
+ commands, requests = self._state_overridden(want, have, diff)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'merged':
+ commands, requests = self._state_merged(want, have, diff)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have, diff)
+
+ ret_commands = remove_empties_from_list(commands)
+ return ret_commands, requests
+
+ def _state_replaced(self, want, have, diff):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ requests = []
+ commands = []
+ commands_del = []
+
+ commands_del = self.get_replaced_delete_list(want, have)
+
+ if commands_del:
+ commands.extend(update_states(commands_del, "deleted"))
+
+ requests_del = self.get_delete_vlan_mapping_requests(commands_del, have, is_delete_all=True)
+ if requests_del:
+ requests.extend(requests_del)
+
+ if diff or commands_del:
+ requests_rep = self.get_create_vlan_mapping_requests(want, have)
+ if len(requests_rep):
+ requests.extend(requests_rep)
+ commands = update_states(want, "replaced")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_overridden(self, want, have, diff):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ commands_del = get_diff(have, want, TEST_KEYS)
+ if commands_del:
+ requests_del = self.get_delete_vlan_mapping_requests(commands_del, have, is_delete_all=True)
+ requests.extend(requests_del)
+ commands_del = update_states(commands_del, "deleted")
+ commands.extend(commands_del)
+
+ commands_over = diff
+ if diff:
+ requests_over = self.get_create_vlan_mapping_requests(commands_over, have)
+ requests.extend(requests_over)
+ commands_over = update_states(commands_over, "overridden")
+ commands.extend(commands_over)
+
+ return commands, requests
+
+ def _state_merged(self, want, have, diff):
+ """ The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = diff
+ requests = self.get_create_vlan_mapping_requests(commands, have)
+
+ if commands and len(requests):
+ commands = update_states(commands, 'merged')
+ else:
+ commands = []
+
+ return commands, requests
+
+ def _state_deleted(self, want, have):
+ """ The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ requests = []
+ is_delete_all = False
+
+ if not want:
+ commands = have
+ is_delete_all = True
+ else:
+ commands = want
+
+ requests.extend(self.get_delete_vlan_mapping_requests(commands, have, is_delete_all))
+
+ if len(requests) == 0:
+ commands = []
+
+ if commands:
+ commands = update_states(commands, 'deleted')
+
+ return commands, requests
+
+ def get_replaced_delete_list(self, commands, have):
+ matched = []
+
+ for cmd in commands:
+ name = cmd.get('name', None)
+ interface_name = name.replace('/', '%2f')
+ mapping_list = cmd.get('mapping', [])
+
+ matched_interface_name = None
+ matched_mapping_list = []
+ for existing in have:
+ have_name = existing.get('name', None)
+ have_interface_name = have_name.replace('/', '%2f')
+ have_mapping_list = existing.get('mapping', [])
+ if interface_name == have_interface_name:
+ matched_interface_name = have_interface_name
+ matched_mapping_list = have_mapping_list
+
+ if mapping_list and matched_mapping_list:
+ returned_mapping_list = []
+ for mapping in mapping_list:
+ service_vlan = mapping.get('service_vlan', None)
+
+ for matched_mapping in matched_mapping_list:
+ matched_service_vlan = matched_mapping.get('service_vlan', None)
+
+ if matched_service_vlan and service_vlan:
+ if matched_service_vlan == service_vlan:
+ priority = mapping.get('priority', None)
+ have_priority = matched_mapping.get('priority', None)
+ inner_vlan = mapping.get('inner_vlan', None)
+ have_inner_vlan = matched_mapping.get('inner_vlan', None)
+ dot1q_tunnel = mapping.get('dot1q_tunnel', False)
+ have_dot1q_tunnel = matched_mapping.get('dot1q_tunnel', False)
+ vlan_ids = mapping.get('vlan_ids', [])
+ have_vlan_ids = matched_mapping.get('vlan_ids', [])
+
+ if priority != have_priority:
+ returned_mapping_list.append(mapping)
+ elif inner_vlan != have_inner_vlan:
+ returned_mapping_list.append(mapping)
+ elif dot1q_tunnel != have_dot1q_tunnel:
+ returned_mapping_list.append(mapping)
+ elif sorted(vlan_ids) != sorted(have_vlan_ids):
+ returned_mapping_list.append(mapping)
+
+ if returned_mapping_list:
+ matched.append({'name': interface_name, 'mapping': returned_mapping_list})
+
+ return matched
+
+ def get_delete_vlan_mapping_requests(self, commands, have, is_delete_all):
+ """ Get list of requests to delete vlan mapping configurations
+ for all interfaces specified by the commands
+ """
+ url = "data/openconfig-interfaces:interfaces/interface={}/openconfig-interfaces-ext:mapped-vlans/mapped-vlan={}"
+ priority_url = "/ingress-mapping/config/mapped-vlan-priority"
+ vlan_ids_url = "/match/single-tagged/config/vlan-ids={}"
+ method = "DELETE"
+ requests = []
+
+ # Delete all vlan mappings
+ if is_delete_all:
+ for cmd in commands:
+ name = cmd.get('name', None)
+ interface_name = name.replace('/', '%2f')
+ mapping_list = cmd.get('mapping', [])
+
+ if mapping_list:
+ for mapping in mapping_list:
+ service_vlan = mapping.get('service_vlan', None)
+ path = url.format(interface_name, service_vlan)
+ request = {"path": path, "method": method}
+ requests.append(request)
+
+ return requests
+
+ else:
+ for cmd in commands:
+ name = cmd.get('name', None)
+ interface_name = name.replace('/', '%2f')
+ mapping_list = cmd.get('mapping', [])
+
+ # Checks if there is a interface matching the delete command
+ have_interface_name = None
+ have_mapping_list = []
+ for tmp in have:
+ tmp_name = tmp.get('name', None)
+ tmp_interface_name = tmp_name.replace('/', '%2f')
+ tmp_mapping_list = tmp.get('mapping', [])
+ if interface_name == tmp_interface_name:
+ have_interface_name = tmp_interface_name
+ have_mapping_list = tmp_mapping_list
+
+ # Delete part or all of single mapping
+ if mapping_list:
+ for mapping in mapping_list:
+ service_vlan = mapping.get('service_vlan', None)
+ vlan_ids = mapping.get('vlan_ids', None)
+ priority = mapping.get('priority', None)
+
+ # Checks if there is a vlan mapping matching the delete command
+ have_service_vlan = None
+ have_vlan_ids = None
+ have_priority = None
+ for have_mapping in have_mapping_list:
+ if have_mapping.get('service_vlan', None) == service_vlan:
+ have_service_vlan = have_mapping.get('service_vlan', None)
+ have_vlan_ids = have_mapping.get('vlan_ids', None)
+ have_priority = have_mapping.get('priority', None)
+
+ if service_vlan and have_service_vlan:
+ if vlan_ids or priority:
+ # Delete priority
+ if priority and have_priority:
+ path = url.format(interface_name, service_vlan) + priority_url
+ request = {"path": path, "method": method}
+ requests.append(request)
+ # Delete vlan ids
+ if vlan_ids and have_vlan_ids:
+ vlan_ids_str = ""
+ same_vlan_ids_list = self.get_vlan_ids_diff(vlan_ids, have_vlan_ids, same=True)
+ if same_vlan_ids_list:
+ for vlan in same_vlan_ids_list:
+ if vlan_ids_str:
+ vlan_ids_str = vlan_ids_str + "%2C" + vlan.replace("-", "..")
+ else:
+ vlan_ids_str = vlan.replace("-", "..")
+ path = url.format(interface_name, service_vlan) + vlan_ids_url.format(vlan_ids_str)
+ request = {"path": path, "method": method}
+ requests.append(request)
+ # Delete entire mapping
+ else:
+ path = url.format(interface_name, service_vlan)
+ request = {"path": path, "method": method}
+ requests.append(request)
+ # Delete all mappings in an interface
+ else:
+ if have_mapping_list:
+ for mapping in have_mapping_list:
+ service_vlan = mapping.get('service_vlan', None)
+ path = url.format(interface_name, service_vlan)
+ request = {"path": path, "method": method}
+ requests.append(request)
+
+ return requests
+
+ def get_create_vlan_mapping_requests(self, commands, have):
+ """ Get list of requests to create/modify vlan mapping configurations
+ for all interfaces specified by the commands
+ """
+ requests = []
+ if not commands:
+ return requests
+
+ for cmd in commands:
+ name = cmd.get('name', None)
+ interface_name = name.replace('/', '%2f')
+ mapping_list = cmd.get('mapping', [])
+
+ if mapping_list:
+ for mapping in mapping_list:
+ requests.append(self.get_create_vlan_mapping_request(interface_name, mapping))
+ return requests
+
+ def get_create_vlan_mapping_request(self, interface_name, mapping):
+ url = "data/openconfig-interfaces:interfaces/interface={}/openconfig-interfaces-ext:mapped-vlans"
+ body = {}
+ method = "PATCH"
+ match_data = None
+
+ service_vlan = mapping.get('service_vlan', None)
+ priority = mapping.get('priority', None)
+ vlan_ids = mapping.get('vlan_ids', [])
+ dot1q_tunnel = mapping.get('dot1q_tunnel', None)
+ inner_vlan = mapping.get('inner_vlan', None)
+
+ if not dot1q_tunnel:
+ if len(vlan_ids) > 1:
+ raise Exception("When dot1q-tunnel is false only one VLAN ID can be passed to the vlan_ids list")
+ if not vlan_ids and priority:
+ match_data = None
+ elif vlan_ids:
+ if inner_vlan:
+ match_data = {'double-tagged': {'config': {'inner-vlan-id': inner_vlan, 'outer-vlan-id': int(vlan_ids[0])}}}
+ else:
+ match_data = {'single-tagged': {'config': {'vlan-ids': [int(vlan_ids[0])]}}}
+ if priority:
+ ing_data = {'config': {'vlan-stack-action': 'SWAP', 'mapped-vlan-priority': priority}}
+ egr_data = {'config': {'vlan-stack-action': 'SWAP', 'mapped-vlan-priority': priority}}
+ else:
+ ing_data = {'config': {'vlan-stack-action': 'SWAP'}}
+ egr_data = {'config': {'vlan-stack-action': 'SWAP'}}
+ else:
+ if inner_vlan:
+ raise Exception("Inner vlan can only be passed when dot1q_tunnel is false")
+ if not vlan_ids and priority:
+ match_data = None
+ elif vlan_ids:
+ vlan_ids_list = []
+ for vlan in vlan_ids:
+ vlan_ids_list.append(int(vlan))
+ match_data = {'single-tagged': {'config': {'vlan-ids': vlan_ids_list}}}
+ if priority:
+ ing_data = {'config': {'vlan-stack-action': 'PUSH', 'mapped-vlan-priority': priority}}
+ egr_data = {'config': {'vlan-stack-action': 'POP', 'mapped-vlan-priority': priority}}
+ else:
+ ing_data = {'config': {'vlan-stack-action': 'PUSH'}}
+ egr_data = {'config': {'vlan-stack-action': 'POP'}}
+ if match_data:
+ body = {'openconfig-interfaces-ext:mapped-vlans': {'mapped-vlan': [
+ {'vlan-id': service_vlan,
+ 'config': {'vlan-id': service_vlan},
+ 'match': match_data,
+ 'ingress-mapping': ing_data,
+ 'egress-mapping': egr_data}
+ ]}}
+ else:
+ body = {'openconfig-interfaces-ext:mapped-vlans': {'mapped-vlan': [
+ {'vlan-id': service_vlan,
+ 'config': {'vlan-id': service_vlan},
+ 'ingress-mapping': ing_data,
+ 'egress-mapping': egr_data}
+ ]}}
+
+ request = {"path": url.format(interface_name), "method": method, "data": body}
+ return request
+
+ def get_vlan_ids_diff(self, vlan_ids, have_vlan_ids, same):
+ """ Takes two vlan id lists and finds the difference.
+ :param vlan_ids: list of vlan ids that is looking for diffs
+ :param have_vlan_ids: list of vlan ids that is being compared to
+ :param same: if true will instead return list of shared values
+ :rtype: list(str)
+ """
+ results = []
+
+ for vlan_id in vlan_ids:
+ if same:
+ if vlan_id in have_vlan_ids:
+ results.append(vlan_id)
+ else:
+ if vlan_id not in have_vlan_ids:
+ results.append(vlan_id)
+
+ return results
+
+ def vlanIdsRangeStr(self, vlanList):
+ rangeList = []
+ for vid in vlanList:
+ if "-" in vid:
+ vidList = vid.split("-")
+ lower = int(vidList[0])
+ upper = int(vidList[1])
+ for i in range(lower, upper + 1):
+ rangeList.append(str(i))
+ else:
+ rangeList.append(vid)
+ return rangeList
+
+ def convert_vlan_ids_range(self, config):
+
+ interface_index = 0
+ for conf in config:
+ name = conf.get('name', None)
+ interface_name = name.replace('/', '%2f')
+ mapping_list = conf.get('mapping', [])
+
+ mapping_index = 0
+ if mapping_list:
+ for mapping in mapping_list:
+ vlan_ids = mapping.get('vlan_ids', None)
+
+ if vlan_ids:
+ config[interface_index]['mapping'][mapping_index]['vlan_ids'] = self.vlanIdsRangeStr(vlan_ids)
+ mapping_index = mapping_index + 1
+ interface_index = interface_index + 1
+
+ return config
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlans/vlans.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlans/vlans.py
index 404051074..0a0b105a7 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlans/vlans.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlans/vlans.py
@@ -14,17 +14,17 @@ created
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import json
-
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
to_list,
+ search_obj_in_list,
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
get_diff,
+ get_replaced_config,
update_states,
remove_empties_from_list,
)
@@ -35,23 +35,20 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
to_request,
edit_config
)
-from ansible.module_utils._text import to_native
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ get_new_config,
+ get_formatted_config_diff
+)
from ansible.module_utils.connection import ConnectionError
-import traceback
-
-LIB_IMP_ERR = None
-ERR_MSG = None
-try:
- import jinja2
- HAS_LIB = True
-except Exception as e:
- HAS_LIB = False
- ERR_MSG = to_native(e)
- LIB_IMP_ERR = traceback.format_exc()
+
TEST_KEYS = [
{'config': {'vlan_id': ''}},
]
+TEST_KEYS_formatted_diff = [
+ {'config': {'vlan_id': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}},
+]
class Vlans(ConfigBase):
@@ -109,6 +106,18 @@ class Vlans(ConfigBase):
if result['changed']:
result['after'] = changed_vlans_facts
+ new_config = changed_vlans_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_vlans_facts,
+ TEST_KEYS_formatted_diff)
+ new_config.sort(key=lambda x: x['vlan_id'])
+ result['after(generated)'] = new_config
+
+ if self._module._diff:
+ result['diff'] = get_formatted_config_diff(existing_vlans_facts,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
@@ -121,7 +130,7 @@ class Vlans(ConfigBase):
to the desired configuration
"""
want = remove_empties_from_list(self._module.params['config'])
- have = existing_vlans_facts
+ have = remove_empties_from_list(existing_vlans_facts)
resp = self.set_state(want, have)
return to_list(resp)
@@ -157,7 +166,29 @@ class Vlans(ConfigBase):
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
- return self._state_merged(want, have, diff)
+ commands = []
+ requests = []
+
+ replaced_config = get_replaced_config(want, have, TEST_KEYS)
+ replaced_vlans = []
+ for config in replaced_config:
+ vlan_obj = search_obj_in_list(config['vlan_id'], want, 'vlan_id')
+ if vlan_obj and vlan_obj.get('description', None) is None:
+ replaced_vlans.append(config)
+
+ if replaced_vlans:
+ del_requests = self.get_delete_vlans_requests(replaced_vlans, False)
+ requests.extend(del_requests)
+ commands.extend(update_states(replaced_config, "deleted"))
+
+ if diff:
+ rep_commands = diff
+ rep_requests = self.get_create_vlans_requests(rep_commands)
+ if len(rep_requests) > 0:
+ requests.extend(rep_requests)
+ commands.extend(update_states(rep_commands, "replaced"))
+
+ return commands, requests
def _state_overridden(self, want, have, diff):
""" The command generator when state is overridden
@@ -166,20 +197,41 @@ class Vlans(ConfigBase):
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
- ret_requests = list()
- commands = list()
- vlans_to_delete = get_diff(have, want, TEST_KEYS)
- if vlans_to_delete:
- delete_vlans_requests = self.get_delete_vlans_requests(vlans_to_delete)
- ret_requests.extend(delete_vlans_requests)
- commands.extend(update_states(vlans_to_delete, "deleted"))
+ commands = []
+ requests = []
+
+ r_diff = get_diff(have, want, TEST_KEYS)
+ if not diff and not r_diff:
+ return commands, requests
+
+ del_vlans = []
+ del_descr_vlans = []
+ for config in r_diff:
+ vlan_obj = search_obj_in_list(config['vlan_id'], want, 'vlan_id')
+ if vlan_obj:
+ if vlan_obj.get('description', None) is None:
+ del_descr_vlans.append(config)
+ else:
+ del_vlans.append(config)
+
+ if del_vlans:
+ del_requests = self.get_delete_vlans_requests(del_vlans, True)
+ requests.extend(del_requests)
+ commands.extend(update_states(del_vlans, "deleted"))
+
+ if del_descr_vlans:
+ del_requests = self.get_delete_vlans_requests(del_descr_vlans, False)
+ requests.extend(del_requests)
+ commands.extend(update_states(del_descr_vlans, "deleted"))
if diff:
- vlans_to_create_requests = self.get_create_vlans_requests(diff)
- ret_requests.extend(vlans_to_create_requests)
- commands.extend(update_states(diff, "merged"))
+ ovr_commands = diff
+ ovr_requests = self.get_create_vlans_requests(ovr_commands)
+ if len(ovr_requests) > 0:
+ requests.extend(ovr_requests)
+ commands.extend(update_states(ovr_commands, "overridden"))
- return commands, ret_requests
+ return commands, requests
def _state_merged(self, want, have, diff):
""" The command generator when state is merged
@@ -204,16 +256,18 @@ class Vlans(ConfigBase):
"""
commands = list()
# if want is none, then delete all the vlans
+ delete_vlan = False
if not want:
commands = have
+ delete_vlan = True
else: # delete specific vlans
commands = get_diff(want, diff, TEST_KEYS)
- requests = self.get_delete_vlans_requests(commands)
+ requests = self.get_delete_vlans_requests(commands, delete_vlan)
commands = update_states(commands, "deleted")
return commands, requests
- def get_delete_vlans_requests(self, configs):
+ def get_delete_vlans_requests(self, configs, delete_vlan=False):
requests = []
if not configs:
return requests
@@ -223,7 +277,7 @@ class Vlans(ConfigBase):
for vlan in configs:
vlan_id = vlan.get("vlan_id")
description = vlan.get("description")
- if description:
+ if description and not delete_vlan:
path = self.get_delete_vlan_config_attr(vlan_id, "description")
else:
path = url.format(vlan_id)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vrfs/vrfs.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vrfs/vrfs.py
index 83deb0ecb..2a07e6456 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vrfs/vrfs.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vrfs/vrfs.py
@@ -14,6 +14,7 @@ created
from __future__ import absolute_import, division, print_function
__metaclass__ = type
+from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -30,12 +31,22 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
update_states,
normalize_interface_name
)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.formatted_diff_utils import (
+ __DELETE_CONFIG_IF_NO_SUBCONFIG,
+ get_new_config,
+ get_formatted_config_diff
+)
from ansible.module_utils.connection import ConnectionError
PATCH = 'patch'
DELETE = 'DELETE'
+MGMT_VRF_NAME = 'mgmt'
TEST_KEYS = [
- {'interfaces': {'name': ''}},
+ {'interfaces': {'name': ''}}
+]
+TEST_KEYS_formatted_diff = [
+ {'config': {'name': ''}},
+ {'interfaces': {'name': '', '__delete_op': __DELETE_CONFIG_IF_NO_SUBCONFIG}}
]
@@ -97,6 +108,17 @@ class Vrfs(ConfigBase):
if result['changed']:
result['after'] = changed_vrf_interfaces_facts
+ new_config = changed_vrf_interfaces_facts
+ if self._module.check_mode:
+ result.pop('after', None)
+ new_config = get_new_config(commands, existing_vrf_interfaces_facts,
+ TEST_KEYS_formatted_diff)
+ result['after(generated)'] = new_config
+
+ if self._module._diff:
+ result['diff'] = get_formatted_config_diff(existing_vrf_interfaces_facts,
+ new_config,
+ self._module._verbosity)
result['warnings'] = warnings
return result
@@ -137,6 +159,10 @@ class Vrfs(ConfigBase):
commands, requests = self._state_deleted(want, have)
elif state == 'merged':
commands, requests = self._state_merged(want, have, diff)
+ elif state == 'overridden':
+ commands, requests = self._state_overridden(want, have)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have)
return commands, requests
@@ -172,7 +198,7 @@ class Vrfs(ConfigBase):
"""
# if want is none, then delete all the vrfs
if not want:
- commands = have
+ commands = self.preprocess_mgmt_vrf_for_deleted(have)
self.delete_all_flag = True
else:
commands = want
@@ -180,7 +206,7 @@ class Vrfs(ConfigBase):
requests = []
if commands:
- requests = self.get_delete_vrf_interface_requests(commands, have, want)
+ requests = self.get_delete_vrf_interface_requests(commands, have)
if len(requests) > 0:
commands = update_states(commands, "deleted")
@@ -189,7 +215,76 @@ class Vrfs(ConfigBase):
return commands, requests
- def get_delete_vrf_interface_requests(self, configs, have, want):
+ def _state_replaced(self, want, have):
+ """ The command generator when state is replaced
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ requests = []
+
+ replaced_config = self.get_replaced_config(have, want)
+ self.sort_config(replaced_config)
+ self.sort_config(want)
+
+ if replaced_config and replaced_config != want:
+ self.delete_all_flag = False
+ del_requests = self.get_delete_vrf_interface_requests(replaced_config, have, 'replaced')
+ requests.extend(del_requests)
+ commands.extend(update_states(replaced_config, "deleted"))
+ replaced_config = []
+
+ if not replaced_config and want:
+ add_commands = want
+ add_requests = self.get_create_requests(add_commands, have)
+
+ if len(add_requests) > 0:
+ requests.extend(add_requests)
+ commands.extend(update_states(add_commands, "replaced"))
+
+ return commands, requests
+
+ def _state_overridden(self, want, have):
+ """ The command generator when state is overridden
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :param diff: the difference between want and have
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ self.sort_config(have)
+ self.sort_config(want)
+
+ commands = []
+ requests = []
+
+ if have and have != want:
+ want, have = self.preprocess_mgmt_vrf_for_overridden(want, have)
+
+ self.delete_all_flag = True
+ del_requests = self.get_delete_vrf_interface_requests(have, have)
+ requests.extend(del_requests)
+ commands.extend(update_states(have, "deleted"))
+ have = []
+
+ if not have and want:
+ add_commands = want
+ add_requests = self.get_create_requests(add_commands, have)
+
+ if len(add_requests) > 0:
+ requests.extend(add_requests)
+ commands.extend(update_states(add_commands, "overridden"))
+
+ return commands, requests
+
+ def get_delete_vrf_interface_requests(self, configs, have, state=None):
requests = []
if not configs:
return requests
@@ -211,21 +306,29 @@ class Vrfs(ConfigBase):
continue
# if members are not mentioned delet the vrf name
- if (self._module.params['state'] == 'deleted' and self.delete_all_flag) or empty_flag:
+ adjusted_delete_all_flag = name != MGMT_VRF_NAME and self.delete_all_flag
+ adjusted_empty_flag = empty_flag
+ if state == 'replaced':
+ adjusted_empty_flag = empty_flag and name != MGMT_VRF_NAME
+
+ if adjusted_delete_all_flag or adjusted_empty_flag:
url = 'data/openconfig-network-instance:network-instances/network-instance={0}'.format(name)
request = {"path": url, "method": method}
requests.append(request)
else:
- matched_members = matched.get('members', None)
-
- if matched_members:
- matched_intf = matched_members.get('interfaces', None)
- if matched_intf:
- for del_mem in matched_intf:
- url = 'data/openconfig-network-instance:network-instances/'
- url = url + 'network-instance={0}/interfaces/interface={1}'.format(name, del_mem['name'])
- request = {"path": url, "method": method}
- requests.append(request)
+ have_members = matched.get('members', None)
+ conf_members = conf.get('members', None)
+
+ if have_members:
+ have_intf = have_members.get('interfaces', None)
+ conf_intf = conf_members.get('interfaces', None)
+ if conf_intf:
+ for del_mem in conf_intf:
+ if del_mem in have_intf:
+ url = 'data/openconfig-network-instance:network-instances/'
+ url = url + 'network-instance={0}/interfaces/interface={1}'.format(name, del_mem['name'])
+ request = {"path": url, "method": method}
+ requests.append(request)
return requests
@@ -301,3 +404,62 @@ class Vrfs(ConfigBase):
network_inst_payload["openconfig-network-instance:interface"].append(member_payload)
return network_inst_payload
+
+ def get_vrf_name(self, vrf):
+ return vrf.get('name')
+
+ def get_interface_name(self, intf):
+ return intf.get('name')
+
+ def sort_config(self, conf):
+ if conf:
+ conf.sort(key=self.get_vrf_name)
+ for vrf in conf:
+ if vrf.get('members', None) and vrf['members'].get('interfaces', None):
+ vrf['members']['interfaces'].sort(key=self.get_interface_name)
+
+ def get_replaced_config(self, have, want):
+
+ replaced_vrfs = []
+ for vrf in want:
+ vrf_name = vrf['name']
+ have_vrf = next((h_vrf for h_vrf in have if h_vrf['name'] == vrf_name), None)
+ if have_vrf:
+ replaced_vrfs.append(have_vrf)
+
+ return replaced_vrfs
+
+ def preprocess_mgmt_vrf_for_deleted(self, have):
+ new_have = have
+ conf = next((vrf for vrf in new_have if vrf['name'] == MGMT_VRF_NAME), None)
+ if conf:
+ new_have = deepcopy(have)
+ new_have.remove(conf)
+ return new_have
+
+ def preprocess_mgmt_vrf_for_overridden(self, want, have):
+ new_want = deepcopy(want)
+ new_have = deepcopy(have)
+ h_conf = next((vrf for vrf in new_have if vrf['name'] == MGMT_VRF_NAME), None)
+ if h_conf:
+ conf = next((vrf for vrf in new_want if vrf['name'] == MGMT_VRF_NAME), None)
+ if conf:
+ mv_intfs = []
+ if conf.get('members', None) and conf['members'].get('interfaces', None):
+ mv_intfs = conf['members'].get('interfaces', [])
+
+ h_mv_intfs = []
+ if h_conf.get('members', None) and h_conf['members'].get('interfaces', None):
+ h_mv_intfs = h_conf['members'].get('interfaces', [])
+
+ mv_intfs.sort(key=lambda x: x['name'])
+ h_mv_intfs.sort(key=lambda x: x['name'])
+ if mv_intfs == h_mv_intfs:
+ new_want.remove(conf)
+ new_have.remove(h_conf)
+ elif not h_mv_intfs:
+ new_have.remove(h_conf)
+ else:
+ new_have.remove(h_conf)
+
+ return new_want, new_have
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vxlans/vxlans.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vxlans/vxlans.py
index d44adcedf..0a87c98c8 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vxlans/vxlans.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vxlans/vxlans.py
@@ -26,7 +26,9 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
get_diff,
- update_states
+ update_states,
+ get_replaced_config,
+ send_requests
)
from ansible.module_utils.connection import ConnectionError
@@ -124,7 +126,7 @@ class Vxlans(ConfigBase):
diff = get_diff(want, have, test_keys)
if state == 'overridden':
- commands, requests = self._state_overridden(want, have, diff)
+ commands, requests = self._state_overridden(want, have)
elif state == 'deleted':
commands, requests = self._state_deleted(want, have, diff)
elif state == 'merged':
@@ -141,57 +143,63 @@ class Vxlans(ConfigBase):
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
+ requests = []
+ replaced_config = get_replaced_config(want, have, test_keys)
+
+ if replaced_config:
+ self.sort_lists_in_config(replaced_config)
+ self.sort_lists_in_config(have)
+ is_delete_all = (replaced_config == have)
+ if is_delete_all:
+ requests = self.get_delete_all_vxlan_request(have)
+ else:
+ requests = self.get_delete_vxlan_request(replaced_config, have)
+
+ send_requests(self._module, requests)
+ commands = want
+ else:
+ commands = diff
requests = []
- commands = []
- commands_del = get_diff(have, want, test_keys)
- requests_del = []
- if commands_del:
- requests_del = self.get_delete_vxlan_request(commands_del, have)
- if requests_del:
- requests.extend(requests_del)
- commands_del = update_states(commands_del, "deleted")
- commands.extend(commands_del)
-
- commands_rep = diff
- requests_rep = []
- if commands_rep:
- requests_rep = self.get_create_vxlans_request(commands_rep, have)
- if requests_rep:
- requests.extend(requests_rep)
- commands_rep = update_states(commands_rep, "replaced")
- commands.extend(commands_rep)
+ if commands:
+ requests = self.get_create_vxlans_request(commands, have)
+ if len(requests) > 0:
+ commands = update_states(commands, "replaced")
+ else:
+ commands = []
+ else:
+ commands = []
return commands, requests
- def _state_overridden(self, want, have, diff):
+ def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
- requests = []
+ self.sort_lists_in_config(want)
+ self.sort_lists_in_config(have)
+
+ if have and have != want:
+ requests = self.get_delete_all_vxlan_request(have)
+ send_requests(self._module, requests)
+
+ have = []
+
commands = []
+ requests = []
+
+ if not have and want:
+ commands = want
+ requests = self.get_create_vxlans_request(commands, have)
- commands_del = get_diff(have, want)
- requests_del = []
- if commands_del:
- requests_del = self.get_delete_vxlan_request(commands_del, have)
- if requests_del:
- requests.extend(requests_del)
- commands_del = update_states(commands_del, "deleted")
- commands.extend(commands_del)
-
- commands_over = diff
- requests_over = []
- if commands_over:
- requests_over = self.get_create_vxlans_request(commands_over, have)
- if requests_over:
- requests.extend(requests_over)
- commands_over = update_states(commands_over, "overridden")
- commands.extend(commands_over)
+ if len(requests) > 0:
+ commands = update_states(commands, "overridden")
+ else:
+ commands = []
return commands, requests
@@ -271,6 +279,7 @@ class Vxlans(ConfigBase):
vlan_map_requests = []
src_ip_requests = []
primary_ip_requests = []
+ evpn_nvo_requests = []
tunnel_requests = []
# Need to delete in reverse order of creation.
@@ -282,6 +291,7 @@ class Vxlans(ConfigBase):
vrf_map_list = conf.get('vrf_map', [])
src_ip = conf.get('source_ip', None)
primary_ip = conf.get('primary_ip', None)
+ evpn_nvo = conf.get('evpn_nvo', None)
if vrf_map_list:
vrf_map_requests.extend(self.get_delete_vrf_map_request(conf, conf, name, vrf_map_list))
@@ -291,6 +301,8 @@ class Vxlans(ConfigBase):
src_ip_requests.extend(self.get_delete_src_ip_request(conf, conf, name, src_ip))
if primary_ip:
primary_ip_requests.extend(self.get_delete_primary_ip_request(conf, conf, name, primary_ip))
+ if evpn_nvo:
+ evpn_nvo_requests.extend(self.get_delete_evpn_request(conf, conf, evpn_nvo))
tunnel_requests.extend(self.get_delete_tunnel_request(conf, conf, name))
if vrf_map_requests:
@@ -301,6 +313,8 @@ class Vxlans(ConfigBase):
requests.extend(src_ip_requests)
if primary_ip_requests:
requests.extend(primary_ip_requests)
+ if evpn_nvo_requests:
+ requests.extend(evpn_nvo_requests)
if tunnel_requests:
requests.extend(tunnel_requests)
@@ -315,6 +329,7 @@ class Vxlans(ConfigBase):
vrf_map_requests = []
vlan_map_requests = []
src_ip_requests = []
+ evpn_nvo_requests = []
primary_ip_requests = []
tunnel_requests = []
@@ -325,6 +340,7 @@ class Vxlans(ConfigBase):
name = conf['name']
src_ip = conf.get('source_ip', None)
+ evpn_nvo = conf.get('evpn_nvo', None)
primary_ip = conf.get('primary_ip', None)
vlan_map_list = conf.get('vlan_map', None)
vrf_map_list = conf.get('vrf_map', None)
@@ -342,7 +358,7 @@ class Vxlans(ConfigBase):
is_delete_full = False
if (name and vlan_map_list is None and vrf_map_list is None and
- src_ip is None and primary_ip is None):
+ src_ip is None and evpn_nvo is None and primary_ip is None):
is_delete_full = True
vrf_map_list = matched.get("vrf_map", [])
vlan_map_list = matched.get("vlan_map", [])
@@ -364,7 +380,8 @@ class Vxlans(ConfigBase):
have_vlan_map_count -= len(temp_vlan_map_requests)
if src_ip:
src_ip_requests.extend(self.get_delete_src_ip_request(conf, matched, name, src_ip))
-
+ if evpn_nvo:
+ evpn_nvo_requests.extend(self.get_delete_evpn_request(conf, matched, evpn_nvo))
if primary_ip:
primary_ip_requests.extend(self.get_delete_primary_ip_request(conf, matched, name, primary_ip))
if is_delete_full:
@@ -376,6 +393,8 @@ class Vxlans(ConfigBase):
requests.extend(vlan_map_requests)
if src_ip_requests:
requests.extend(src_ip_requests)
+ if evpn_nvo_requests:
+ requests.extend(evpn_nvo_requests)
if primary_ip_requests:
requests.extend(primary_ip_requests)
if tunnel_requests:
@@ -399,7 +418,7 @@ class Vxlans(ConfigBase):
payload = self.build_create_tunnel_payload(conf)
request = {"path": url, "method": PATCH, "data": payload}
requests.append(request)
- if conf.get('source_ip', None):
+ if conf.get('evpn_nvo', None):
requests.append(self.get_create_evpn_request(conf))
return requests
@@ -502,12 +521,23 @@ class Vxlans(ConfigBase):
payload_url = dict({"sonic-vrf:vni": vrf_map['vni']})
return payload_url
- def get_delete_evpn_request(self, conf):
+ def get_delete_evpn_request(self, conf, matched, del_evpn_nvo):
# Create URL and payload
- url = "data/sonic-vxlan:sonic-vxlan/EVPN_NVO/EVPN_NVO_LIST={evpn_nvo}".format(evpn_nvo=conf['evpn_nvo'])
- request = {"path": url, "method": DELETE}
+ requests = []
- return request
+ url = "data/sonic-vxlan:sonic-vxlan/EVPN_NVO/EVPN_NVO_LIST={evpn_nvo}"
+
+ is_change_needed = False
+ if matched:
+ matched_evpn_nvo = matched.get('evpn_nvo', None)
+ if matched_evpn_nvo and matched_evpn_nvo == del_evpn_nvo:
+ is_change_needed = True
+
+ if is_change_needed:
+ request = {"path": url.format(evpn_nvo=conf['evpn_nvo']), "method": DELETE}
+ requests.append(request)
+
+ return requests
def get_delete_tunnel_request(self, conf, matched, name):
# Create URL and payload
@@ -530,9 +560,7 @@ class Vxlans(ConfigBase):
if matched_source_ip and matched_source_ip == del_source_ip:
is_change_needed = True
- # Delete the EVPN NVO if the source_ip address is being deleted.
if is_change_needed:
- requests.append(self.get_delete_evpn_request(conf))
request = {"path": url.format(name=name), "method": DELETE}
requests.append(request)
@@ -604,3 +632,18 @@ class Vxlans(ConfigBase):
requests.append(request)
return requests
+
+ def sort_lists_in_config(self, config):
+ if config:
+ config.sort(key=self.get_name)
+ for cfg in config:
+ if 'vlan_map' in cfg and cfg['vlan_map']:
+ cfg['vlan_map'].sort(key=self.get_vni)
+ if 'vrf_map' in cfg and cfg['vrf_map']:
+ cfg['vrf_map'].sort(key=self.get_vni)
+
+ def get_name(self, name):
+ return name.get('name')
+
+ def get_vni(self, vni):
+ return vni.get('vni')
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/aaa/aaa.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/aaa/aaa.py
index 5a7bd05c9..541a5805e 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/aaa/aaa.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/aaa/aaa.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2021 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/acl_interfaces/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/acl_interfaces/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/acl_interfaces/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/acl_interfaces/acl_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/acl_interfaces/acl_interfaces.py
new file mode 100644
index 000000000..ec2973d84
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/acl_interfaces/acl_interfaces.py
@@ -0,0 +1,148 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic acl_interfaces fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible.module_utils.connection import ConnectionError
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+
+
+class Acl_interfacesFacts(object):
+ """ The sonic acl_interfaces fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Acl_interfacesArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for acl_interfaces
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if connection: # just for linting purposes, remove
+ pass
+
+ if not data:
+ acl_interfaces_configs = self.get_acl_interfaces()
+
+ objs = []
+ for interface_config in acl_interfaces_configs.items():
+ obj = self.render_config(self.generated_spec, interface_config)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('acl_interfaces', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['acl_interfaces'] = params['config']
+
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+ config['name'] = conf[0]
+ config['access_groups'] = []
+
+ acls = {'mac': [], 'ipv4': [], 'ipv6': []}
+ for acl in conf[1]:
+ acl_type = acl.pop('type')
+ if acl_type in ('ACL_L2', 'openconfig-acl:ACL_L2'):
+ acls['mac'].append(acl)
+ elif acl_type in ('ACL_IPV4', 'openconfig-acl:ACL_IPV4'):
+ acls['ipv4'].append(acl)
+ elif acl_type in ('ACL_IPV6', 'openconfig-acl:ACL_IPV6'):
+ acls['ipv6'].append(acl)
+
+ for acl_type, acl_list in acls.items():
+ if acl_list:
+ config['access_groups'].append({
+ 'type': acl_type,
+ 'acls': acl_list
+ })
+
+ return config
+
+ def get_acl_interfaces(self):
+ """Get all interface access-group configurations available in chassis"""
+ acl_interfaces_path = 'data/openconfig-acl:acl/interfaces'
+ method = 'GET'
+ request = [{'path': acl_interfaces_path, 'method': method}]
+
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ acl_interfaces = []
+ if response[0][1].get('openconfig-acl:interfaces'):
+ acl_interfaces = response[0][1]['openconfig-acl:interfaces'].get('interface', [])
+
+ acl_interfaces_configs = {}
+ for interface in acl_interfaces:
+ acls_list = []
+
+ ingress_acls = interface.get('ingress-acl-sets', {}).get('ingress-acl-set', [])
+ for acl in ingress_acls:
+ if acl.get('config'):
+ acls_list.append({
+ 'name': acl['config']['set-name'],
+ 'type': acl['config']['type'],
+ 'direction': 'in'
+ })
+
+ egress_acls = interface.get('egress-acl-sets', {}).get('egress-acl-set', [])
+ for acl in egress_acls:
+ if acl.get('config'):
+ acls_list.append({
+ 'name': acl['config']['set-name'],
+ 'type': acl['config']['type'],
+ 'direction': 'out'
+ })
+
+ acl_interfaces_configs[interface['id']] = acls_list
+
+ return acl_interfaces_configs
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bfd/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bfd/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bfd/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bfd/bfd.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bfd/bfd.py
new file mode 100644
index 000000000..b8786947d
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bfd/bfd.py
@@ -0,0 +1,236 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic bfd fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.bfd.bfd import BfdArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+
+
+class BfdFacts(object):
+ """ The sonic bfd fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = BfdArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for bfd
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+
+ if not data:
+ bfd_cfg = self.get_bfd_config(self._module)
+ data = self.update_bfd(bfd_cfg)
+ objs = self.render_config(self.generated_spec, data)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['bfd'] = params['config']
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ return conf
+
+ def update_bfd(self, data):
+ bfd_dict = {}
+ if data:
+ bfd_dict['profiles'] = self.update_profiles(data)
+ bfd_dict['single_hops'] = self.update_single_hops(data)
+ bfd_dict['multi_hops'] = self.update_multi_hops(data)
+
+ return bfd_dict
+
+ def update_profiles(self, data):
+ all_profiles = []
+ bfd_profile = data.get('openconfig-bfd-ext:bfd-profile', None)
+ if bfd_profile:
+ profile_list = bfd_profile.get('profile', None)
+ if profile_list:
+ for profile in profile_list:
+ profile_dict = {}
+ profile_name = profile['profile-name']
+ config = profile['config']
+ enabled = config.get('enabled', None)
+ transmit_interval = config.get('desired-minimum-tx-interval', None)
+ receive_interval = config.get('required-minimum-receive', None)
+ detect_multiplier = config.get('detection-multiplier', None)
+ passive_mode = config.get('passive-mode', None)
+ min_ttl = config.get('minimum-ttl', None)
+ echo_interval = config.get('desired-minimum-echo-receive', None)
+ echo_mode = config.get('echo-active', None)
+
+ if profile_name:
+ profile_dict['profile_name'] = profile_name
+ if enabled is not None:
+ profile_dict['enabled'] = enabled
+ if transmit_interval:
+ profile_dict['transmit_interval'] = transmit_interval
+ if receive_interval:
+ profile_dict['receive_interval'] = receive_interval
+ if detect_multiplier:
+ profile_dict['detect_multiplier'] = detect_multiplier
+ if passive_mode is not None:
+ profile_dict['passive_mode'] = passive_mode
+ if min_ttl:
+ profile_dict['min_ttl'] = min_ttl
+ if echo_interval:
+ profile_dict['echo_interval'] = echo_interval
+ if echo_mode is not None:
+ profile_dict['echo_mode'] = echo_mode
+ if profile_dict:
+ all_profiles.append(profile_dict)
+
+ return all_profiles
+
+ def update_single_hops(self, data):
+ all_single_hops = []
+ bfd_single_hop = data.get('openconfig-bfd-ext:bfd-shop-sessions', None)
+ if bfd_single_hop:
+ single_hop_list = bfd_single_hop.get('single-hop', None)
+ if single_hop_list:
+ for hop in single_hop_list:
+ single_hop_dict = {}
+ remote_address = hop['remote-address']
+ vrf = hop['vrf']
+ interface = hop['interface']
+ local_address = hop['local-address']
+ config = hop['config']
+ enabled = config.get('enabled', None)
+ transmit_interval = config.get('desired-minimum-tx-interval', None)
+ receive_interval = config.get('required-minimum-receive', None)
+ detect_multiplier = config.get('detection-multiplier', None)
+ passive_mode = config.get('passive-mode', None)
+ echo_interval = config.get('desired-minimum-echo-receive', None)
+ echo_mode = config.get('echo-active', None)
+ profile_name = config.get('profile-name', None)
+
+ if remote_address:
+ single_hop_dict['remote_address'] = remote_address
+ if vrf:
+ single_hop_dict['vrf'] = vrf
+ if interface:
+ single_hop_dict['interface'] = interface
+ if local_address:
+ single_hop_dict['local_address'] = local_address
+ if enabled is not None:
+ single_hop_dict['enabled'] = enabled
+ if transmit_interval:
+ single_hop_dict['transmit_interval'] = transmit_interval
+ if receive_interval:
+ single_hop_dict['receive_interval'] = receive_interval
+ if detect_multiplier:
+ single_hop_dict['detect_multiplier'] = detect_multiplier
+ if passive_mode is not None:
+ single_hop_dict['passive_mode'] = passive_mode
+ if echo_interval:
+ single_hop_dict['echo_interval'] = echo_interval
+ if echo_mode is not None:
+ single_hop_dict['echo_mode'] = echo_mode
+ if profile_name:
+ single_hop_dict['profile_name'] = profile_name
+ if single_hop_dict:
+ all_single_hops.append(single_hop_dict)
+
+ return all_single_hops
+
+ def update_multi_hops(self, data):
+ all_multi_hops = []
+ bfd_multi_hop = data.get('openconfig-bfd-ext:bfd-mhop-sessions', None)
+ if bfd_multi_hop:
+ multi_hop_list = bfd_multi_hop.get('multi-hop', None)
+ if multi_hop_list:
+ for hop in multi_hop_list:
+ multi_hop_dict = {}
+ remote_address = hop['remote-address']
+ vrf = hop['vrf']
+ local_address = hop['local-address']
+ config = hop['config']
+ enabled = config.get('enabled', None)
+ transmit_interval = config.get('desired-minimum-tx-interval', None)
+ receive_interval = config.get('required-minimum-receive', None)
+ detect_multiplier = config.get('detection-multiplier', None)
+ passive_mode = config.get('passive-mode', None)
+ min_ttl = config.get('minimum-ttl', None)
+ profile_name = config.get('profile-name', None)
+
+ if remote_address:
+ multi_hop_dict['remote_address'] = remote_address
+ if vrf:
+ multi_hop_dict['vrf'] = vrf
+ if local_address:
+ multi_hop_dict['local_address'] = local_address
+ if enabled is not None:
+ multi_hop_dict['enabled'] = enabled
+ if transmit_interval:
+ multi_hop_dict['transmit_interval'] = transmit_interval
+ if receive_interval:
+ multi_hop_dict['receive_interval'] = receive_interval
+ if detect_multiplier:
+ multi_hop_dict['detect_multiplier'] = detect_multiplier
+ if passive_mode is not None:
+ multi_hop_dict['passive_mode'] = passive_mode
+ if min_ttl:
+ multi_hop_dict['min_ttl'] = min_ttl
+ if profile_name:
+ multi_hop_dict['profile_name'] = profile_name
+ if multi_hop_dict:
+ all_multi_hops.append(multi_hop_dict)
+
+ return all_multi_hops
+
+ def get_bfd_config(self, module):
+ bfd_cfg = None
+ get_bfd_path = '/data/openconfig-bfd:bfd'
+ request = {'path': get_bfd_path, 'method': 'get'}
+
+ try:
+ response = edit_config(module, to_request(module, request))
+ if 'openconfig-bfd:bfd' in response[0][1]:
+ bfd_cfg = response[0][1].get('openconfig-bfd:bfd', None)
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+ return bfd_cfg
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp/bgp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp/bgp.py
index c86b53c2a..7ecf253e7 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp/bgp.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp/bgp.py
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -49,6 +48,7 @@ class BgpFacts(object):
'admin_max_med': ['max-med', 'admin-max-med-val'],
'max_med_on_startup_timer': ['max-med', 'time'],
'max_med_on_startup_med_val': ['max-med', 'max-med-val'],
+ 'rt_delay': 'route-map-process-delay'
}
def __init__(self, module, subspec='config', options='options'):
@@ -92,8 +92,8 @@ class BgpFacts(object):
ansible_facts['ansible_network_resources'].pop('bgp', None)
facts = {}
if objs:
- params = utils.validate_config(self.argument_spec, {'config': remove_empties_from_list(objs)})
- facts['bgp'] = params['config']
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['bgp'] = remove_empties_from_list(params['config'])
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_af/bgp_af.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_af/bgp_af.py
index fd37533e4..511fb024a 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_af/bgp_af.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_af/bgp_af.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2019 Red Hat
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -64,6 +63,10 @@ class Bgp_afFacts(object):
'network': ['network-config', 'network'],
'dampening': ['route-flap-damping', 'config', 'enabled'],
'route_advertise_list': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:route-advertise', 'route-advertise-list'],
+ 'rd': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:config', 'route-distinguisher'],
+ 'rt_in': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:config', 'import-rts'],
+ 'rt_out': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:config', 'export-rts'],
+ 'vnis': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:vnis', 'vni']
}
af_redis_params_map = {
@@ -104,6 +107,7 @@ class Bgp_afFacts(object):
self.update_max_paths(data)
self.update_network(data)
self.update_route_advertise_list(data)
+ self.update_vnis(data)
bgp_redis_data = get_all_bgp_af_redistribute(self._module, vrf_list, self.af_redis_params_map)
self.update_redis_data(data, bgp_redis_data)
self.update_afis(data)
@@ -119,8 +123,8 @@ class Bgp_afFacts(object):
ansible_facts['ansible_network_resources'].pop('bgp_af', None)
facts = {}
if objs:
- params = utils.validate_config(self.argument_spec, {'config': remove_empties_from_list(objs)})
- facts['bgp_af'] = params['config']
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['bgp_af'] = remove_empties_from_list(params['config'])
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
@@ -241,6 +245,38 @@ class Bgp_afFacts(object):
rt_adv_lst.append(rt_adv_dict)
af['route_advertise_list'] = rt_adv_lst
+ def update_vnis(self, data):
+ for conf in data:
+ afs = conf.get('address_family', [])
+ if afs:
+ for af in afs:
+ vnis = af.get('vnis', None)
+ if vnis:
+ vnis_list = []
+ for vni in vnis:
+ vni_dict = {}
+ vni_config = vni['config']
+ vni_number = vni_config.get('vni-number', None)
+ vni_adv_gw = vni_config.get('advertise-default-gw', None)
+ vni_adv_svi = vni_config.get('advertise-svi-ip', None)
+ vni_rd = vni_config.get('route-distinguisher', None)
+ vni_rt_in = vni_config.get('import-rts', [])
+ vni_rt_out = vni_config.get('export-rts', [])
+ if vni_number:
+ vni_dict['vni_number'] = vni_number
+ if vni_adv_gw is not None:
+ vni_dict['advertise_default_gw'] = vni_adv_gw
+ if vni_adv_svi is not None:
+ vni_dict['advertise_svi_ip'] = vni_adv_svi
+ if vni_rd:
+ vni_dict['rd'] = vni_rd
+ if vni_rt_in:
+ vni_dict['rt_in'] = vni_rt_in
+ if vni_rt_out:
+ vni_dict['rt_out'] = vni_rt_out
+ vnis_list.append(vni_dict)
+ af['vnis'] = vnis_list
+
def normalize_af_redis_params(self, af):
norm_af = list()
for e_af in af:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_as_paths/bgp_as_paths.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_as_paths/bgp_as_paths.py
index 822db22a4..31cada350 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_as_paths/bgp_as_paths.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_as_paths/bgp_as_paths.py
@@ -11,7 +11,6 @@ based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -73,8 +72,6 @@ class Bgp_as_pathsFacts(object):
else:
result['permit'] = False
as_path_list_configs.append(result)
- # with open('/root/ansible_log.log', 'a+') as fp:
- # fp.write('as_path_list: ' + str(as_path_list_configs) + '\n')
return as_path_list_configs
def populate_facts(self, connection, ansible_facts, data=None):
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_communities/bgp_communities.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_communities/bgp_communities.py
index ffa294221..ff4827e61 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_communities/bgp_communities.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_communities/bgp_communities.py
@@ -11,7 +11,6 @@ based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -67,26 +66,37 @@ class Bgp_communitiesFacts(object):
match = member_config['match-set-options']
permit_str = member_config.get('openconfig-bgp-policy-ext:action', None)
members = member_config.get("community-member", [])
- result['name'] = name
+ result['name'] = str(name)
result['match'] = match
+ result['members'] = None
+ result['permit'] = False
if permit_str and permit_str == 'PERMIT':
result['permit'] = True
- else:
- result['permit'] = False
if members:
result['type'] = 'expanded' if 'REGEX' in members[0] else 'standard'
+ if result['type'] == 'expanded':
+ members = [':'.join(i.split(':')[1:]) for i in members]
+ members.sort()
+ result['members'] = {'regex': members}
else:
- result['type'] = ''
- if result['type'] == 'expanded':
- members = [':'.join(i.split(':')[1:]) for i in members]
- result['local_as'] = True if "NO_EXPORT_SUBCONFED" in members else False
- result['no_advertise'] = True if "NO_ADVERTISE" in members else False
- result['no_export'] = True if "NO_EXPORT" in members else False
- result['no_peer'] = True if "NOPEER" in members else False
- result['members'] = {'regex': members}
+ result['type'] = 'standard'
+
+ if result['type'] == 'standard':
+ result['local_as'] = None
+ result['no_advertise'] = None
+ result['no_export'] = None
+ result['no_peer'] = None
+ for i in members:
+ if "NO_EXPORT_SUBCONFED" in i:
+ result['local_as'] = True
+ elif "NO_ADVERTISE" in i:
+ result['no_advertise'] = True
+ elif "NO_EXPORT" in i:
+ result['no_export'] = True
+ elif "NOPEER" in i:
+ result['no_peer'] = True
+
bgp_communities_configs.append(result)
- # with open('/root/ansible_log.log', 'a+') as fp:
- # fp.write('bgp_communities: ' + str(bgp_communities_configs) + '\n')
return bgp_communities_configs
def populate_facts(self, connection, ansible_facts, data=None):
@@ -129,17 +139,5 @@ class Bgp_communitiesFacts(object):
:rtype: dictionary
:returns: The generated config
"""
- config = deepcopy(spec)
- try:
- config['name'] = str(conf['name'])
- config['members'] = conf['members']
- config['match'] = conf['match']
- config['type'] = conf['type']
- config['permit'] = conf['permit']
- except TypeError:
- config['name'] = None
- config['members'] = None
- config['match'] = None
- config['type'] = None
- config['permit'] = None
- return utils.remove_empties(config)
+
+ return conf
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_ext_communities/bgp_ext_communities.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_ext_communities/bgp_ext_communities.py
index b1d7c4ad0..814a25d11 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_ext_communities/bgp_ext_communities.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_ext_communities/bgp_ext_communities.py
@@ -11,7 +11,6 @@ based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -69,34 +68,38 @@ class Bgp_ext_communitiesFacts(object):
match = member_config['match-set-options']
permit_str = member_config.get('openconfig-bgp-policy-ext:action', None)
members = member_config.get("ext-community-member", [])
- result['name'] = name
+ result['name'] = str(name)
result['match'] = match.lower()
-
+ result['members'] = dict()
+ result['type'] = 'standard'
+ result['permit'] = False
if permit_str and permit_str == 'PERMIT':
result['permit'] = True
+ if members:
+ result['type'] = 'expanded' if 'REGEX' in members[0] else 'standard'
+ if result['type'] == 'expanded':
+ members = [':'.join(i.split(':')[1:]) for i in members]
+ members_list = list(map(str, members))
+ members_list.sort()
+ result['members'] = {'regex': members_list}
else:
- result['permit'] = False
-
- result['members'] = dict()
- rt = list()
- soo = list()
- regex = list()
- for member in members:
- if member.startswith('route-target'):
- rt.append(':'.join(member.split(':')[1:]))
- elif member.startswith('route-origin'):
- soo.append(':'.join(member.split(':')[1:]))
- elif member.startswith('REGEX'):
- regex.append(':'.join(member.split(':')[1:]))
-
- result['type'] = 'standard'
- if regex and len(regex) > 0:
- result['type'] = 'expanded'
- result['members']['regex'] = regex
- if rt and len(rt) > 0:
- result['members']['route_target'] = rt
- if soo and len(soo) > 0:
- result['members']['route_origin'] = soo
+ rt = list()
+ soo = list()
+ for member in members:
+ if member.startswith('route-origin'):
+ soo.append(':'.join(member.split(':')[1:]))
+ else:
+ rt.append(':'.join(member.split(':')[1:]))
+ route_target_list = list(map(str, rt))
+ route_origin_list = list(map(str, soo))
+ route_target_list.sort()
+ route_origin_list.sort()
+
+ if route_target_list and len(route_target_list) > 0:
+ result['members']['route_target'] = route_target_list
+
+ if route_origin_list and len(route_origin_list) > 0:
+ result['members']['route_origin'] = route_origin_list
bgp_extcommunities_configs.append(result)
@@ -142,17 +145,5 @@ class Bgp_ext_communitiesFacts(object):
:rtype: dictionary
:returns: The generated config
"""
- config = deepcopy(spec)
- try:
- config['name'] = str(conf['name'])
- config['members'] = conf['members']
- config['match'] = conf['match']
- config['type'] = conf['type']
- config['permit'] = conf['permit']
- except TypeError:
- config['name'] = None
- config['members'] = None
- config['match'] = None
- config['type'] = None
- config['permit'] = None
- return utils.remove_empties(config)
+
+ return conf
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors/bgp_neighbors.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors/bgp_neighbors.py
index 903b93de1..687420991 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors/bgp_neighbors.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors/bgp_neighbors.py
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -110,8 +109,8 @@ class Bgp_neighborsFacts(object):
ansible_facts['ansible_network_resources'].pop('bgp_neighbors', None)
facts = {}
if objs:
- params = utils.validate_config(self.argument_spec, {'config': remove_empties_from_list(objs)})
- facts['bgp_neighbors'] = params['config']
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['bgp_neighbors'] = remove_empties_from_list(params['config'])
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors_af/bgp_neighbors_af.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors_af/bgp_neighbors_af.py
index 26119b61c..8f034bb2a 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors_af/bgp_neighbors_af.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors_af/bgp_neighbors_af.py
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -121,7 +120,6 @@ class Bgp_neighbors_afFacts(object):
ipv4_unicast = norm_nei_af.get('ipv4_unicast', None)
ipv6_unicast = norm_nei_af.get('ipv6_unicast', None)
- l2vpn_evpn = norm_nei_af.get('l2vpn_evpn', None)
if ipv4_unicast:
if 'config' in ipv4_unicast:
ip_afi = update_bgp_nbr_pg_ip_afi_dict(ipv4_unicast['config'])
@@ -142,12 +140,6 @@ class Bgp_neighbors_afFacts(object):
if prefix_limit:
norm_nei_af['prefix_limit'] = prefix_limit
norm_nei_af.pop('ipv6_unicast')
- elif l2vpn_evpn:
- if 'config' in l2vpn_evpn:
- prefix_limit = update_bgp_nbr_pg_prefix_limit_dict(l2vpn_evpn['config'])
- if prefix_limit:
- norm_nei_af['prefix_limit'] = prefix_limit
- norm_nei_af.pop('l2vpn_evpn')
norm_neighbor_afs.append(norm_nei_af)
if norm_neighbor_afs:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/copp/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/copp/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/copp/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/copp/copp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/copp/copp.py
new file mode 100644
index 000000000..52a01d3d1
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/copp/copp.py
@@ -0,0 +1,127 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic copp fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.copp.copp import CoppArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+
+
+class CoppFacts(object):
+ """ The sonic copp fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = CoppArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for bfd
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+
+ if not data:
+ copp_cfg = self.get_copp_config(self._module)
+ data = self.update_copp_groups(copp_cfg)
+ objs = self.render_config(self.generated_spec, data)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['copp'] = params['config']
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ return conf
+
+ def update_copp_groups(self, data):
+ config_dict = {}
+ all_copp_groups = []
+ if data:
+ copp_groups = data.get('copp-groups', None)
+ if copp_groups:
+ copp_group_list = copp_groups.get('copp-group', None)
+ if copp_group_list:
+ for group in copp_group_list:
+ group_dict = {}
+ copp_name = group['name']
+ config = group['config']
+ trap_priority = config.get('trap-priority', None)
+ trap_action = config.get('trap-action', None)
+ queue = config.get('queue', None)
+ cir = config.get('cir', None)
+ cbs = config.get('cbs', None)
+
+ if copp_name:
+ group_dict['copp_name'] = copp_name
+ if trap_priority:
+ group_dict['trap_priority'] = trap_priority
+ if trap_action:
+ group_dict['trap_action'] = trap_action
+ if queue:
+ group_dict['queue'] = queue
+ if cir:
+ group_dict['cir'] = cir
+ if cbs:
+ group_dict['cbs'] = cbs
+ if group_dict:
+ all_copp_groups.append(group_dict)
+ if all_copp_groups:
+ config_dict['copp_groups'] = all_copp_groups
+
+ return config_dict
+
+ def get_copp_config(self, module):
+ copp_cfg = None
+ get_copp_path = '/data/openconfig-copp-ext:copp'
+ request = {'path': get_copp_path, 'method': 'get'}
+
+ try:
+ response = edit_config(module, to_request(module, request))
+ if 'openconfig-copp-ext:copp' in response[0][1]:
+ copp_cfg = response[0][1].get('openconfig-copp-ext:copp', None)
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+ return copp_cfg
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_relay/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_relay/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_relay/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_relay/dhcp_relay.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_relay/dhcp_relay.py
new file mode 100644
index 000000000..70f78dc24
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_relay/dhcp_relay.py
@@ -0,0 +1,208 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic dhcp_relay fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.dhcp_relay.dhcp_relay import Dhcp_relayArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils.connection import ConnectionError
+
+SELECT_VALUE_TO_BOOL = {
+ 'ENABLE': True,
+ 'DISABLE': False
+}
+
+
+class Dhcp_relayFacts(object):
+ """ The sonic dhcp_relay fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Dhcp_relayArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for dhcp_relay
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if connection: # just for linting purposes, remove
+ pass
+
+ if not data:
+ dhcp_relay_configs = self.get_dhcp_relay()
+ dhcpv6_relay_configs = self.get_dhcpv6_relay()
+
+ all_relay_configs = {}
+ for intf_name, dhcp_relay_config in dhcp_relay_configs.items():
+ all_relay_configs[intf_name] = {}
+ all_relay_configs[intf_name]['ipv4'] = dhcp_relay_config
+
+ for intf_name, dhcpv6_relay_config in dhcpv6_relay_configs.items():
+ if all_relay_configs.get(intf_name):
+ all_relay_configs[intf_name]['ipv6'] = dhcpv6_relay_config
+ else:
+ all_relay_configs[intf_name] = {}
+ all_relay_configs[intf_name]['ipv6'] = dhcpv6_relay_config
+
+ objs = []
+ for relay_config in all_relay_configs.items():
+ obj = self.render_config(self.generated_spec, relay_config)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('dhcp_relay', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['dhcp_relay'] = utils.remove_empties({'config': params['config']})['config']
+
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+ config['name'] = conf[0]
+
+ if conf[1].get('ipv4'):
+ ipv4_dict = conf[1]['ipv4']
+ if ipv4_dict.get('policy_action'):
+ ipv4_dict['policy_action'] = ipv4_dict['policy_action'].lower()
+
+ ipv4_dict['link_select'] = SELECT_VALUE_TO_BOOL.get(ipv4_dict['link_select'])
+ ipv4_dict['vrf_select'] = SELECT_VALUE_TO_BOOL.get(ipv4_dict['vrf_select'])
+
+ config['ipv4'] = ipv4_dict
+ else:
+ config.pop('ipv4')
+
+ if conf[1].get('ipv6'):
+ ipv6_dict = conf[1]['ipv6']
+ ipv6_dict['vrf_select'] = SELECT_VALUE_TO_BOOL.get(ipv6_dict['vrf_select'])
+
+ config['ipv6'] = ipv6_dict
+ else:
+ config.pop('ipv6')
+
+ return config
+
+ def get_dhcp_relay(self):
+ """Get all DHCP relay configurations available in chassis"""
+ dhcp_relay_interfaces_path = 'data/openconfig-relay-agent:relay-agent/dhcp'
+ method = 'GET'
+ request = [{'path': dhcp_relay_interfaces_path, 'method': method}]
+
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ dhcp_relay_interfaces = []
+ if (response[0][1].get('openconfig-relay-agent:dhcp')
+ and response[0][1]['openconfig-relay-agent:dhcp'].get('interfaces')):
+ dhcp_relay_interfaces = response[0][1]['openconfig-relay-agent:dhcp']['interfaces'].get('interface', [])
+
+ dhcp_relay_configs = {}
+ for interface in dhcp_relay_interfaces:
+ ipv4_dict = {}
+ server_addresses = []
+
+ config = interface.get('config', {})
+ for address in config.get('helper-address', []):
+ temp = {}
+ temp['address'] = address
+ server_addresses.append(temp)
+ ipv4_dict['server_addresses'] = server_addresses
+
+ ipv4_dict['max_hop_count'] = config.get('openconfig-relay-agent-ext:max-hop-count')
+ ipv4_dict['policy_action'] = config.get('openconfig-relay-agent-ext:policy-action')
+ ipv4_dict['source_interface'] = config.get('openconfig-relay-agent-ext:src-intf')
+ ipv4_dict['vrf_name'] = config.get('openconfig-relay-agent-ext:vrf')
+
+ opt_config = interface.get('agent-information-option', {}).get('config', {})
+ ipv4_dict['circuit_id'] = opt_config.get('circuit-id')
+ ipv4_dict['link_select'] = opt_config.get('openconfig-relay-agent-ext:link-select')
+ ipv4_dict['vrf_select'] = opt_config.get('openconfig-relay-agent-ext:vrf-select')
+
+ dhcp_relay_configs[interface['id']] = ipv4_dict
+
+ return dhcp_relay_configs
+
+ def get_dhcpv6_relay(self):
+ """Get all DHCPv6 relay configurations available in chassis"""
+ dhcpv6_relay_interfaces_path = 'data/openconfig-relay-agent:relay-agent/dhcpv6'
+ method = 'GET'
+ request = [{'path': dhcpv6_relay_interfaces_path, 'method': method}]
+
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ dhcpv6_relay_interfaces = []
+ if (response[0][1].get('openconfig-relay-agent:dhcpv6')
+ and response[0][1]['openconfig-relay-agent:dhcpv6'].get('interfaces')):
+ dhcpv6_relay_interfaces = response[0][1]['openconfig-relay-agent:dhcpv6']['interfaces'].get('interface', [])
+
+ dhcpv6_relay_configs = {}
+ for interface in dhcpv6_relay_interfaces:
+ ipv6_dict = {}
+ server_addresses = []
+
+ config = interface.get('config', {})
+ for address in config.get('helper-address', []):
+ temp = {}
+ temp['address'] = address
+ server_addresses.append(temp)
+ ipv6_dict['server_addresses'] = server_addresses
+
+ ipv6_dict['max_hop_count'] = config.get('openconfig-relay-agent-ext:max-hop-count')
+ ipv6_dict['source_interface'] = config.get('openconfig-relay-agent-ext:src-intf')
+ ipv6_dict['vrf_name'] = config.get('openconfig-relay-agent-ext:vrf')
+
+ opt_config = interface.get('options', {}).get('config', {})
+ ipv6_dict['vrf_select'] = opt_config.get('openconfig-relay-agent-ext:vrf-select')
+
+ dhcpv6_relay_configs[interface['id']] = ipv6_dict
+
+ return dhcpv6_relay_configs
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_snooping/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_snooping/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_snooping/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_snooping/dhcp_snooping.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_snooping/dhcp_snooping.py
new file mode 100644
index 000000000..c0464c8f1
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/dhcp_snooping/dhcp_snooping.py
@@ -0,0 +1,213 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic dhcp_snooping fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.dhcp_snooping.dhcp_snooping import Dhcp_snoopingArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils.connection import ConnectionError
+
+
+class Dhcp_snoopingFacts(object):
+ """ The sonic dhcp_snooping fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Dhcp_snoopingArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for dhcp_snooping
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if connection: # just for linting purposes, remove
+ pass
+
+ if not data:
+ data = self.get_dhcp_snooping()
+
+ obj = self.render_config(self.generated_spec, data)
+
+ ansible_facts['ansible_network_resources'].pop('dhcp_snooping', None)
+ facts = {}
+ if obj:
+ params = utils.validate_config(self.argument_spec, {'config': obj})
+ params_cleaned = {'config': utils.remove_empties(params['config'])}
+ facts['dhcp_snooping'] = params_cleaned['config']
+
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def get_dhcp_snooping(self):
+ config = {}
+
+ config['top_level'] = self.get_dhcp_snooping_top_level()
+ config['binding'] = self.get_dhcp_snooping_binding()
+
+ return config
+
+ def get_dhcp_snooping_top_level(self):
+ """Get all DHCP snooping configurations available in chassis"""
+ dhcp_snooping_path = 'data/openconfig-dhcp-snooping:dhcp-snooping'
+ method = 'GET'
+ request = [{'path': dhcp_snooping_path, 'method': method}]
+
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ config = {}
+ if (response[0][1].get('openconfig-dhcp-snooping:dhcp-snooping')):
+ config = response[0][1].get('openconfig-dhcp-snooping:dhcp-snooping')
+
+ return config
+
+ def get_dhcp_snooping_binding(self):
+ dhcp_binding_snooping_path = 'data/openconfig-dhcp-snooping:dhcp-snooping-binding'
+ method = 'GET'
+ request = [{'path': dhcp_binding_snooping_path, 'method': method}]
+
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ config = {}
+ if (response[0][1].get('openconfig-dhcp-snooping:dhcp-snooping-binding')):
+ config = response[0][1].get('openconfig-dhcp-snooping:dhcp-snooping-binding')
+
+ return config
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+
+ v4 = {'afi': 'ipv4'}
+ v6 = {'afi': 'ipv6'}
+ config['afis'] = [v4, v6]
+
+ # Start with the top-level config from the device.
+ top_level = conf.get('top_level', {})
+
+ # Transform the "config" dict from the top-level device config.
+ deviceConfig = top_level.get('config', {})
+
+ v4_enabled = deviceConfig.get('dhcpv4-admin-enable', None)
+ if v4_enabled:
+ v4['enabled'] = True
+ else:
+ v4['enabled'] = False
+ v6_enabled = deviceConfig.get('dhcpv6-admin-enable', None)
+ if v6_enabled:
+ v6['enabled'] = True
+ else:
+ v6['enabled'] = False
+
+ v4_verify_mac = deviceConfig.get('dhcpv4-verify-mac-address', None)
+ if v4_verify_mac is False:
+ v4['verify_mac'] = False
+ else:
+ v4['verify_mac'] = True
+ v6_verify_mac = deviceConfig.get('dhcpv6-verify-mac-address', None)
+ if v6_verify_mac is False:
+ v6['verify_mac'] = False
+ else:
+ v6['verify_mac'] = True
+
+ # Transform the "state" dict from the top-level device config.
+ state = top_level.get('state', {})
+
+ v4_vlans = state.get('dhcpv4-snooping-vlan', [])
+ if len(v4_vlans) > 0:
+ v4['vlans'] = v4_vlans
+ v6_vlans = state.get('dhcpv6-snooping-vlan', [])
+ if len(v6_vlans) > 0:
+ v6['vlans'] = v6_vlans
+
+ STANDARD_ETH = "Eth"
+ PC = 'PortChannel'
+ v4_trusted_intf = state.get('dhcpv4-trusted-intf', [])
+ if len(v4_trusted_intf) > 0:
+ v4['trusted'] = []
+ for intfName in v4_trusted_intf:
+ intf = {}
+ if intfName.startswith(STANDARD_ETH) or intfName.startswith(PC):
+ intf['intf_name'] = intfName
+ else:
+ continue
+ v4['trusted'].append(intf)
+ v6_trusted_intf = state.get('dhcpv6-trusted-intf', [])
+ if len(v6_trusted_intf) > 0:
+ v6['trusted'] = []
+ for intfName in v6_trusted_intf:
+ intf = {}
+ if intfName.startswith(STANDARD_ETH) or intfName.startswith(PC):
+ intf['intf_name'] = intfName
+ else:
+ continue
+ v6['trusted'].append(intf)
+
+ # Transform the binding config from the device.
+ binding = conf.get('binding', {})
+ binding_list_container = binding.get('dhcp-snooping-binding-entry-list', {})
+ binding_list = binding_list_container.get('dhcp-snooping-binding-list', [])
+ if len(binding_list) > 0:
+ v4_entries = []
+ v6_entries = []
+ for entry in binding_list:
+ binding = {}
+ binding['mac_addr'] = entry['mac']
+ binding['ip_addr'] = entry['state']['ipaddress']
+ binding['intf_name'] = entry['state']['intf']
+ binding['vlan_id'] = entry['state']['vlan']
+ if entry['iptype'] == 'ipv4':
+ v4_entries.append(binding)
+ elif entry['iptype'] == 'ipv6':
+ v6_entries.append(binding)
+ if len(v4_entries) > 0:
+ v4['source_bindings'] = v4_entries
+ if len(v6_entries) > 0:
+ v6['source_bindings'] = v6_entries
+
+ return config
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/facts.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/facts.py
index 75622632a..dbe597448 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/facts.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/facts.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# Copyright 2021 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -32,6 +32,7 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.mclag.mclag import MclagFacts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.prefix_lists.prefix_lists import Prefix_listsFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.vlan_mapping.vlan_mapping import Vlan_mappingFacts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.vrfs.vrfs import VrfsFacts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.vxlans.vxlans import VxlansFacts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.users.users import UsersFacts
@@ -42,6 +43,21 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.radius_server.radius_server import Radius_serverFacts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.static_routes.static_routes import Static_routesFacts
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ntp.ntp import NtpFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.logging.logging import LoggingFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.pki.pki import PkiFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.ip_neighbor.ip_neighbor import Ip_neighborFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.port_group.port_group import Port_groupFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.dhcp_relay.dhcp_relay import Dhcp_relayFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.dhcp_snooping.dhcp_snooping import Dhcp_snoopingFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.acl_interfaces.acl_interfaces import Acl_interfacesFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.l2_acls.l2_acls import L2_aclsFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.l3_acls.l3_acls import L3_aclsFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.lldp_global.lldp_global import Lldp_globalFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.mac.mac import MacFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.bfd.bfd import BfdFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.copp.copp import CoppFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.route_maps.route_maps import Route_mapsFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.stp.stp import StpFacts
FACT_LEGACY_SUBSETS = {}
FACT_RESOURCE_SUBSETS = dict(
@@ -59,6 +75,7 @@ FACT_RESOURCE_SUBSETS = dict(
bgp_ext_communities=Bgp_ext_communitiesFacts,
mclag=MclagFacts,
prefix_lists=Prefix_listsFacts,
+ vlan_mapping=Vlan_mappingFacts,
vrfs=VrfsFacts,
vxlans=VxlansFacts,
users=UsersFacts,
@@ -69,6 +86,21 @@ FACT_RESOURCE_SUBSETS = dict(
radius_server=Radius_serverFacts,
static_routes=Static_routesFacts,
ntp=NtpFacts,
+ logging=LoggingFacts,
+ pki=PkiFacts,
+ ip_neighbor=Ip_neighborFacts,
+ port_group=Port_groupFacts,
+ dhcp_relay=Dhcp_relayFacts,
+ dhcp_snooping=Dhcp_snoopingFacts,
+ acl_interfaces=Acl_interfacesFacts,
+ l2_acls=L2_aclsFacts,
+ l3_acls=L3_aclsFacts,
+ lldp_global=Lldp_globalFacts,
+ mac=MacFacts,
+ bfd=BfdFacts,
+ copp=CoppFacts,
+ route_maps=Route_mapsFacts,
+ stp=StpFacts
)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/interfaces/interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/interfaces/interfaces.py
index a36b5d3c0..7ce15fe1b 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/interfaces/interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/interfaces/interfaces.py
@@ -1,6 +1,6 @@
#
# -*- coding: utf-8 -*-
-# © Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# © Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -59,6 +58,7 @@ class InterfacesFacts(object):
if "openconfig-interfaces:interfaces" in response[0][1]:
all_interfaces = response[0][1].get("openconfig-interfaces:interfaces", {})
+
return all_interfaces['interface']
def populate_facts(self, connection, ansible_facts, data=None):
@@ -94,8 +94,8 @@ class InterfacesFacts(object):
if objs:
facts['interfaces'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
- if params:
- facts['interfaces'].extend(params['config'])
+ for cfg in params['config']:
+ facts['interfaces'].append(utils.remove_empties(cfg))
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
@@ -115,7 +115,7 @@ class InterfacesFacts(object):
def transform_config(self, conf):
exist_cfg = conf['config']
- trans_cfg = None
+ trans_cfg = dict()
is_loop_back = False
name = conf['name']
@@ -125,16 +125,29 @@ class InterfacesFacts(object):
if pos > 0:
name = name[0:pos]
- if not (is_loop_back and self.is_loop_back_already_esist(name)) and (name != "eth0"):
- trans_cfg = dict()
+ if not (is_loop_back and self.is_loop_back_already_exist(name)) and (name != "eth0") and (name != "Management0"):
trans_cfg['name'] = name
if is_loop_back:
self.update_loop_backs(name)
else:
trans_cfg['enabled'] = exist_cfg['enabled'] if exist_cfg.get('enabled') is not None else True
- trans_cfg['description'] = exist_cfg['description'] if exist_cfg.get('description') else ""
+ trans_cfg['description'] = exist_cfg.get('description')
trans_cfg['mtu'] = exist_cfg['mtu'] if exist_cfg.get('mtu') else 9100
+ if name.startswith('Eth') and 'openconfig-if-ethernet:ethernet' in conf:
+ if conf['openconfig-if-ethernet:ethernet'].get('config', None):
+ eth_conf = conf['openconfig-if-ethernet:ethernet']['config']
+ if 'auto-negotiate' in eth_conf:
+ trans_cfg['auto_negotiate'] = eth_conf['auto-negotiate']
+ trans_cfg['speed'] = eth_conf['port-speed'].split(':', 1)[-1]
+ if 'openconfig-if-ethernet-ext2:advertised-speed' in eth_conf:
+ adv_speed_str = eth_conf['openconfig-if-ethernet-ext2:advertised-speed']
+ if adv_speed_str != '':
+ trans_cfg['advertised_speed'] = adv_speed_str.split(",")
+ trans_cfg['advertised_speed'].sort()
+ if 'openconfig-if-ethernet-ext2:port-fec' in eth_conf:
+ trans_cfg['fec'] = eth_conf['openconfig-if-ethernet-ext2:port-fec'].split(':', 1)[-1]
+
return trans_cfg
def reset_loop_backs(self):
@@ -143,5 +156,5 @@ class InterfacesFacts(object):
def update_loop_backs(self, loop_back):
self.loop_backs += "{0},".format(loop_back)
- def is_loop_back_already_esist(self, loop_back):
+ def is_loop_back_already_exist(self, loop_back):
return (",{0},".format(loop_back) in self.loop_backs)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ip_neighbor/ip_neighbor.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ip_neighbor/ip_neighbor.py
new file mode 100644
index 000000000..4c077c43f
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ip_neighbor/ip_neighbor.py
@@ -0,0 +1,126 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic ip_neighbor fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import re
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ip_neighbor.ip_neighbor import Ip_neighborArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils.connection import ConnectionError
+
+GET = "get"
+
+
+class Ip_neighborFacts(object):
+ """ The sonic ip_neighbor fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Ip_neighborArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for ip_neighbor
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ # typically data is populated from the current device configuration
+ # data = connection.get('show running-config | section neighbor')
+ # using mock data instead
+ data = self.get_ip_neighbor_global()
+
+ objs = self.render_config(self.generated_spec, data)
+
+ ansible_facts['ansible_network_resources'].pop('ip_neighbor', None)
+
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['ip_neighbor'] = params['config']
+
+ ansible_facts['ansible_network_resources'].update(facts)
+
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ return conf
+
+ def get_ip_neighbor_global(self):
+ """Get IP neighbor global configurations"""
+
+ config_path = "data/openconfig-neighbor:neighbor-globals/neighbor-global=Values/config"
+ config_request = [{"path": config_path, "method": GET}]
+ config_response = []
+
+ ip_neigh_glb_conf = dict()
+
+ try:
+ config_response = edit_config(self._module, to_request(self._module, config_request))
+ except ConnectionError as exc:
+ if re.search("code.*404", str(exc)):
+ # 'code': 404, 'error-message': 'Resource not found'
+ return ip_neigh_glb_conf
+ else:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ config = dict()
+ if 'openconfig-neighbor:config' in config_response[0][1]:
+ config = config_response[0][1].get('openconfig-neighbor:config', {})
+
+ if "ipv4-arp-timeout" in config:
+ ip_neigh_glb_conf["ipv4_arp_timeout"] = config["ipv4-arp-timeout"]
+
+ if "ipv4-drop-neighbor-aging-time" in config:
+ ip_neigh_glb_conf["ipv4_drop_neighbor_aging_time"] = config["ipv4-drop-neighbor-aging-time"]
+
+ if "ipv6-drop-neighbor-aging-time" in config:
+ ip_neigh_glb_conf["ipv6_drop_neighbor_aging_time"] = config["ipv6-drop-neighbor-aging-time"]
+
+ if "ipv6-nd-cache-expiry" in config:
+ ip_neigh_glb_conf["ipv6_nd_cache_expiry"] = config["ipv6-nd-cache-expiry"]
+
+ if "num-local-neigh" in config:
+ ip_neigh_glb_conf["num_local_neigh"] = config["num-local-neigh"]
+
+ return ip_neigh_glb_conf
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_acls/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_acls/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_acls/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_acls/l2_acls.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_acls/l2_acls.py
new file mode 100644
index 000000000..5644cf876
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_acls/l2_acls.py
@@ -0,0 +1,236 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic l2_acls fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible.module_utils.connection import ConnectionError
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.l2_acls.l2_acls import L2_aclsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+
+ETHERTYPE_FORMAT = '0x{:04x}'
+
+action_payload_to_value_map = {
+ 'ACCEPT': 'permit',
+ 'DISCARD': 'discard',
+ 'DO_NOT_NAT': 'do-not-nat',
+ 'DROP': 'deny',
+ 'TRANSIT': 'transit',
+}
+ethertype_payload_to_protocol_map = {
+ '0x0800': 'ipv4',
+ '0x0806': 'arp',
+ '0x86dd': 'ipv6',
+ 'ETHERTYPE_ARP': 'arp',
+ 'ETHERTYPE_IPV4': 'ipv4',
+ 'ETHERTYPE_IPV6': 'ipv6'
+}
+ethertype_payload_to_value_map = {
+ 'ETHERTYPE_LLDP': '0x88cc',
+ 'ETHERTYPE_MPLS': '0x8847',
+ 'ETHERTYPE_ROCE': '0x8915'
+}
+pcp_value_to_traffic_map = {
+ 0: 'be',
+ 1: 'bk',
+ 2: 'ee',
+ 3: 'ca',
+ 4: 'vi',
+ 5: 'vo',
+ 6: 'ic',
+ 7: 'nc'
+}
+
+
+class L2_aclsFacts(object):
+ """ The sonic l2_acls fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = L2_aclsArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for l2_acls
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if connection: # just for linting purposes, remove
+ pass
+
+ if not data:
+ l2_acls_configs = self.get_l2_acls()
+
+ objs = []
+ for l2_acl_config in l2_acls_configs:
+ obj = self.render_config(self.generated_spec, l2_acl_config)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('l2_acls', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['l2_acls'] = utils.remove_empties({'config': params['config']})['config']
+
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+ config['name'] = conf['name']
+ config['remark'] = conf['remark']
+ config['rules'] = conf['rules']
+
+ for rule in config['rules']:
+ if ":" in rule['action']:
+ rule['action'] = rule['action'].split(":")[-1]
+ rule['action'] = action_payload_to_value_map[rule['action']]
+
+ rule['source'] = {}
+ rule['destination'] = {}
+ if rule.get('l2') is None:
+ rule['source']['any'] = True
+ rule['destination']['any'] = True
+ continue
+
+ l2_config = rule.pop('l2')
+ if l2_config.get('source-mac') and l2_config.get('source-mac-mask'):
+ if l2_config['source-mac-mask'].lower() == 'ff:ff:ff:ff:ff:ff':
+ rule['source']['host'] = l2_config['source-mac'].lower()
+ else:
+ rule['source']['address'] = l2_config['source-mac'].lower()
+ rule['source']['address_mask'] = l2_config['source-mac-mask'].lower()
+ elif l2_config.get('source-mac'):
+ rule['source']['host'] = l2_config['source-mac'].lower()
+ else:
+ rule['source']['any'] = True
+
+ if l2_config.get('destination-mac') and l2_config.get('destination-mac-mask'):
+ if l2_config['destination-mac-mask'].lower() == 'ff:ff:ff:ff:ff:ff':
+ rule['destination']['host'] = l2_config['destination-mac'].lower()
+ else:
+ rule['destination']['address'] = l2_config['destination-mac'].lower()
+ rule['destination']['address_mask'] = l2_config['destination-mac-mask'].lower()
+ elif l2_config.get('destination-mac'):
+ rule['destination']['host'] = l2_config['destination-mac'].lower()
+ else:
+ rule['destination']['any'] = True
+
+ if l2_config.get('ethertype'):
+ ethertype = l2_config['ethertype']
+ rule['ethertype'] = {}
+ if isinstance(ethertype, str):
+ ethertype = ethertype.split(':')[-1]
+ if ethertype in ethertype_payload_to_protocol_map:
+ rule['ethertype'][ethertype_payload_to_protocol_map[ethertype]] = True
+ else:
+ rule['ethertype']['value'] = ethertype_payload_to_value_map[ethertype]
+ else:
+ ethertype = ETHERTYPE_FORMAT.format(ethertype)
+ if ethertype in ethertype_payload_to_protocol_map:
+ rule['ethertype'][ethertype_payload_to_protocol_map[ethertype]] = True
+ else:
+ rule['ethertype']['value'] = ethertype
+
+ if l2_config.get('openconfig-acl-ext:vlanid'):
+ rule['vlan_id'] = l2_config['openconfig-acl-ext:vlanid']
+ if l2_config.get('openconfig-acl-ext:vlan-tag-format') == 'openconfig-acl-ext:MULTI_TAGGED':
+ rule['vlan_tag_format'] = {'multi_tagged': True}
+
+ if l2_config.get('openconfig-acl-ext:dei') is not None:
+ rule['dei'] = l2_config['openconfig-acl-ext:dei']
+
+ if l2_config.get('openconfig-acl-ext:pcp') is not None:
+ rule['pcp'] = {}
+ if l2_config.get('openconfig-acl-ext:pcp-mask') is not None:
+ rule['pcp']['value'] = l2_config['openconfig-acl-ext:pcp']
+ rule['pcp']['mask'] = l2_config['openconfig-acl-ext:pcp-mask']
+ else:
+ rule['pcp']['traffic_type'] = pcp_value_to_traffic_map[l2_config['openconfig-acl-ext:pcp']]
+
+ return config
+
+ def get_l2_acls(self):
+ """Get all l2 acl configurations available in chassis"""
+ acls_path = 'data/openconfig-acl:acl/acl-sets'
+ method = 'GET'
+ request = [{'path': acls_path, 'method': method}]
+
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ acls = []
+ if response[0][1].get('openconfig-acl:acl-sets'):
+ acls = response[0][1]['openconfig-acl:acl-sets'].get('acl-set', [])
+
+ l2_acls_configs = []
+ for acl in acls:
+ acl_config = {}
+ acl_rules = []
+
+ config = acl['config']
+ if config.get('type') not in ('ACL_L2', 'openconfig-acl:ACL_L2'):
+ continue
+
+ acl_config['name'] = config['name']
+ acl_config['remark'] = config.get('description')
+ acl_config['rules'] = acl_rules
+
+ acl_entries = acl.get('acl-entries', {}).get('acl-entry', [])
+ for acl_entry in acl_entries:
+ acl_rule = {}
+
+ acl_entry_config = acl_entry['config']
+ acl_rule['sequence_num'] = acl_entry_config['sequence-id']
+ acl_rule['remark'] = acl_entry_config.get('description')
+
+ acl_rule['action'] = acl_entry['actions']['config']['forwarding-action']
+ acl_rule['l2'] = acl_entry.get('l2', {}).get('config', {})
+
+ acl_rules.append(acl_rule)
+
+ l2_acls_configs.append(acl_config)
+
+ return l2_acls_configs
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py
index 07d7f97dd..78d5b002e 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -47,8 +46,8 @@ class L2_interfacesFacts(object):
self.generated_spec = utils.generate_dict(facts_argument_spec)
- def vlan_range_to_list(self, in_range):
- range_bounds = in_range.split('-')
+ def vlan_range_to_list(self, in_range, range_str):
+ range_bounds = in_range.split(range_str)
range_bottom = int(range_bounds[0])
range_top = int(range_bounds[1]) + 1
vlan_list = list(range(range_bottom, range_top))
@@ -79,15 +78,22 @@ class L2_interfacesFacts(object):
new_det['trunk'] = {}
new_det['trunk']['allowed_vlans'] = []
- # Save trunk vlans as a list of single vlan dicts: Convert
- # any ranges to lists of individual vlan dicts and merge
- # each resulting "range list" onto the main list for the
- # interface.
+ # Save trunk vlans and vlan ranges as a list of single vlan dicts:
+ # Convert single vlan values to strings and convert any ranges
+ # to the argspec range format. (This block assumes that any string
+ # value received is a range, using either ".." or "-" as a
+ # separator between the boundaries of the range. It also assumes
+ # that any non-string value received is an integer specifying a
+ # single vlan.)
for vlan in open_cfg_vlan['config'].get('trunk-vlans'):
- if isinstance(vlan, str) and '-' in vlan:
- new_det['trunk']['allowed_vlans'].extend(self.vlan_range_to_list(vlan))
+ vlan_argspec = ''
+ if isinstance(vlan, str):
+ vlan_argspec = vlan.replace('"', '')
+ if '..' in vlan_argspec:
+ vlan_argspec = vlan_argspec.replace('..', '-')
else:
- new_det['trunk']['allowed_vlans'].append({'vlan': vlan})
+ vlan_argspec = str(vlan)
+ new_det['trunk']['allowed_vlans'].append({'vlan': vlan_argspec})
l2_interfaces.append(new_det)
return l2_interfaces
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_acls/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_acls/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_acls/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_acls/l3_acls.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_acls/l3_acls.py
new file mode 100644
index 000000000..799064c9b
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_acls/l3_acls.py
@@ -0,0 +1,322 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic l3_acls fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible.module_utils.connection import ConnectionError
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.l3_acls.l3_acls import L3_aclsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+
+IPV4_HOST_MASK = '/32'
+IPV6_HOST_MASK = '/128'
+L4_PORT_START = 0
+L4_PORT_END = 65535
+
+action_payload_to_value_map = {
+ 'ACCEPT': 'permit',
+ 'DISCARD': 'discard',
+ 'DO_NOT_NAT': 'do-not-nat',
+ 'DROP': 'deny',
+ 'TRANSIT': 'transit',
+}
+protocol_payload_to_value_map = {
+ 'IP_ICMP': 'icmp',
+ 'IP_IGMP': 2,
+ 'IP_TCP': 'tcp',
+ 'IP_UDP': 'udp',
+ 'IP_RSVP': 46,
+ 'IP_GRE': 47,
+ 'IP_AUTH': 51,
+ 'IP_PIM': 103,
+ 'IP_L2TP': 115
+}
+protocol_number_to_name_map = {
+ 1: 'icmp',
+ 6: 'tcp',
+ 17: 'udp',
+ 58: 'icmpv6'
+}
+dscp_value_to_name_map = {
+ 0: 'default',
+ 8: 'cs1',
+ 16: 'cs2',
+ 24: 'cs3',
+ 32: 'cs4',
+ 40: 'cs5',
+ 48: 'cs6',
+ 56: 'cs7',
+ 10: 'af11',
+ 12: 'af12',
+ 14: 'af13',
+ 18: 'af21',
+ 20: 'af22',
+ 22: 'af23',
+ 26: 'af31',
+ 28: 'af32',
+ 30: 'af33',
+ 34: 'af41',
+ 36: 'af42',
+ 38: 'af43',
+ 46: 'ef',
+ 44: 'voice_admit'
+}
+
+
+class L3_aclsFacts(object):
+ """ The sonic l3_acls fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = L3_aclsArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for l3_acls
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if connection: # just for linting purposes, remove
+ pass
+
+ if not data:
+ l3_acls_configs = self.get_l3_acls()
+
+ objs = []
+ for l3_acl_config in l3_acls_configs:
+ obj = self.render_config(self.generated_spec, l3_acl_config)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('l3_acls', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['l3_acls'] = utils.remove_empties({'config': params['config']})['config']
+
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+ config['address_family'] = conf['address_family']
+ config['acls'] = conf['acls']
+ is_ipv4 = bool(config['address_family'] == 'ipv4')
+
+ for acl in config['acls']:
+ for rule in acl['rules']:
+ rule['source'] = {}
+ rule['destination'] = {}
+ rule['protocol'] = {}
+ rule['protocol_options'] = {}
+
+ if ":" in rule['action']:
+ rule['action'] = rule['action'].split(":")[-1]
+ rule['action'] = action_payload_to_value_map[rule['action']]
+
+ l2_config = rule.pop('l2', None)
+ l3_config = rule.pop('l3', None)
+ l4_config = rule.pop('l4', None)
+ if l3_config is None:
+ if is_ipv4:
+ rule['protocol']['name'] = 'ip'
+ else:
+ rule['protocol']['name'] = 'ipv6'
+
+ rule['source']['any'] = True
+ rule['destination']['any'] = True
+ continue
+
+ protocol = l3_config.get('protocol')
+ if protocol is not None:
+ if isinstance(protocol, str):
+ protocol = protocol.replace('openconfig-packet-match-types:', '')
+ protocol = protocol_payload_to_value_map[protocol]
+ if isinstance(protocol, str):
+ rule['protocol']['name'] = protocol
+ else:
+ rule['protocol']['number'] = protocol
+ else:
+ protocol = protocol_number_to_name_map.get(protocol, protocol)
+ if isinstance(protocol, str):
+ rule['protocol']['name'] = protocol
+ else:
+ rule['protocol']['number'] = protocol
+ else:
+ if is_ipv4:
+ rule['protocol']['name'] = 'ip'
+ else:
+ rule['protocol']['name'] = 'ipv6'
+
+ rule['source'] = self._convert_ip_addr_to_spec_fmt(l3_config.get('source-address'), is_ipv4)
+ rule['destination'] = self._convert_ip_addr_to_spec_fmt(l3_config.get('destination-address'), is_ipv4)
+ if protocol in ('tcp', 'udp'):
+ rule['source']['port_number'] = self._convert_l4_port_to_spec_fmt(l4_config.get('source-port'))
+ rule['destination']['port_number'] = self._convert_l4_port_to_spec_fmt(l4_config.get('destination-port'))
+
+ if protocol in ('icmp', 'icmpv6'):
+ rule['protocol_options'][protocol] = {
+ 'code': l4_config.get('openconfig-acl-ext:icmp-code'),
+ 'type': l4_config.get('openconfig-acl-ext:icmp-type')
+ }
+ elif protocol == 'tcp':
+ rule['protocol_options']['tcp'] = {}
+ if l4_config.get('openconfig-acl-ext:tcp-session-established'):
+ rule['protocol_options']['tcp']['established'] = True
+ else:
+ for flag in l4_config.get('tcp-flags', []):
+ flag = flag.split(':')[-1].replace('TCP_', '').lower()
+ rule['protocol_options']['tcp'][flag] = True
+
+ dscp = l3_config.get('dscp')
+ if dscp in dscp_value_to_name_map:
+ rule['dscp'] = {dscp_value_to_name_map[dscp]: True}
+ else:
+ rule['dscp'] = {'value': dscp}
+
+ rule['vlan_id'] = l2_config.get('openconfig-acl-ext:vlanid')
+
+ return config
+
+ def get_l3_acls(self):
+ """Get all l3 acl configurations available in chassis"""
+ acls_path = 'data/openconfig-acl:acl/acl-sets'
+ method = 'GET'
+ request = [{'path': acls_path, 'method': method}]
+
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ acls = []
+ if response[0][1].get('openconfig-acl:acl-sets'):
+ acls = response[0][1]['openconfig-acl:acl-sets'].get('acl-set', [])
+
+ ipv4_acls_configs = []
+ ipv6_acls_configs = []
+ for acl in acls:
+ is_ipv4 = False
+ acl_config = {}
+ acl_rules = []
+
+ config = acl['config']
+ if config.get('type') in ('ACL_IPV4', 'openconfig-acl:ACL_IPV4'):
+ is_ipv4 = True
+ elif config.get('type') in ('ACL_IPV6', 'openconfig-acl:ACL_IPV6'):
+ is_ipv4 = False
+ else:
+ continue
+
+ acl_config['name'] = config['name']
+ acl_config['remark'] = config.get('description')
+ acl_config['rules'] = acl_rules
+
+ acl_entries = acl.get('acl-entries', {}).get('acl-entry', [])
+ for acl_entry in acl_entries:
+ acl_rule = {}
+
+ acl_entry_config = acl_entry['config']
+ acl_rule['sequence_num'] = acl_entry_config['sequence-id']
+ acl_rule['remark'] = acl_entry_config.get('description')
+
+ acl_rule['action'] = acl_entry['actions']['config']['forwarding-action']
+ acl_rule['l2'] = acl_entry.get('l2', {}).get('config', {})
+ if is_ipv4:
+ acl_rule['l3'] = acl_entry.get('ipv4', {}).get('config', {})
+ else:
+ acl_rule['l3'] = acl_entry.get('ipv6', {}).get('config', {})
+ acl_rule['l4'] = acl_entry.get('transport', {}).get('config', {})
+
+ acl_rules.append(acl_rule)
+
+ if is_ipv4:
+ ipv4_acls_configs.append(acl_config)
+ else:
+ ipv6_acls_configs.append(acl_config)
+
+ l3_acls_configs = []
+ if ipv4_acls_configs:
+ l3_acls_configs.append({'address_family': 'ipv4', 'acls': ipv4_acls_configs})
+ if ipv6_acls_configs:
+ l3_acls_configs.append({'address_family': 'ipv6', 'acls': ipv6_acls_configs})
+
+ return l3_acls_configs
+
+ @staticmethod
+ def _convert_ip_addr_to_spec_fmt(ip_addr, is_ipv4=False):
+ spec_fmt = {}
+ if ip_addr is not None:
+ ip_addr = ip_addr.lower()
+ if is_ipv4:
+ host_mask = IPV4_HOST_MASK
+ else:
+ host_mask = IPV6_HOST_MASK
+
+ if ip_addr.endswith(host_mask):
+ spec_fmt['host'] = ip_addr.replace(host_mask, '')
+ else:
+ spec_fmt['prefix'] = ip_addr
+ else:
+ spec_fmt['any'] = True
+
+ return spec_fmt
+
+ @staticmethod
+ def _convert_l4_port_to_spec_fmt(l4_port):
+ spec_fmt = {}
+ if l4_port is not None:
+ if isinstance(l4_port, str) and '..' in l4_port:
+ l4_port = [int(i) for i in l4_port.split('..')]
+ if l4_port[0] == L4_PORT_START:
+ spec_fmt['lt'] = l4_port[1]
+ elif l4_port[1] == L4_PORT_END:
+ spec_fmt['gt'] = l4_port[0]
+ else:
+ spec_fmt['range'] = {
+ 'begin': l4_port[0],
+ 'end': l4_port[1]
+ }
+ else:
+ spec_fmt['eq'] = int(l4_port)
+
+ return spec_fmt
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_interfaces/l3_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_interfaces/l3_interfaces.py
index 69a6dcd44..e91a2b033 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_interfaces/l3_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_interfaces/l3_interfaces.py
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py
index 728196813..d83659d92 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lldp_global/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lldp_global/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lldp_global/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lldp_global/lldp_global.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lldp_global/lldp_global.py
new file mode 100644
index 000000000..75f3dad51
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lldp_global/lldp_global.py
@@ -0,0 +1,114 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic lldp_global fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.lldp_global.lldp_global import Lldp_globalArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils.connection import ConnectionError
+
+
+GET = "get"
+
+
+class Lldp_globalFacts(object):
+ """ The sonic lldp_global fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Lldp_globalArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for lldp_global
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if connection: # just for linting purposes, remove
+ pass
+
+ obj = self.get_all_lldp_configs()
+
+ ansible_facts['ansible_network_resources'].pop('lldp_global', None)
+ facts = {}
+ if obj:
+ params = utils.validate_config(self.argument_spec, {'config': obj})
+ facts['lldp_global'] = utils.remove_empties(params['config'])
+
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ return conf
+
+ def get_all_lldp_configs(self):
+ """Get all the lldp_global configured in the device"""
+ request = [{"path": "data/openconfig-lldp:lldp/config", "method": GET}]
+ lldp_global_data = {}
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ lldp_global_data['tlv_select'] = {}
+ lldp_global_data['tlv_select']['management_address'] = True
+ lldp_global_data['tlv_select']['system_capabilities'] = True
+ lldp_global_data['enable'] = True
+ if 'openconfig-lldp:config' in response[0][1]:
+ raw_lldp_global_data = response[0][1]['openconfig-lldp:config']
+ if 'enabled' in raw_lldp_global_data:
+ lldp_global_data['enable'] = raw_lldp_global_data['enabled']
+ if 'hello-timer' in raw_lldp_global_data:
+ lldp_global_data['hello_time'] = raw_lldp_global_data['hello-timer']
+ if 'openconfig-lldp-ext:mode' in raw_lldp_global_data:
+ lldp_global_data['mode'] = raw_lldp_global_data['openconfig-lldp-ext:mode'].lower()
+ if 'system-description' in raw_lldp_global_data:
+ lldp_global_data['system_description'] = raw_lldp_global_data['system-description']
+ if 'system-name' in raw_lldp_global_data:
+ lldp_global_data['system_name'] = raw_lldp_global_data['system-name']
+ if 'openconfig-lldp-ext:multiplier' in raw_lldp_global_data:
+ lldp_global_data['multiplier'] = raw_lldp_global_data['openconfig-lldp-ext:multiplier']
+ if 'suppress-tlv-advertisement' in raw_lldp_global_data:
+ for tlv_select in raw_lldp_global_data['suppress-tlv-advertisement']:
+ tlv_select = tlv_select.replace('openconfig-lldp-types:', '').lower()
+ if tlv_select in ('management_address', 'system_capabilities'):
+ lldp_global_data['tlv_select'][tlv_select] = False
+ return lldp_global_data
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/logging/logging.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/logging/logging.py
new file mode 100644
index 000000000..c3c05035e
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/logging/logging.py
@@ -0,0 +1,128 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic logging fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.logging.logging import LoggingArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils.connection import ConnectionError
+
+GET = "get"
+
+
+class LoggingFacts(object):
+ """ The sonic logging fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = LoggingArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for logging
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ # typically data is populated from the current device configuration
+ # data = connection.get('show running-config | section ^interface')
+ # using mock data instead
+ data = self.get_logging_configuration()
+
+ obj = self.render_config(self.generated_spec, data)
+
+ ansible_facts['ansible_network_resources'].pop('logging', None)
+ facts = {}
+ if obj:
+ params = utils.validate_config(self.argument_spec, {'config': obj})
+ facts['logging'] = params['config']
+
+ ansible_facts['ansible_network_resources'].update(facts)
+
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ return conf
+
+ def get_logging_configuration(self):
+ """Get all logging configuration"""
+
+ config_request = [{"path": "data/openconfig-system:system/logging", "method": GET}]
+ config_response = []
+ try:
+ config_response = edit_config(self._module, to_request(self._module, config_request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ logging_response = dict()
+ if 'openconfig-system:logging' in config_response[0][1]:
+ logging_response = config_response[0][1].get('openconfig-system:logging', {})
+
+ remote_servers = []
+ if 'remote-servers' in logging_response:
+ remote_servers = logging_response['remote-servers'].get('remote-server', [])
+
+ logging_config = dict()
+
+ logging_servers = []
+ for remote_server in remote_servers:
+ rs_config = remote_server.get('config', {})
+ logging_server = {}
+ logging_server['host'] = rs_config['host']
+ if 'openconfig-system-ext:message-type' in rs_config:
+ logging_server['message_type'] = rs_config['openconfig-system-ext:message-type']
+ if 'openconfig-system-ext:source-interface' in rs_config:
+ logging_server['source_interface'] = rs_config['openconfig-system-ext:source-interface']
+ if logging_server['source_interface'].startswith("Management") or \
+ logging_server['source_interface'].startswith("Mgmt"):
+ logging_server['source_interface'] = 'eth0'
+ if 'openconfig-system-ext:vrf-name' in rs_config:
+ logging_server['vrf'] = rs_config['openconfig-system-ext:vrf-name']
+ if 'remote-port' in rs_config:
+ logging_server['remote_port'] = rs_config['remote-port']
+
+ logging_servers.append(logging_server)
+
+ logging_config['remote_servers'] = logging_servers
+
+ return logging_config
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mac/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mac/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mac/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mac/mac.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mac/mac.py
new file mode 100644
index 000000000..26f705040
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mac/mac.py
@@ -0,0 +1,151 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic mac_address fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ remove_empties_from_list
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.mac.mac import MacArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import (
+ get_all_vrfs,
+)
+
+NETWORK_INSTANCE_PATH = '/data/openconfig-network-instance:network-instances/network-instance'
+
+
+class MacFacts(object):
+ """ The sonic mac fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = MacArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for mac_address
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+ if connection: # just for linting purposes, remove
+ pass
+
+ if not data:
+ data = self.update_mac(self._module)
+ # operate on a collection of resource x
+ for conf in data:
+ if conf:
+ obj = self.render_config(conf)
+ # split the config into instances of the resource
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('mac', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': remove_empties_from_list(objs)})
+ facts['mac'] = params['config']
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def render_config(self, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ return conf
+
+ def update_mac(self, module):
+ mac_address_cfg_list = []
+ vrfs = get_all_vrfs(module)
+ for vrf_name in vrfs:
+ aging_time = self.get_config(vrf_name, module, 'fdb/config/mac-aging-time', 'openconfig-network-instance:mac-aging-time')
+ dampening_cfg_dict = self.get_config(vrf_name, module, 'openconfig-mac-dampening:mac-dampening/config', 'openconfig-mac-dampening:config')
+ entries_dict = self.get_config(vrf_name, module, 'fdb/mac-table/entries', 'openconfig-network-instance:entries')
+ cfg_dict = {}
+ mac_dict = {}
+ mac_table_entries = []
+ dampening_interval = dampening_cfg_dict.get('interval', None)
+ dampening_threshold = dampening_cfg_dict.get('threshold', None)
+
+ if entries_dict:
+ entry_list = entries_dict.get('entry', [])
+ for entry in entry_list:
+ entry_dict = {}
+ mac_address = entry.get('mac-address', None)
+ vlan_id = entry.get('vlan', None)
+ interface = entry.get('interface', {}).get('interface-ref', {}).get('config', {}).get('interface', None)
+ if mac_address:
+ entry_dict['mac_address'] = mac_address
+ if vlan_id:
+ entry_dict['vlan_id'] = vlan_id
+ if interface:
+ entry_dict['interface'] = interface
+ if entry_dict:
+ mac_table_entries.append(entry_dict)
+
+ if aging_time:
+ mac_dict['aging_time'] = aging_time
+ if dampening_interval:
+ mac_dict['dampening_interval'] = dampening_interval
+ if dampening_threshold:
+ mac_dict['dampening_threshold'] = dampening_threshold
+ if mac_table_entries:
+ mac_dict['mac_table_entries'] = mac_table_entries
+ if mac_dict:
+ cfg_dict['mac'] = mac_dict
+ cfg_dict['vrf_name'] = vrf_name
+ mac_address_cfg_list.append(cfg_dict)
+
+ return mac_address_cfg_list
+
+ def get_config(self, vrf_name, module, path, name):
+ cfg_dict = {}
+ get_path = '%s=%s/%s' % (NETWORK_INSTANCE_PATH, vrf_name, path)
+ request = {'path': get_path, 'method': 'get'}
+
+ try:
+ response = edit_config(module, to_request(module, request))
+ if name in response[0][1]:
+ cfg_dict = response[0][1].get(name, None)
+ except Exception as exc:
+ pass
+
+ return cfg_dict
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mclag/mclag.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mclag/mclag.py
index 69864cdf9..9c57f6cc0 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mclag/mclag.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mclag/mclag.py
@@ -23,6 +23,9 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
to_request,
edit_config
)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_ranges_in_list
+)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.mclag.mclag import MclagArgs
from ansible.module_utils.connection import ConnectionError
@@ -76,7 +79,7 @@ class MclagFacts(object):
facts = {}
if objs:
params = utils.validate_config(self.argument_spec, {'config': objs})
- facts['mclag'] = params['config']
+ facts['mclag'] = utils.remove_empties(params['config'])
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
@@ -118,6 +121,8 @@ class MclagFacts(object):
config['peer_link'] = domain_config['peer-link']
if domain_config.get('mclag-system-mac', None):
config['system_mac'] = domain_config['mclag-system-mac']
+ if domain_config.get('delay-restore', None):
+ config['delay_restore'] = domain_config['delay-restore']
if conf.get('vlan-interfaces', None) and conf['vlan-interfaces'].get('vlan-interface', None):
vlans_list = []
@@ -125,7 +130,15 @@ class MclagFacts(object):
for vlan in vlan_data:
vlans_list.append({'vlan': vlan['name']})
if vlans_list:
- config['unique_ip'] = {'vlans': vlans_list}
+ config['unique_ip'] = {'vlans': self.get_vlan_range_list(vlans_list)}
+
+ if conf.get('vlan-ifs', None) and conf['vlan-ifs'].get('vlan-if', None):
+ vlans_list = []
+ vlan_data = conf['vlan-ifs']['vlan-if']
+ for vlan in vlan_data:
+ vlans_list.append({'vlan': vlan['name']})
+ if vlans_list:
+ config['peer_gateway'] = {'vlans': self.get_vlan_range_list(vlans_list)}
if conf.get('interfaces', None) and conf['interfaces'].get('interface', None):
portchannels_list = []
@@ -136,4 +149,27 @@ class MclagFacts(object):
if portchannels_list:
config['members'] = {'portchannels': portchannels_list}
+ if conf.get('mclag-gateway-macs', None) and conf['mclag-gateway-macs'].get('mclag-gateway-mac', None):
+ gw_mac_data = conf['mclag-gateway-macs']['mclag-gateway-mac']
+ if gw_mac_data[0].get('config', None) and gw_mac_data[0]['config'].get('gateway-mac', None):
+ config['gateway_mac'] = gw_mac_data[0]['config']['gateway-mac']
+
return config
+
+ @staticmethod
+ def get_vlan_range_list(vlans_list):
+ """Returns list of VLAN ranges for given list of VLANs"""
+ vlan_range_list = []
+ vlan_id_list = []
+
+ for vlan in vlans_list:
+ match = re.match(r'Vlan(\d+)', vlan['vlan'])
+ if match:
+ vlan_id_list.append(int(match.group(1)))
+
+ if vlan_id_list:
+ vlan_id_list.sort()
+ for vlan_range in get_ranges_in_list(vlan_id_list):
+ vlan_range_list.append({'vlan': 'Vlan{0}'.format('-'.join(map(str, (vlan_range[0], vlan_range[-1])[:len(vlan_range)])))})
+
+ return vlan_range_list
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ntp/ntp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ntp/ntp.py
index a47142b47..d52516705 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ntp/ntp.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ntp/ntp.py
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -113,16 +112,16 @@ class NtpFacts(object):
ntp_config = dict()
- if 'network-instance' in ntp_global_config:
+ if 'network-instance' in ntp_global_config and ntp_global_config['network-instance']:
ntp_config['vrf'] = ntp_global_config['network-instance']
if 'enable-ntp-auth' in ntp_global_config:
ntp_config['enable_ntp_auth'] = ntp_global_config['enable-ntp-auth']
- if 'source-interface' in ntp_global_config:
+ if 'source-interface' in ntp_global_config and ntp_global_config['source-interface']:
ntp_config['source_interfaces'] = ntp_global_config['source-interface']
- if 'trusted-key' in ntp_global_config:
+ if 'trusted-key' in ntp_global_config and ntp_global_config['trusted-key']:
ntp_config['trusted_keys'] = ntp_global_config['trusted-key']
servers = []
@@ -134,8 +133,10 @@ class NtpFacts(object):
server['key_id'] = ntp_server['config']['key-id']
server['minpoll'] = ntp_server['config'].get('minpoll', None)
server['maxpoll'] = ntp_server['config'].get('maxpoll', None)
+ server['prefer'] = ntp_server['config'].get('prefer', None)
servers.append(server)
- ntp_config['servers'] = servers
+ if servers:
+ ntp_config['servers'] = servers
keys = []
for ntp_key in ntp_keys:
@@ -148,6 +149,7 @@ class NtpFacts(object):
key['key_type'] = key_type
key['key_value'] = ntp_key['config'].get('key-value', None)
keys.append(key)
- ntp_config['ntp_keys'] = keys
+ if keys:
+ ntp_config['ntp_keys'] = keys
return ntp_config
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/pki/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/pki/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/pki/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/pki/pki.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/pki/pki.py
new file mode 100644
index 000000000..240c50335
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/pki/pki.py
@@ -0,0 +1,144 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell EMC
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic pki fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.pki.pki import (
+ PkiArgs,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config,
+)
+
+pki_path = "data/openconfig-pki:pki/"
+security_profiles_path = "data/openconfig-pki:pki/security-profiles"
+
+
+class PkiFacts(object):
+ """The sonic pki fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = PkiArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for pki
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if connection: # just for linting purposes, remove
+ pass
+ resources = {}
+ if not data:
+ result = self.get_pki()
+ if len(result) > 0 and result[0]:
+ code, resources = result[0]
+
+ objs = {}
+ if (
+ resources.get("openconfig-pki:pki")
+ and resources.get("openconfig-pki:pki").get("security-profiles")
+ and resources.get("openconfig-pki:pki")
+ .get("security-profiles")
+ .get("security-profile")
+ ):
+ sps = (
+ resources.get("openconfig-pki:pki")
+ .get("security-profiles")
+ .get("security-profile")
+ )
+ sps_conf = [r.get("config") for r in sps]
+ rep_conf = []
+ for c in sps_conf:
+ conf = {}
+ for k, v in c.items():
+ conf[k.replace("-", "_")] = v
+ rep_conf.append(conf)
+ objs["security_profiles"] = rep_conf
+ if (
+ resources.get("openconfig-pki:pki")
+ and resources.get("openconfig-pki:pki").get("trust-stores")
+ and resources.get("openconfig-pki:pki")
+ .get("trust-stores")
+ .get("trust-store")
+ ):
+ tsts = (
+ resources.get("openconfig-pki:pki")
+ .get("trust-stores")
+ .get("trust-store")
+ )
+ tsts_conf = [r.get("config") for r in tsts]
+ rep_conf = []
+ for c in tsts_conf:
+ conf = {}
+ for k, v in c.items():
+ conf[k.replace("-", "_")] = v
+ rep_conf.append(conf)
+
+ objs["trust_stores"] = rep_conf
+
+ ansible_facts["ansible_network_resources"].pop("pki", None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(
+ self.argument_spec, {"config": objs}
+ )
+ facts["pki"] = params["config"]
+
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
+
+ def get_pki(self):
+ request = {"path": pki_path, "method": "get"}
+ try:
+ response = edit_config(
+ self._module, to_request(self._module, request)
+ )
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ return response
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+ return utils.remove_empties(config)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/port_breakout/port_breakout.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/port_breakout/port_breakout.py
index 938bd6423..08b143dd5 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/port_breakout/port_breakout.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/port_breakout/port_breakout.py
@@ -11,8 +11,6 @@ based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
-import json
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -29,7 +27,6 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
from ansible.module_utils.connection import ConnectionError
GET = "get"
-POST = "post"
class Port_breakoutFacts(object):
@@ -98,8 +95,8 @@ class Port_breakoutFacts(object):
return conf
def get_all_port_breakout(self):
- """Get all the port_breakout configured in the device"""
- request = [{"path": "operations/sonic-port-breakout:breakout_capabilities", "method": POST}]
+ """Get all the port_breakout configured on the device"""
+ request = [{"path": "data/sonic-port-breakout:sonic-port-breakout/BREAKOUT_CFG/BREAKOUT_CFG_LIST", "method": GET}]
port_breakout_list = []
try:
response = edit_config(self._module, to_request(self._module, request))
@@ -107,12 +104,12 @@ class Port_breakoutFacts(object):
self._module.fail_json(msg=str(exc), code=exc.code)
raw_port_breakout_list = []
- if "sonic-port-breakout:output" in response[0][1]:
- raw_port_breakout_list = response[0][1].get("sonic-port-breakout:output", {}).get('caps', [])
+ if "sonic-port-breakout:BREAKOUT_CFG_LIST" in response[0][1]:
+ raw_port_breakout_list = response[0][1].get("sonic-port-breakout:BREAKOUT_CFG_LIST", [])
for port_breakout in raw_port_breakout_list:
name = port_breakout.get('port', None)
- mode = port_breakout.get('defmode', None)
+ mode = port_breakout.get('brkout_mode', None)
if name and mode:
if '[' in mode:
mode = mode[:mode.index('[')]
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/port_group/port_group.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/port_group/port_group.py
new file mode 100644
index 000000000..c6e4816c4
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/port_group/port_group.py
@@ -0,0 +1,116 @@
+#
+# -*- coding: utf-8 -*-
+# © Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic port group fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.port_group.port_group import Port_groupArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils.connection import ConnectionError
+
+GET = "get"
+
+
+class Port_groupFacts(object):
+ """ The sonic port group fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Port_groupArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for port groups
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ # typically data is populated from the current device configuration
+ # data = connection.get('show running-config | section port-group')
+ # using mock data instead
+ data = self.get_port_groups()
+
+ objs = []
+ for conf in data:
+ if conf:
+ obj = self.render_config(self.generated_spec, conf)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('port_group', None)
+ facts = {}
+ if objs:
+ facts['port_group'] = []
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ if params:
+ facts['port_group'].extend(params['config'])
+ ansible_facts['ansible_network_resources'].update(facts)
+
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ return conf
+
+ def get_port_groups(self):
+ """Get all the port group configurations"""
+
+ pgs_request = [{"path": "data/openconfig-port-group:port-groups/port-group", "method": GET}]
+ try:
+ pgs_response = edit_config(self._module, to_request(self._module, pgs_request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ pgs_config = []
+ if "openconfig-port-group:port-group" in pgs_response[0][1]:
+ pgs_config = pgs_response[0][1].get("openconfig-port-group:port-group", [])
+
+ pgs = []
+ for pg_config in pgs_config:
+ pg = dict()
+ if 'config' in pg_config:
+ pg['id'] = pg_config['id']
+ speed_str = pg_config['config'].get('speed', None)
+ if speed_str:
+ pg['speed'] = speed_str.split(":", 1)[-1]
+ pgs.append(pg)
+
+ return pgs
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/radius_server/radius_server.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/radius_server/radius_server.py
index 72593b225..33ab55a72 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/radius_server/radius_server.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/radius_server/radius_server.py
@@ -11,8 +11,6 @@ based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
-import json
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -103,7 +101,7 @@ class Radius_serverFacts(object):
if 'auth-type' in raw_radius_global_data:
radius_server_data['auth_type'] = raw_radius_global_data['auth-type']
- if 'secret-key' in raw_radius_global_data:
+ if 'secret-key' in raw_radius_global_data and raw_radius_global_data['secret-key']:
radius_server_data['key'] = raw_radius_global_data['secret-key']
if 'timeout' in raw_radius_global_data:
radius_server_data['timeout'] = raw_radius_global_data['timeout']
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/route_maps/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/route_maps/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/route_maps/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/route_maps/route_maps.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/route_maps/route_maps.py
new file mode 100644
index 000000000..05e6d6188
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/route_maps/route_maps.py
@@ -0,0 +1,517 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic route_maps fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import re
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.route_maps.route_maps import Route_mapsArgs
+
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import remove_empties_from_list
+
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic \
+ import to_request, edit_config
+
+
+class Route_mapsFacts(object):
+ """ The sonic route_maps fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Route_mapsArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for route_maps
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ # Fetch data from the current device configuration
+ # (Skip if operating on previously fetched configuration.)
+ data = self.get_all_route_maps()
+
+ # split the unparsed route map configuration list into a list
+ # of parsed route map statement "instances" (dictonary "objects").
+ route_maps = []
+ for route_map_cfg in data:
+ route_map_stmts = self.route_map_cfg_parse(route_map_cfg)
+ if route_map_stmts:
+ route_maps.extend(route_map_stmts)
+
+ ansible_facts['ansible_network_resources'].pop('route_maps', None)
+ facts = {}
+ if route_maps:
+ params = utils.validate_config(self.argument_spec,
+ {'config': route_maps})
+ params_cleaned = {'config': remove_empties_from_list(params['config'])}
+ facts['route_maps'] = params_cleaned['config']
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def get_all_route_maps(self):
+ '''Execute a REST "GET" API to fetch all of the current route map configuration
+ from the target device.'''
+
+ route_map_fetch_spec = \
+ "openconfig-routing-policy:routing-policy/policy-definitions"
+ route_map_resp_key = "openconfig-routing-policy:policy-definitions"
+ route_map_key = "policy-definition"
+ url = "data/%s" % route_map_fetch_spec
+ method = "GET"
+ request = [{"path": url, "method": method}]
+
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc))
+
+ route_maps_unparsed = []
+ resp_route_map_envelope = response[0][1].get(route_map_resp_key, None)
+ if resp_route_map_envelope:
+ route_maps_unparsed = resp_route_map_envelope.get(route_map_key, None)
+ return route_maps_unparsed
+
+ def route_map_cfg_parse(self, unparsed_route_map):
+ '''Parse the raw input configuration JSON representation for the route map specified
+ by the "unparsed_route_map" input parameter. Parse the information to
+ convert it to a dictionary matching the "argspec" for the "route_maps" resource
+ module.'''
+
+ parsed_route_map_stmts = []
+
+ if not unparsed_route_map.get("config"):
+ return parsed_route_map_stmts
+ route_map_name = unparsed_route_map.get('name')
+ if not route_map_name:
+ return parsed_route_map_stmts
+ route_map_statements = unparsed_route_map.get('statements')
+ if not route_map_statements:
+ return parsed_route_map_stmts
+ route_map_stmts_list = route_map_statements.get('statement')
+ if not route_map_stmts_list:
+ return parsed_route_map_stmts
+
+ for route_map_stmt in route_map_stmts_list:
+ parsed_route_map_stmt = {}
+ parsed_seq_num = route_map_stmt.get('name')
+ if not parsed_seq_num:
+ continue
+ parsed_route_map_stmt['map_name'] = route_map_name
+ parsed_route_map_stmt['sequence_num'] = parsed_seq_num
+ self.get_route_map_stmt_set_attr(route_map_stmt, parsed_route_map_stmt)
+ self.get_route_map_stmt_match_attr(route_map_stmt, parsed_route_map_stmt)
+ self.get_route_map_call_attr(route_map_stmt, parsed_route_map_stmt)
+ parsed_route_map_stmts.append(parsed_route_map_stmt)
+
+ return parsed_route_map_stmts
+
+ def get_route_map_stmt_set_attr(self, route_map_stmt, parsed_route_map_stmt):
+ '''Parse the "set" attribute portion of the raw input configuration JSON
+ representation for the route map "statement" specified
+ by the "route_map_stmt," input parameter. Parse the information to
+ convert it to a dictionary matching the "argspec" for the "route_maps" resource
+ module.'''
+
+ stmt_actions = route_map_stmt.get('actions')
+ if not stmt_actions:
+ return
+
+ # Fetch the permit/deny action for the route map statement
+ actions_config = stmt_actions.get('config')
+ if not actions_config:
+ return
+ permit_deny_config = actions_config.get('policy-result')
+ if not permit_deny_config:
+ return
+ if permit_deny_config == "ACCEPT_ROUTE":
+ parsed_route_map_stmt['action'] = "permit"
+ elif permit_deny_config == "REJECT_ROUTE":
+ parsed_route_map_stmt['action'] = "deny"
+ else:
+ return
+
+ # Create a dict object to hold "set" attributes.
+ parsed_route_map_stmt['set'] = {}
+ parsed_route_map_stmt_set = parsed_route_map_stmt['set']
+
+ # Fetch non-required top level set attributes
+ set_metric_action = stmt_actions.get('metric-action')
+ if set_metric_action:
+ set_metric_action_cfg = set_metric_action.get('config')
+ if set_metric_action_cfg:
+ metric_action = set_metric_action_cfg.get('action')
+ if metric_action:
+ parsed_route_map_stmt_set['metric'] = {}
+ if metric_action == 'openconfig-routing-policy:METRIC_SET_VALUE':
+ value = set_metric_action_cfg.get('metric')
+ if value:
+ parsed_route_map_stmt_set['metric']['value'] = value
+ elif metric_action == 'openconfig-routing-policy:METRIC_SET_RTT':
+ parsed_route_map_stmt_set['metric']['rtt_action'] = 'set'
+ elif metric_action == 'openconfig-routing-policy:METRIC_ADD_RTT':
+ parsed_route_map_stmt_set['metric']['rtt_action'] = 'add'
+ elif metric_action == 'openconfig-routing-policy:METRIC_SUBTRACT_RTT':
+ parsed_route_map_stmt_set['metric']['rtt_action'] = 'subtract'
+
+ # Possible anomalous state due to partial deletion of metric config via REST
+ if parsed_route_map_stmt_set['metric'] == {}:
+ parsed_route_map_stmt_set.pop('metric')
+
+ # Fetch BGP policy action attributes
+ set_bgp_policy = stmt_actions.get('openconfig-bgp-policy:bgp-actions')
+ if set_bgp_policy:
+ self.get_route_map_set_bgp_policy_attr(set_bgp_policy, parsed_route_map_stmt_set)
+
+ def get_route_map_set_bgp_policy_attr(self, set_bgp_policy, parsed_route_map_stmt_set):
+ '''Parse the BGP policy "set" attribute portion of the raw input
+ configuration JSON representation within the route map "statement"
+ that is currently being parsed. The configuration section to be parsed
+ is specified by the "set_bgp_policy" input parameter. Parse the
+ information to convert it to a dictionary matching the "argspec" for
+ the "route_maps" resource module.'''
+
+ # Fetch as_path_prepend config
+ set_as_path_top = set_bgp_policy.get('set-as-path-prepend')
+ if set_as_path_top and set_as_path_top.get('config'):
+ as_path_prepend = \
+ set_as_path_top['config'].get(
+ 'openconfig-routing-policy-ext:asn-list')
+ if as_path_prepend:
+ parsed_route_map_stmt_set['as_path_prepend'] = \
+ as_path_prepend
+
+ # Fetch community list "delete" config
+ set_comm_list_delete_top = set_bgp_policy.get('set-community-delete')
+ if set_comm_list_delete_top:
+ set_comm_list_delete_config = set_comm_list_delete_top.get('config')
+ if set_comm_list_delete_config:
+ comm_list_delete = \
+ set_comm_list_delete_config.get('community-set-delete')
+ if comm_list_delete:
+ parsed_route_map_stmt_set['comm_list_delete'] = \
+ comm_list_delete
+
+ # Fetch community attributes.
+ self.get_rmap_set_community(set_bgp_policy, parsed_route_map_stmt_set)
+
+ # Fetch extended community attributes.
+ self.get_rmap_set_extcommunity(set_bgp_policy, parsed_route_map_stmt_set)
+
+ # Fetch other BGP policy "set" attributes
+ set_bgp_policy_cfg = set_bgp_policy.get('config')
+ if set_bgp_policy_cfg:
+ ip_next_hop = set_bgp_policy_cfg.get('set-next-hop')
+ if ip_next_hop:
+ parsed_route_map_stmt_set['ip_next_hop'] = ip_next_hop
+
+ ipv6_next_hop_global_addr = set_bgp_policy_cfg.get('set-ipv6-next-hop-global')
+ ipv6_prefer_global = set_bgp_policy_cfg.get('set-ipv6-next-hop-prefer-global')
+ if ipv6_next_hop_global_addr or (ipv6_prefer_global is not None):
+ parsed_route_map_stmt_set['ipv6_next_hop'] = {}
+ set_ipv6_nexthop = parsed_route_map_stmt_set['ipv6_next_hop']
+ if ipv6_next_hop_global_addr:
+ set_ipv6_nexthop['global_addr'] = ipv6_next_hop_global_addr
+ if ipv6_prefer_global is not None:
+ set_ipv6_nexthop['prefer_global'] = ipv6_prefer_global
+
+ local_preference = set_bgp_policy_cfg.get('set-local-pref')
+ if local_preference:
+ parsed_route_map_stmt_set['local_preference'] = local_preference
+
+ set_origin = set_bgp_policy_cfg.get('set-route-origin')
+ if set_origin:
+ if set_origin == 'EGP':
+ parsed_route_map_stmt_set['origin'] = 'egp'
+ elif set_origin == 'IGP':
+ parsed_route_map_stmt_set['origin'] = 'igp'
+ elif set_origin == 'INCOMPLETE':
+ parsed_route_map_stmt_set['origin'] = 'incomplete'
+
+ weight = set_bgp_policy_cfg.get('set-weight')
+ if weight:
+ parsed_route_map_stmt_set['weight'] = weight
+
+ @staticmethod
+ def get_rmap_set_community(set_bgp_policy, parsed_route_map_stmt_set):
+ '''Parse the "community" sub-section of the BGP policy "set" attribute
+ portion of the raw input configuration JSON representation.
+ The BGP policy "set" configuration section to be parsed is specified
+ by the "set_bgp_policy" input parameter. Parse the information
+ to convert it to a dictionary matching the "argspec" for the "route_maps"
+ resource module.'''
+
+ set_community_top = set_bgp_policy.get('set-community')
+ if (set_community_top and set_community_top.get('inline') and
+ set_community_top['inline'].get('config') and
+ set_community_top['inline']['config'].get('communities')):
+
+ set_community_config_list = \
+ set_community_top['inline']['config']['communities']
+ parsed_route_map_stmt_set['community'] = {}
+ parsed_rmap_stmt_set_comm = parsed_route_map_stmt_set['community']
+ for set_community_config_item in set_community_config_list:
+ if (set_community_config_item.split(':')[0] in
+ ('openconfig-bgp-types', 'openconfig-routing-policy-ext')):
+ set_community_attr = set_community_config_item.split(':')[1]
+ if not parsed_rmap_stmt_set_comm.get('community_attributes'):
+ parsed_rmap_stmt_set_comm['community_attributes'] = []
+ parsed_comm_attr_list = \
+ parsed_rmap_stmt_set_comm['community_attributes']
+ comm_attr_rest_to_argspec = {
+ 'NO_EXPORT_SUBCONFED': 'local_as',
+ 'NO_ADVERTISE': 'no_advertise',
+ 'NO_EXPORT': 'no_export',
+ 'NOPEER': 'no_peer',
+ 'NONE': 'none',
+ 'ADDITIVE': 'additive'
+ }
+ if set_community_attr in comm_attr_rest_to_argspec:
+ parsed_comm_attr_list.append(
+ comm_attr_rest_to_argspec[set_community_attr])
+ else:
+ if not parsed_rmap_stmt_set_comm.get('community_number'):
+ parsed_rmap_stmt_set_comm['community_number'] = []
+ parsed_comm_num_list = \
+ parsed_rmap_stmt_set_comm['community_number']
+ set_community_num_val_match = \
+ re.match(r'\d+:\d+$', set_community_config_item)
+ if set_community_num_val_match:
+ parsed_comm_num_list.append(set_community_config_item)
+
+ @staticmethod
+ def get_rmap_set_extcommunity(set_bgp_policy, parsed_route_map_stmt_set):
+ '''Parse the "extcommunity" sub-section of the BGP policy "set"
+ attribute portion of the raw input configuration JSON representation.
+ The BGP policy "set" configuration section to be parsed is specified
+ by the "set_bgp_policy" input parameter. Parse the information
+ to convert it to a dictionary matching the "argspec" for the "route_maps"
+ resource module.'''
+ set_extcommunity_top = set_bgp_policy.get('set-ext-community')
+ if (set_extcommunity_top and set_extcommunity_top.get('inline') and
+ set_extcommunity_top['inline'].get('config') and
+ set_extcommunity_top['inline']['config'].get('communities')):
+ set_extcommunity_config_list = \
+ set_extcommunity_top['inline']['config']['communities']
+ if set_extcommunity_config_list:
+ parsed_route_map_stmt_set['extcommunity'] = {}
+ parsed_rmap_stmt_set_extcomm = parsed_route_map_stmt_set['extcommunity']
+ for set_extcommunity_config_item in set_extcommunity_config_list:
+ if 'route-target:' in set_extcommunity_config_item:
+ rt_val = set_extcommunity_config_item.replace('route-target:', '')
+ if parsed_rmap_stmt_set_extcomm.get('rt'):
+ parsed_rmap_stmt_set_extcomm['rt'].append(rt_val)
+ else:
+ parsed_rmap_stmt_set_extcomm['rt'] = [rt_val]
+ elif 'route-origin:' in set_extcommunity_config_item:
+ soo_val = set_extcommunity_config_item.replace('route-origin:', '')
+ if parsed_rmap_stmt_set_extcomm.get('soo'):
+ parsed_rmap_stmt_set_extcomm['soo'].append(soo_val)
+ else:
+ parsed_rmap_stmt_set_extcomm['soo'] = [soo_val]
+
+ @staticmethod
+ def get_route_map_call_attr(route_map_stmt, parsed_route_map_stmt):
+ '''Parse the "call" attribute portion of the raw input configuration JSON
+ representation for the route map "statement" specified
+ by the "route_map_stmt," input parameter. Parse the information to
+ convert it to a dictionary matching the "argspec" for the "route_maps" resource
+ module.'''
+
+ stmt_conditions = route_map_stmt.get('conditions')
+ if not stmt_conditions:
+ return
+
+ # Fetch the "call" policy configuration for the route map statement
+ conditions_config = stmt_conditions.get('config')
+ if not conditions_config:
+ return
+ call_str = conditions_config.get('call-policy')
+ if not call_str:
+ return
+ parsed_route_map_stmt['call'] = call_str
+
+ def get_route_map_stmt_match_attr(self, route_map_stmt, parsed_route_map_stmt):
+ '''Parse the "match" attributes in the raw input configuration JSON
+ representation for the route map "statement" specified
+ by the "route_map_stmt," input parameter. Parse the information to
+ convert it to a dictionary matching the "argspec" for the "route_maps" resource
+ module.'''
+
+ # Create a dict object to hold "match" attributes.
+ parsed_route_map_stmt['match'] = {}
+ parsed_rmap_match = parsed_route_map_stmt['match']
+
+ stmt_conditions = route_map_stmt.get('conditions')
+ if not stmt_conditions:
+ return
+
+ # Fetch match as-path configuration
+ if (stmt_conditions.get('match-as-path-set') and
+ stmt_conditions['match-as-path-set'].get('config')):
+ as_path = \
+ stmt_conditions['match-as-path-set']['config'].get('as-path-set')
+ if as_path:
+ parsed_rmap_match['as_path'] = as_path
+
+ # Fetch BGP policy match attributes.
+ rmap_bgp_policy_match = stmt_conditions.get('openconfig-bgp-policy:bgp-conditions')
+ if rmap_bgp_policy_match:
+ self.get_rmap_match_bgp_policy_attr(rmap_bgp_policy_match, parsed_rmap_match)
+
+ # Fetch other match attributes
+ if (stmt_conditions.get('match-interface') and
+ stmt_conditions['match-interface'].get('config')):
+ match_interface = stmt_conditions['match-interface']['config'].get('interface')
+ if match_interface:
+ parsed_rmap_match['interface'] = match_interface
+
+ if (stmt_conditions.get('match-prefix-set') and
+ stmt_conditions['match-prefix-set']['config']):
+ match_prefix_set = \
+ stmt_conditions['match-prefix-set']['config']
+ if match_prefix_set and match_prefix_set.get('prefix-set'):
+ if not parsed_rmap_match.get('ip'):
+ parsed_rmap_match['ip'] = {}
+ parsed_rmap_match['ip']['address'] = \
+ match_prefix_set['prefix-set']
+ if (match_prefix_set and
+ match_prefix_set.get('openconfig-routing-policy-ext:ipv6-prefix-set')):
+ parsed_rmap_match['ipv6'] = {}
+ parsed_rmap_match['ipv6']['address'] = \
+ match_prefix_set['openconfig-routing-policy-ext:ipv6-prefix-set']
+
+ if (stmt_conditions.get('match-neighbor-set') and
+ stmt_conditions['match-neighbor-set'].get('config') and
+ stmt_conditions['match-neighbor-set']['config'].get(
+ 'openconfig-routing-policy-ext:address')):
+ parsed_rmap_match_peer = stmt_conditions[
+ 'match-neighbor-set']['config']['openconfig-routing-policy-ext:address'][0]
+ parsed_rmap_match['peer'] = {}
+ if ':' in parsed_rmap_match_peer:
+ parsed_rmap_match['peer']['ipv6'] = parsed_rmap_match_peer
+ elif '.' in parsed_rmap_match_peer:
+ parsed_rmap_match['peer']['ip'] = parsed_rmap_match_peer
+ else:
+ parsed_rmap_match['peer']['interface'] = parsed_rmap_match_peer
+
+ if (stmt_conditions.get('config') and
+ stmt_conditions['config'].get('install-protocol-eq')):
+ parsed_rmap_match_source_protocol = \
+ stmt_conditions['config']['install-protocol-eq']
+ if parsed_rmap_match_source_protocol == "openconfig-policy-types:BGP":
+ parsed_rmap_match['source_protocol'] = "bgp"
+ elif parsed_rmap_match_source_protocol == "openconfig-policy-types:OSPF":
+ parsed_rmap_match['source_protocol'] = "ospf"
+ elif parsed_rmap_match_source_protocol == "openconfig-policy-types:STATIC":
+ parsed_rmap_match['source_protocol'] = "static"
+ elif parsed_rmap_match_source_protocol == \
+ "openconfig-policy-types:DIRECTLY_CONNECTED":
+ parsed_rmap_match['source_protocol'] = "connected"
+
+ if stmt_conditions.get(
+ 'openconfig-routing-policy-ext:match-src-network-instance'):
+ match_src_vrf = \
+ stmt_conditions[
+ 'openconfig-routing-policy-ext:match-src-network-instance'].get('config')
+ if match_src_vrf and match_src_vrf.get('name'):
+ parsed_rmap_match['source_vrf'] = match_src_vrf['name']
+
+ if (stmt_conditions.get('match-tag-set') and
+ stmt_conditions['match-tag-set'].get('config')):
+ match_tag = \
+ stmt_conditions['match-tag-set']['config'].get(
+ 'openconfig-routing-policy-ext:tag-value')
+ if match_tag:
+ parsed_rmap_match['tag'] = match_tag[0]
+
+ @staticmethod
+ def get_rmap_match_bgp_policy_attr(rmap_bgp_policy_match, parsed_rmap_match):
+ '''Parse the BGP policy "match" attribute portion of the raw input
+ configuration JSON representation within the route map "statement"
+ that is currently being parsed. The configuration section to be parsed
+ is specified by the "rmap_bgp_match_cfg" input parameter. Parse the
+ information to convert it to a dictionary matching the "argspec" for
+ the "route_maps" resource module.'''
+
+ if (rmap_bgp_policy_match.get('match-as-path-set') and
+ rmap_bgp_policy_match['match-as-path-set'].get('config')):
+ as_path = rmap_bgp_policy_match['match-as-path-set']['config'].get('as-path-set')
+ if as_path:
+ parsed_rmap_match['as_path'] = as_path
+
+ # Fetch BGP policy match "config" attributes
+ rmap_bgp_match_cfg = rmap_bgp_policy_match.get('config')
+ if rmap_bgp_match_cfg:
+ match_metric = rmap_bgp_match_cfg.get('med-eq')
+ if match_metric:
+ parsed_rmap_match['metric'] = match_metric
+
+ match_origin = rmap_bgp_match_cfg.get('origin-eq')
+ if match_origin:
+ if match_origin == 'IGP':
+ parsed_rmap_match['origin'] = 'igp'
+ elif match_origin == 'EGP':
+ parsed_rmap_match['origin'] = 'egp'
+ elif match_origin == 'INCOMPLETE':
+ parsed_rmap_match['origin'] = 'incomplete'
+
+ if rmap_bgp_match_cfg.get('local-pref-eq'):
+ parsed_rmap_match['local_preference'] = rmap_bgp_match_cfg['local-pref-eq']
+
+ if rmap_bgp_match_cfg.get('community-set'):
+ parsed_rmap_match['community'] = rmap_bgp_match_cfg['community-set']
+
+ if rmap_bgp_match_cfg.get('ext-community-set'):
+ parsed_rmap_match['ext_comm'] = rmap_bgp_match_cfg['ext-community-set']
+
+ if rmap_bgp_match_cfg.get('openconfig-bgp-policy-ext:next-hop-set'):
+ parsed_rmap_match['ip'] = {}
+ parsed_rmap_match['ip']['next_hop'] = \
+ rmap_bgp_match_cfg['openconfig-bgp-policy-ext:next-hop-set']
+
+ # Fetch BGP policy match "evpn" attributes
+ if rmap_bgp_policy_match.get('openconfig-bgp-policy-ext:match-evpn-set'):
+ bgp_policy_match_evpn_cfg = \
+ rmap_bgp_policy_match['openconfig-bgp-policy-ext:match-evpn-set'].get('config')
+ if bgp_policy_match_evpn_cfg:
+ parsed_rmap_match['evpn'] = {}
+ if bgp_policy_match_evpn_cfg.get('vni-number'):
+ parsed_rmap_match['evpn']['vni'] = \
+ bgp_policy_match_evpn_cfg.get('vni-number')
+ if bgp_policy_match_evpn_cfg.get('default-type5-route'):
+ parsed_rmap_match['evpn']['default_route'] = True
+ evpn_route_type = bgp_policy_match_evpn_cfg.get('route-type')
+ if evpn_route_type:
+ if evpn_route_type == "openconfig-bgp-policy-ext:MACIP":
+ parsed_rmap_match['evpn']['route_type'] = "macip"
+ elif evpn_route_type == "openconfig-bgp-policy-ext:MULTICAST":
+ parsed_rmap_match['evpn']['route_type'] = "multicast"
+ elif evpn_route_type == "openconfig-bgp-policy-ext:PREFIX":
+ parsed_rmap_match['evpn']['route_type'] = "prefix"
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/static_routes/static_routes.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/static_routes/static_routes.py
index f83566440..e0d404be7 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/static_routes/static_routes.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/static_routes/static_routes.py
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -139,7 +138,7 @@ class Static_routesFacts(object):
blackhole = config.get('blackhole', None)
track = config.get('track', None)
tag = config.get('tag', None)
- if blackhole:
+ if blackhole is not None:
index_dict['blackhole'] = blackhole
if interface:
index_dict['interface'] = interface
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/stp/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/stp/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/stp/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/stp/stp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/stp/stp.py
new file mode 100644
index 000000000..da779c502
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/stp/stp.py
@@ -0,0 +1,364 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic stp fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ remove_empties
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.stp.stp import StpArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+
+
+stp_map = {
+ 'openconfig-spanning-tree-types:EDGE_ENABLE': True,
+ 'openconfig-spanning-tree-types:EDGE_DISABLE': False,
+ 'openconfig-spanning-tree-types:MSTP': 'mst',
+ 'openconfig-spanning-tree-ext:PVST': 'pvst',
+ 'openconfig-spanning-tree-types:RAPID_PVST': 'rapid_pvst',
+ 'P2P': 'point-to-point',
+ 'SHARED': 'shared',
+ 'LOOP': 'loop',
+ 'ROOT': 'root',
+ 'NONE': 'none'
+}
+
+
+class StpFacts(object):
+ """ The sonic stp fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = StpArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for stp
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+
+ if not data:
+ stp_cfg = self.get_stp_config(self._module)
+ data = self.update_stp(stp_cfg)
+ objs = self.render_config(self.generated_spec, data)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': remove_empties(objs)})
+ facts['stp'] = params['config']
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ return conf
+
+ def update_stp(self, data):
+ config_dict = {}
+ if data:
+ config_dict['global'] = self.update_global(data)
+ config_dict['interfaces'] = self.update_interfaces(data)
+ config_dict['mstp'] = self.update_mstp(data)
+ config_dict['pvst'] = self.update_pvst(data)
+ config_dict['rapid_pvst'] = self.update_rapid_pvst(data)
+
+ return config_dict
+
+ def update_global(self, data):
+ global_dict = {}
+ stp_global = data.get('global', None)
+
+ if stp_global:
+ config = stp_global.get('config', None)
+ if config:
+ enabled_protocol = config.get('enabled-protocol', None)
+ loop_guard = config.get('loop-guard', None)
+ bpdu_filter = config.get('bpdu-filter', None)
+ disabled_vlans = config.get('openconfig-spanning-tree-ext:disabled-vlans', None)
+ root_guard_timeout = config.get('openconfig-spanning-tree-ext:rootguard-timeout', None)
+ portfast = config.get('openconfig-spanning-tree-ext:portfast', None)
+ hello_time = config.get('openconfig-spanning-tree-ext:hello-time', None)
+ max_age = config.get('openconfig-spanning-tree-ext:max-age', None)
+ fwd_delay = config.get('openconfig-spanning-tree-ext:forwarding-delay', None)
+ bridge_priority = config.get('openconfig-spanning-tree-ext:bridge-priority', None)
+
+ if enabled_protocol:
+ global_dict['enabled_protocol'] = stp_map[enabled_protocol[0]]
+ if loop_guard is not None:
+ global_dict['loop_guard'] = loop_guard
+ if bpdu_filter is not None:
+ global_dict['bpdu_filter'] = bpdu_filter
+ if disabled_vlans:
+ global_dict['disabled_vlans'] = self.convert_vlans_list(disabled_vlans)
+ if root_guard_timeout:
+ global_dict['root_guard_timeout'] = root_guard_timeout
+ if portfast is not None:
+ global_dict['portfast'] = portfast
+ if hello_time:
+ global_dict['hello_time'] = hello_time
+ if max_age:
+ global_dict['max_age'] = max_age
+ if fwd_delay:
+ global_dict['fwd_delay'] = fwd_delay
+ if bridge_priority:
+ global_dict['bridge_priority'] = bridge_priority
+
+ return global_dict
+
+ def update_interfaces(self, data):
+ interfaces_list = []
+ interfaces = data.get('interfaces', None)
+
+ if interfaces:
+ intf_list = interfaces.get('interface', None)
+ if intf_list:
+ for intf in intf_list:
+ intf_dict = {}
+ config = intf.get('config', None)
+ intf_name = config.get('name', None)
+ edge_port = config.get('edge-port', None)
+ link_type = config.get('link-type', None)
+ guard = config.get('guard', None)
+ bpdu_guard = config.get('bpdu-guard', None)
+ bpdu_filter = config.get('bpdu-filter', None)
+ portfast = config.get('openconfig-spanning-tree-ext:portfast', None)
+ uplink_fast = config.get('openconfig-spanning-tree-ext:uplink-fast', None)
+ shutdown = config.get('openconfig-spanning-tree-ext:bpdu-guard-port-shutdown', None)
+ cost = config.get('openconfig-spanning-tree-ext:cost', None)
+ port_priority = config.get('openconfig-spanning-tree-ext:port-priority', None)
+ stp_enable = config.get('openconfig-spanning-tree-ext:spanning-tree-enable', None)
+
+ if intf_name:
+ intf_dict['intf_name'] = intf_name
+ if edge_port is not None:
+ intf_dict['edge_port'] = stp_map[edge_port]
+ if link_type:
+ intf_dict['link_type'] = stp_map[link_type]
+ if guard:
+ intf_dict['guard'] = stp_map[guard]
+ if bpdu_guard is not None:
+ intf_dict['bpdu_guard'] = bpdu_guard
+ if bpdu_filter is not None:
+ intf_dict['bpdu_filter'] = bpdu_filter
+ if portfast is not None:
+ intf_dict['portfast'] = portfast
+ if uplink_fast is not None:
+ intf_dict['uplink_fast'] = uplink_fast
+ if shutdown is not None:
+ intf_dict['shutdown'] = shutdown
+ if cost:
+ intf_dict['cost'] = cost
+ if port_priority:
+ intf_dict['port_priority'] = port_priority
+ if stp_enable is not None:
+ intf_dict['stp_enable'] = stp_enable
+ if intf_dict:
+ interfaces_list.append(intf_dict)
+
+ return interfaces_list
+
+ def update_mstp(self, data):
+ mstp_dict = {}
+ mstp = data.get('mstp', None)
+
+ if mstp:
+ config = mstp.get('config', None)
+ mst_instances = mstp.get('mst-instances', None)
+ interfaces = mstp.get('interfaces', None)
+ if config:
+ mst_name = config.get('name', None)
+ revision = config.get('revision', None)
+ max_hop = config.get('max-hop', None)
+ hello_time = config.get('hello-time', None)
+ max_age = config.get('max-age', None)
+ fwd_delay = config.get('forwarding-delay', None)
+
+ if mst_name:
+ mstp_dict['mst_name'] = mst_name
+ if revision:
+ mstp_dict['revision'] = revision
+ if max_hop:
+ mstp_dict['max_hop'] = max_hop
+ if hello_time:
+ mstp_dict['hello_time'] = hello_time
+ if max_age:
+ mstp_dict['max_age'] = max_age
+ if fwd_delay:
+ mstp_dict['fwd_delay'] = fwd_delay
+
+ if mst_instances:
+ mst_instance = mst_instances.get('mst-instance', None)
+ if mst_instance:
+ mst_instances_list = []
+ for inst in mst_instance:
+ inst_dict = {}
+ mst_id = inst.get('mst-id', None)
+ config = inst.get('config', None)
+ interfaces = inst.get('interfaces', None)
+ if mst_id:
+ inst_dict['mst_id'] = mst_id
+ if interfaces:
+ intf_list = self.get_interfaces_list(interfaces)
+ if intf_list:
+ inst_dict['interfaces'] = intf_list
+ if config:
+ vlans = config.get('vlan', None)
+ bridge_priority = config.get('bridge-priority', None)
+ if vlans:
+ inst_dict['vlans'] = self.convert_vlans_list(vlans)
+ if bridge_priority:
+ inst_dict['bridge_priority'] = bridge_priority
+ if inst_dict:
+ mst_instances_list.append(inst_dict)
+ if mst_instances_list:
+ mstp_dict['mst_instances'] = mst_instances_list
+
+ return mstp_dict
+
+ def update_pvst(self, data):
+ pvst_list = []
+ pvst = data.get('openconfig-spanning-tree-ext:pvst', None)
+
+ if pvst:
+ vlans = pvst.get('vlans', None)
+ if vlans:
+ vlans_list = self.get_vlans_list(vlans)
+ if vlans_list:
+ pvst_list = vlans_list
+
+ return pvst_list
+
+ def update_rapid_pvst(self, data):
+ rapid_pvst_list = []
+ rapid_pvst = data.get('rapid-pvst', None)
+
+ if rapid_pvst:
+ vlans = rapid_pvst.get('vlan', None)
+ if vlans:
+ vlans_list = self.get_vlans_list(vlans)
+ if vlans_list:
+ rapid_pvst_list = vlans_list
+
+ return rapid_pvst_list
+
+ def get_stp_config(self, module):
+ stp_cfg = None
+ get_stp_path = '/data/openconfig-spanning-tree:stp'
+ request = {'path': get_stp_path, 'method': 'get'}
+
+ try:
+ response = edit_config(module, to_request(module, request))
+ stp_cfg = response[0][1].get('openconfig-spanning-tree:stp', None)
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+
+ return stp_cfg
+
+ def get_interfaces_list(self, data):
+ intf_list = []
+ interface_list = data.get('interface', None)
+
+ if interface_list:
+ for intf in interface_list:
+ intf_dict = {}
+ config = intf.get('config', None)
+ if config:
+ intf_name = config.get('name', None)
+ cost = config.get('cost', None)
+ port_priority = config.get('port-priority', None)
+
+ if intf_name:
+ intf_dict['intf_name'] = intf_name
+ if cost:
+ intf_dict['cost'] = cost
+ if port_priority:
+ intf_dict['port_priority'] = port_priority
+ if intf_dict:
+ intf_list.append(intf_dict)
+
+ return intf_list
+
+ def get_vlans_list(self, data):
+ vlan_list = []
+
+ for vlan in data:
+ vlan_dict = {}
+ vlan_id = vlan.get('vlan-id')
+ config = vlan.get('config', None)
+ interfaces = vlan.get('interfaces', None)
+
+ if vlan_id:
+ vlan_dict['vlan_id'] = vlan_id
+ if interfaces:
+ intf_list = self.get_interfaces_list(interfaces)
+ if intf_list:
+ vlan_dict['interfaces'] = intf_list
+ if config:
+ hello_time = config.get('hello-time', None)
+ max_age = config.get('max-age', None)
+ fwd_delay = config.get('forwarding-delay', None)
+ bridge_priority = config.get('bridge-priority', None)
+
+ if hello_time:
+ vlan_dict['hello_time'] = hello_time
+ if max_age:
+ vlan_dict['max_age'] = max_age
+ if fwd_delay:
+ vlan_dict['fwd_delay'] = fwd_delay
+ if bridge_priority:
+ vlan_dict['bridge_priority'] = bridge_priority
+ if vlan_dict:
+ vlan_list.append(vlan_dict)
+
+ return vlan_list
+
+ def convert_vlans_list(self, vlans):
+ converted_vlans = []
+
+ for vlan in vlans:
+ if isinstance(vlan, int):
+ converted_vlans.append(str(vlan))
+
+ else:
+ converted_vlans.append(vlan.replace('..', '-'))
+
+ return converted_vlans
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/system/system.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/system/system.py
index 1d7a82d83..65c4491d3 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/system/system.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/system/system.py
@@ -11,7 +11,6 @@ based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/tacacs_server/tacacs_server.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/tacacs_server/tacacs_server.py
index a1e79910f..b752b7a83 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/tacacs_server/tacacs_server.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/tacacs_server/tacacs_server.py
@@ -11,8 +11,6 @@ based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
-import json
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/users/users.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/users/users.py
index 038e97f83..59f08e63e 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/users/users.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/users/users.py
@@ -11,7 +11,6 @@ based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -22,6 +21,9 @@ from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.s
to_request,
edit_config
)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ remove_empties_from_list
+)
from ansible.module_utils.connection import ConnectionError
GET = "get"
@@ -74,8 +76,9 @@ class UsersFacts(object):
if objs:
facts['users'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
+
if params:
- facts['users'].extend(params['config'])
+ facts['users'].extend(remove_empties_from_list(params['config']))
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
@@ -94,7 +97,7 @@ class UsersFacts(object):
def get_all_users(self):
"""Get all the users configured in the device"""
- request = [{"path": "data/sonic-system-aaa:sonic-system-aaa/USER", "method": GET}]
+ request = [{"path": "data/openconfig-system:system/aaa/authentication/users", "method": GET}]
users = []
try:
response = edit_config(self._module, to_request(self._module, request))
@@ -102,21 +105,16 @@ class UsersFacts(object):
self._module.fail_json(msg=str(exc), code=exc.code)
raw_users = []
- if "sonic-system-aaa:USER" in response[0][1]:
- raw_users = response[0][1].get("sonic-system-aaa:USER", {}).get('USER_LIST', [])
+ if "openconfig-system:users" in response[0][1]:
+ raw_users = response[0][1].get("openconfig-system:users", {}).get('user', [])
for raw_user in raw_users:
name = raw_user.get('username', None)
- role = raw_user.get('role', [])
- if role and len(role) > 0:
- role = role[0]
- password = raw_user.get('password', None)
+ role = raw_user.get('config', {}).get('role', None)
user = {}
if name and role:
user['name'] = name
user['role'] = role
- if password:
- user['password'] = password
if user:
users.append(user)
return users
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlan_mapping/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlan_mapping/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlan_mapping/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlan_mapping/vlan_mapping.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlan_mapping/vlan_mapping.py
new file mode 100644
index 000000000..ac53415c7
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlan_mapping/vlan_mapping.py
@@ -0,0 +1,225 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic vlan_mapping fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.vlan_mapping.vlan_mapping import Vlan_mappingArgs
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils.connection import ConnectionError
+
+
+class Vlan_mappingFacts(object):
+ """ The sonic vlan_mapping fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Vlan_mappingArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for vlan_mapping
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if connection: # just for linting purposes, remove
+ pass
+
+ all_vlan_mapping_configs = {}
+ if not data:
+ vlan_mapping_configs = self.get_vlan_mappings()
+ for interface, vlan_config in vlan_mapping_configs.items():
+ vlan_mapping_configs_dict = {}
+ vlan_mapping_configs_dict['name'] = interface
+ vlan_mapping_configs_dict['mapping'] = vlan_config
+ all_vlan_mapping_configs[interface] = vlan_mapping_configs_dict
+
+ objs = []
+ for vlan_mapping_config in all_vlan_mapping_configs.items():
+ obj = self.render_config(self.generated_spec, vlan_mapping_config)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('vlan_mapping', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['vlan_mapping'] = params['config']
+
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+ config['name'] = conf[1]['name']
+ config['mapping'] = conf[1]['mapping']
+
+ return utils.remove_empties(config)
+
+ def get_vlan_mappings(self):
+ """Get all vlan mappings on device"""
+ interfaces = self.get_ports() + self.get_portchannels()
+
+ vlan_mapping_configs = {}
+ for interface in interfaces:
+ response = self.get_port_mappings(interface)
+ if "openconfig-interfaces-ext:mapped-vlans" in response:
+ vlan_list = response["openconfig-interfaces-ext:mapped-vlans"].get("mapped-vlan", {})
+ for vlan_mapping in vlan_list:
+ vlan_mapping_dict = {}
+ vlan_mapping_dict["vlan_ids"] = []
+
+ tmp_dot1q_tunnel = (vlan_mapping
+ .get("egress-mapping", {})
+ .get("config", {})
+ .get("vlan-stack-action", "SWAP"))
+ if tmp_dot1q_tunnel == "SWAP":
+ vlan_mapping_dict["dot1q_tunnel"] = False
+ vlan_mapping_dict["inner_vlan"] = (vlan_mapping
+ .get("match", {})
+ .get("double-tagged", {})
+ .get("config", {})
+ .get("inner-vlan-id", None))
+ if vlan_mapping_dict["inner_vlan"]:
+ (vlan_mapping_dict["vlan_ids"]
+ .append(vlan_mapping.get("match", {})
+ .get("double-tagged", {})
+ .get("config", {})
+ .get("outer-vlan-id", None)))
+ else:
+ (vlan_mapping_dict["vlan_ids"]
+ .append(vlan_mapping.get("match", {})
+ .get("single-tagged", {})
+ .get("config", {})
+ .get("vlan-ids", None)))
+ if vlan_mapping_dict["vlan_ids"]:
+ vlan_mapping_dict["vlan_ids"][0] = vlan_mapping_dict["vlan_ids"][0][0]
+ else:
+ vlan_mapping_dict["dot1q_tunnel"] = True
+ tmp_vlan_ids = (vlan_mapping
+ .get("match", {})
+ .get("single-tagged", {})
+ .get("config", {})
+ .get("vlan-ids", None))
+ if tmp_vlan_ids:
+ vlan_mapping_dict["vlan_ids"].extend(tmp_vlan_ids[0].replace('..', '-').split(','))
+
+ vlan_mapping_dict["service_vlan"] = vlan_mapping.get("vlan-id", None)
+ vlan_mapping_dict["priority"] = (vlan_mapping
+ .get("egress-mapping", {})
+ .get("config", {})
+ .get("mapped-vlan-priority", None))
+
+ if interface["ifname"] in vlan_mapping_configs:
+ vlan_mapping_configs[interface["ifname"]].append(vlan_mapping_dict)
+ else:
+ vlan_mapping_configs[interface["ifname"]] = []
+ vlan_mapping_configs[interface["ifname"]].append(vlan_mapping_dict)
+
+ return vlan_mapping_configs
+
+ def get_port_mappings(self, interface):
+ """Get a ports vlan mappings from device"""
+ ifname = interface["ifname"]
+ if '/' in ifname:
+ ifname = ifname.replace('/', '%2F')
+
+ port_mappings = "data/openconfig-interfaces:interfaces/interface=%s/openconfig-interfaces-ext:mapped-vlans" % ifname
+ method = "GET"
+ request = [{"path": port_mappings, "method": method}]
+
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ return response[0][1]
+
+ def get_ports(self):
+ """Get all port names on device"""
+ all_ports_path = "data/sonic-port:sonic-port/PORT_TABLE"
+ method = "GET"
+ request = [{"path": all_ports_path, "method": method}]
+
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ response = response[0][1]
+
+ port_list = []
+
+ if "sonic-port:PORT_TABLE" in response:
+ component = response["sonic-port:PORT_TABLE"]
+ if "PORT_TABLE_LIST" in component:
+ for port in component["PORT_TABLE_LIST"]:
+ if "Eth" in port["ifname"]:
+ port_list.append({"ifname": port["ifname"]})
+
+ return port_list
+
+ def get_portchannels(self):
+ """Get all portchannel names on device"""
+ all_portchannels_path = "data/sonic-portchannel:sonic-portchannel"
+ method = "GET"
+ request = [{"path": all_portchannels_path, "method": method}]
+
+ try:
+ response = edit_config(self._module, to_request(self._module, request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ response = response[0][1]
+
+ portchannel_list = []
+
+ if "sonic-portchannel:sonic-portchannel" in response:
+ component = response["sonic-portchannel:sonic-portchannel"]
+ if "PORTCHANNEL" in component:
+ component = component["PORTCHANNEL"]
+ if "PORTCHANNEL_LIST" in component:
+ component = component["PORTCHANNEL_LIST"]
+ for portchannel in component:
+ portchannel_list.append({"ifname": portchannel["name"]})
+
+ return portchannel_list
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlans/vlans.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlans/vlans.py
index 7c4af2ea8..3df200048 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlans/vlans.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlans/vlans.py
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vrfs/vrfs.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vrfs/vrfs.py
index 797612bc4..375c453d5 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vrfs/vrfs.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vrfs/vrfs.py
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vxlans/vxlans.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vxlans/vxlans.py
index 51aec6561..e521313b8 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vxlans/vxlans.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vxlans/vxlans.py
@@ -13,7 +13,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
@@ -166,10 +165,9 @@ class VxlansFacts(object):
vxlan['source_ip'] = each_tunnel.get('src_ip', None)
vxlan['primary_ip'] = each_tunnel.get('primary_ip', None)
vxlan['evpn_nvo'] = None
- if vxlan['source_ip']:
- evpn_nvo = next((nvo_map['name'] for nvo_map in vxlans_evpn_nvo_list if nvo_map['source_vtep'] == vxlan['name']), None)
- if evpn_nvo:
- vxlan['evpn_nvo'] = evpn_nvo
+ evpn_nvo = next((nvo_map['name'] for nvo_map in vxlans_evpn_nvo_list if nvo_map['source_vtep'] == vxlan['name']), None)
+ if evpn_nvo:
+ vxlan['evpn_nvo'] = evpn_nvo
vxlans.append(vxlan)
def fill_vlan_map(self, vxlans, vxlan_vlan_map):
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/sonic.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/sonic.py
index 77a63d425..30739ef82 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/sonic.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/sonic.py
@@ -33,7 +33,6 @@ import json
import re
from ansible.module_utils._text import to_text
-from ansible.module_utils.basic import env_fallback
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
to_list,
ComplexList
@@ -132,7 +131,7 @@ def edit_config(module, commands, skip_code=None):
# Start: This is to convert interface name from Eth1/1 to Eth1%2f1
for request in commands:
# This check is to differenciate between requests and commands
- if type(request) is dict:
+ if isinstance(request, dict):
url = request.get("path", None)
if url:
request["path"] = update_url(url)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/bgp_utils.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/bgp_utils.py
index 7471bcb11..9c2d18a52 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/bgp_utils.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/bgp_utils.py
@@ -13,16 +13,10 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
-from copy import deepcopy
-from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
- utils,
-)
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
normalize_interface_name,
)
-from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.bgp.bgp import BgpArgs
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
to_request,
edit_config
@@ -195,11 +189,6 @@ def get_peergroups(module, vrf_name):
prefix_limit = update_bgp_nbr_pg_prefix_limit_dict(pfx_lmt_conf)
if prefix_limit:
samp.update({'prefix_limit': prefix_limit})
- elif 'l2vpn-evpn' in each and 'prefix-limit' in each['l2vpn-evpn'] and 'config' in each['l2vpn-evpn']['prefix-limit']:
- pfx_lmt_conf = each['l2vpn-evpn']['prefix-limit']['config']
- prefix_limit = update_bgp_nbr_pg_prefix_limit_dict(pfx_lmt_conf)
- if prefix_limit:
- samp.update({'prefix_limit': prefix_limit})
if 'prefix-list' in each and 'config' in each['prefix-list']:
pfx_lst_conf = each['prefix-list']['config']
if 'import-policy' in pfx_lst_conf and pfx_lst_conf['import-policy']:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/formatted_diff_utils.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/formatted_diff_utils.py
new file mode 100644
index 000000000..f6385294f
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/formatted_diff_utils.py
@@ -0,0 +1,588 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# 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
+from copy import (
+ deepcopy
+)
+from difflib import (
+ context_diff
+)
+
+
+def get_key_sets(dict_conf):
+ key_set = set(dict_conf.keys())
+ trival_key_set = set()
+ dict_list_key_set = set()
+ for key in key_set:
+ if dict_conf[key] not in [None, [], {}]:
+ if isinstance(dict_conf[key], (list, dict)):
+ dict_list_key_set.add(key)
+ else:
+ trival_key_set.add(key)
+ return trival_key_set, dict_list_key_set
+
+
+def get_test_key_set(key, test_keys):
+ tst_keys = deepcopy(test_keys)
+ t_key_set = set()
+ if not tst_keys or not key:
+ return t_key_set
+
+ t_keys = next((t_key_item[key] for t_key_item in tst_keys if key in t_key_item), None)
+ if t_keys:
+ t_keys.pop('__merge_op', None)
+ t_keys.pop('__delete_op', None)
+ t_keys.pop('__key_match_op', None)
+ t_key_set = set(t_keys.keys())
+
+ return t_key_set
+
+
+#
+# Pre-defined Key Match Operations
+#
+
+
+"""
+Default key match operation.
+"""
+
+
+def __KEY_MATCH_OP_DEFAULT(key_set, command, exist_conf):
+ trival_cmd_key_set, dict_list_cmd_key_set = get_key_sets(command)
+ trival_exist_key_set, dict_list_exist_key_set = get_key_sets(exist_conf)
+
+ common_trival_key_set = trival_cmd_key_set.intersection(trival_exist_key_set)
+ common_dict_list_key_set = dict_list_cmd_key_set.intersection(dict_list_exist_key_set)
+
+ key_matched_cnt = 0
+ for key in common_trival_key_set.union(common_dict_list_key_set):
+ if command[key] == exist_conf[key]:
+ if key in key_set:
+ key_matched_cnt += 1
+
+ key_matched = (key_matched_cnt == len(key_set))
+ return key_matched
+
+
+def get_key_match_op(key, test_keys):
+ k_match_op = __KEY_MATCH_OP_DEFAULT
+ t_key_set = set()
+ if not test_keys or not key:
+ return k_match_op
+
+ t_keys = next((t_key_item[key] for t_key_item in test_keys if key in t_key_item), None)
+ if t_keys:
+ k_match_op = t_keys.get('__key_match_op', __KEY_MATCH_OP_DEFAULT)
+
+ return k_match_op
+
+
+#
+# Pre-defined Merge Operations
+#
+
+
+"""
+Default key match operation: simply merge command to existing config.
+"""
+
+
+def __MERGE_OP_DEFAULT(key_set, command, exist_conf):
+ new_conf = exist_conf
+ trival_cmd_key_set, dict_list_cmd_key_set = get_key_sets(command)
+ nu, dict_list_exist_key_set = get_key_sets(new_conf)
+
+ for key in trival_cmd_key_set:
+ new_conf[key] = command[key]
+
+ only_cmd_dict_list_key_set = dict_list_cmd_key_set.difference(dict_list_exist_key_set)
+ for key in only_cmd_dict_list_key_set:
+ new_conf[key] = command[key]
+
+ return False, new_conf
+
+
+def get_merge_op(key, test_keys):
+ mrg_op = __MERGE_OP_DEFAULT
+ if not test_keys:
+ return mrg_op
+ if not key:
+ key = '__default_ops'
+ t_keys = next((t_key_item[key] for t_key_item in test_keys if key in t_key_item), None)
+ if t_keys:
+ mrg_op = t_keys.get('__merge_op', __MERGE_OP_DEFAULT)
+
+ return mrg_op
+
+
+#
+# Pre-defined Delete Operations
+#
+
+
+"""
+Delete entire configuration.
+"""
+
+
+def __DELETE_CONFIG(key_set, command, exist_conf):
+ new_conf = []
+ return True, new_conf
+
+
+"""
+Delete entire configuration if there is no sub-configuration.
+"""
+
+
+def __DELETE_CONFIG_IF_NO_SUBCONFIG(key_set, command, exist_conf):
+ nu, dict_list_cmd_key_set = get_key_sets(command)
+ if len(dict_list_cmd_key_set) == 0:
+ new_conf = []
+ return True, new_conf
+ else:
+ new_conf = exist_conf
+ return False, new_conf
+
+
+"""
+Delete sub-configuration and leaf configuration, if any.
+"""
+
+
+def __DELETE_SUBCONFIG_AND_LEAFS(key_set, command, exist_conf):
+ new_conf = exist_conf
+
+ trival_cmd_key_set, dict_list_cmd_key_set = get_key_sets(command)
+ trival_cmd_key_not_key_set = trival_cmd_key_set.difference(key_set)
+ for key in trival_cmd_key_not_key_set:
+ new_conf.pop(key, None)
+
+ nu, dict_list_exist_key_set = get_key_sets(exist_conf)
+ common_dict_list_key_set = dict_list_cmd_key_set.intersection(dict_list_exist_key_set)
+ if len(common_dict_list_key_set) != 0:
+ for key in common_dict_list_key_set:
+ new_conf.pop(key, None)
+
+ return True, new_conf
+
+
+"""
+Delete sub-configuration only, if any.
+"""
+
+
+def __DELETE_SUBCONFIG_ONLY(key_set, command, exist_conf):
+ new_conf = exist_conf
+ nu, dict_list_cmd_key_set = get_key_sets(command)
+ nu, dict_list_exist_key_set = get_key_sets(exist_conf)
+ common_dict_list_key_set = dict_list_cmd_key_set.intersection(dict_list_exist_key_set)
+ for key in common_dict_list_key_set:
+ new_conf.pop(key, None)
+ return True, new_conf
+
+
+"""
+Delete configuration if there is no non-key leaf, and
+delete non-key leaf configuration, if any.
+"""
+
+
+def __DELETE_LEAFS_OR_CONFIG_IF_NO_NON_KEY_LEAF(key_set, command, exist_conf):
+ new_conf = exist_conf
+ trival_cmd_key_set, dict_list_cmd_key_set = get_key_sets(command)
+
+ if (len(key_set) == len(trival_cmd_key_set)) and \
+ (len(dict_list_cmd_key_set) == 0):
+ new_conf = []
+ return True, new_conf
+
+ trival_cmd_key_not_key_set = trival_cmd_key_set.difference(key_set)
+ for key in trival_cmd_key_not_key_set:
+ new_conf.pop(key, None)
+
+ return False, new_conf
+
+
+"""
+This is default deletion operation.
+Delete configuration if there is no non-key leaf, and
+delete non-key leaf configuration, if any, if the values of non-key leaf are
+equal between command and existing configuration.
+"""
+
+
+def __DELETE_OP_DEFAULT(key_set, command, exist_conf):
+ new_conf = exist_conf
+ trival_cmd_key_set, dict_list_cmd_key_set = get_key_sets(command)
+
+ if (len(key_set) == len(trival_cmd_key_set)) and \
+ (len(dict_list_cmd_key_set) == 0):
+ new_conf = []
+ return True, new_conf
+
+ trival_cmd_key_not_key_set = trival_cmd_key_set.difference(key_set)
+ for key in trival_cmd_key_not_key_set:
+ command_val = command.get(key, None)
+ new_conf_val = new_conf.get(key, None)
+ if command_val == new_conf_val:
+ new_conf.pop(key, None)
+
+ return False, new_conf
+
+
+def get_delete_op(key, test_keys):
+ del_op = __DELETE_OP_DEFAULT
+ if not test_keys:
+ return del_op
+ if not key:
+ key = '__default_ops'
+ t_keys = next((t_key_item[key] for t_key_item in test_keys if key in t_key_item), None)
+ if t_keys:
+ del_op = t_keys.get('__delete_op', __DELETE_OP_DEFAULT)
+
+ return del_op
+
+
+def get_new_config(commands, exist_conf, test_keys=None):
+
+ if not commands:
+ return exist_conf
+
+ cmds = deepcopy(commands)
+
+ n_conf = list()
+ e_conf = exist_conf
+ for cmd in cmds:
+ state = cmd['state']
+ cmd.pop('state')
+
+ if state == 'merged':
+ n_conf = derive_config_from_merged_cmd(cmd, e_conf, test_keys)
+ elif state == 'deleted':
+ n_conf = derive_config_from_deleted_cmd(cmd, e_conf, test_keys)
+ elif state == 'replaced':
+ n_conf = derive_config_from_merged_cmd(cmd, e_conf, test_keys)
+ elif state == 'overridden':
+ n_conf = derive_config_from_merged_cmd(cmd, e_conf, test_keys)
+ # If the "cmd" is derived from playbook, that is "want", the below
+ # line should be good enough:
+ # n_conf = cmd
+
+ e_conf = n_conf
+
+ return n_conf
+
+
+def derive_config_from_merged_cmd(command, exist_conf, test_keys=None):
+
+ if not command:
+ return exist_conf
+
+ if isinstance(command, list) and isinstance(exist_conf, list):
+ nu, new_conf_dict = derive_config_from_merged_cmd_dict({"config": command},
+ {"config": exist_conf},
+ test_keys)
+ new_conf = new_conf_dict.get("config", [])
+ elif isinstance(command, dict) and isinstance(exist_conf, dict):
+ merge_op_dft = get_merge_op('__default_ops', test_keys)
+ nu, new_conf = derive_config_from_merged_cmd_dict(command, exist_conf,
+ test_keys, None,
+ None, merge_op_dft)
+ elif isinstance(command, dict) and isinstance(exist_conf, list):
+ nu, new_conf_dict = derive_config_from_merged_cmd_dict({"config": [command]},
+ {"config": exist_conf},
+ test_keys)
+ new_conf = new_conf_dict.get("config", [])
+ else:
+ new_conf = exist_conf
+
+ return new_conf
+
+
+def derive_config_from_merged_cmd_dict(command, exist_conf, test_keys=None, key_set=None,
+ key_match_op=None, merge_op=None):
+
+ if test_keys is None:
+ test_keys = []
+ if key_set is None:
+ key_set = set()
+ if key_match_op is None:
+ key_match_op = __KEY_MATCH_OP_DEFAULT
+ if merge_op is None:
+ merge_op = __MERGE_OP_DEFAULT
+
+ new_conf = deepcopy(exist_conf)
+ if not command:
+ return False, new_conf
+
+ trival_cmd_key_set, dict_list_cmd_key_set = get_key_sets(command)
+ trival_exist_key_set, dict_list_exist_key_set = get_key_sets(new_conf)
+
+ common_trival_key_set = trival_cmd_key_set.intersection(trival_exist_key_set)
+ common_dict_list_key_set = dict_list_cmd_key_set.intersection(dict_list_exist_key_set)
+
+ key_matched = key_match_op(key_set, command, new_conf)
+ if key_matched:
+ done, new_conf = merge_op(key_set, command, new_conf)
+ if done:
+ return key_matched, new_conf
+ else:
+ nu, dict_list_exist_key_set = get_key_sets(new_conf)
+ common_dict_list_key_set = dict_list_cmd_key_set.intersection(dict_list_exist_key_set)
+ else:
+ return key_matched, new_conf
+
+ for key in key_set:
+ common_dict_list_key_set.discard(key)
+
+ for key in common_dict_list_key_set:
+
+ cmd_value = command[key]
+ exist_value = new_conf[key]
+
+ t_key_set = get_test_key_set(key, test_keys)
+ t_key_match_op = get_key_match_op(key, test_keys)
+ t_merge_op = get_merge_op(key, test_keys)
+
+ if (isinstance(cmd_value, list) and isinstance(exist_value, list)):
+ c_list = cmd_value
+ e_list = exist_value
+
+ new_conf_list = list()
+ not_dict_item = False
+ dict_no_key_item = False
+ for c_item in c_list:
+ matched_key_dict = False
+ for e_item in e_list:
+ if (isinstance(c_item, dict) and isinstance(e_item, dict)):
+ if t_key_set:
+ remaining_keys = [t_key_item for t_key_item in test_keys if key not in t_key_item]
+ k_mtchd, new_conf_dict = derive_config_from_merged_cmd_dict(c_item,
+ e_item,
+ remaining_keys,
+ t_key_set,
+ t_key_match_op,
+ t_merge_op)
+ if k_mtchd:
+ new_conf[key].remove(e_item)
+ if new_conf_dict:
+ new_conf_list.append(new_conf_dict)
+ matched_key_dict = True
+ break
+ else:
+ dict_no_key_item = True
+ break
+
+ else:
+ not_dict_item = True
+ break
+
+ if not matched_key_dict:
+ new_conf_list.append(c_item)
+
+ if not_dict_item or dict_no_key_item:
+ break
+
+ if dict_no_key_item:
+ new_conf_list = e_list + c_list
+
+ if not_dict_item:
+ c_set = set(c_list)
+ e_set = set(e_list)
+ merge_set = c_set.union(e_set)
+ if merge_set:
+ new_conf[key] = list(merge_set)
+ elif new_conf_list:
+ new_conf[key].extend(new_conf_list)
+
+ elif (isinstance(cmd_value, dict) and isinstance(exist_value, dict)):
+ k_mtchd, new_conf_dict = derive_config_from_merged_cmd_dict(cmd_value,
+ exist_value,
+ test_keys,
+ None,
+ t_key_match_op,
+ t_merge_op)
+ if k_mtchd and new_conf_dict:
+ new_conf[key] = new_conf_dict
+
+ elif (isinstance(cmd_value, (list, dict)) or isinstance(exist_value, (list, dict))):
+ new_conf[key] = exist_value
+ break
+
+ else:
+ continue
+
+ return key_matched, new_conf
+
+
+def derive_config_from_deleted_cmd(command, exist_conf, test_keys=None):
+
+ if not command or not exist_conf:
+ return exist_conf
+
+ if isinstance(command, list) and isinstance(exist_conf, list):
+ nu, new_conf_dict = derive_config_from_deleted_cmd_dict({"config": command},
+ {"config": exist_conf},
+ test_keys)
+ new_conf = new_conf_dict.get("config", [])
+ elif isinstance(command, dict) and isinstance(exist_conf, dict):
+ delete_op_dft = get_delete_op('__default_ops', test_keys)
+ nu, new_conf = derive_config_from_deleted_cmd_dict(command, exist_conf,
+ test_keys, None,
+ None, delete_op_dft)
+ elif isinstance(command, dict) and isinstance(exist_conf, list):
+ nu, new_conf_dict = derive_config_from_deleted_cmd_dict({"config": [command]},
+ {"config": exist_conf},
+ test_keys)
+ new_conf = new_conf_dict.get("config", [])
+ else:
+ new_conf = exist_conf
+
+ return new_conf
+
+
+def derive_config_from_deleted_cmd_dict(command, exist_conf, test_keys=None, key_set=None,
+ key_match_op=None, delete_op=None):
+
+ if test_keys is None:
+ test_keys = []
+ if key_set is None:
+ key_set = set()
+ if key_match_op is None:
+ key_match_op = __KEY_MATCH_OP_DEFAULT
+ if delete_op is None:
+ delete_op = __DELETE_OP_DEFAULT
+
+ new_conf = deepcopy(exist_conf)
+ if not command:
+ return True, []
+
+ trival_cmd_key_set, dict_list_cmd_key_set = get_key_sets(command)
+ trival_exist_key_set, dict_list_exist_key_set = get_key_sets(new_conf)
+
+ common_trival_key_set = trival_cmd_key_set.intersection(trival_exist_key_set)
+ common_dict_list_key_set = dict_list_cmd_key_set.intersection(dict_list_exist_key_set)
+
+ key_matched = key_match_op(key_set, command, new_conf)
+ if key_matched:
+ done, new_conf = delete_op(key_set, command, new_conf)
+ if done:
+ return key_matched, new_conf
+ else:
+ nu, dict_list_exist_key_set = get_key_sets(new_conf)
+ common_dict_list_key_set = dict_list_cmd_key_set.intersection(dict_list_exist_key_set)
+ else:
+ return key_matched, new_conf
+
+ for key in key_set:
+ common_dict_list_key_set.discard(key)
+
+ for key in common_dict_list_key_set:
+
+ cmd_value = command[key]
+ exist_value = new_conf[key]
+
+ t_key_set = get_test_key_set(key, test_keys)
+ t_key_match_op = get_key_match_op(key, test_keys)
+ t_delete_op = get_delete_op(key, test_keys)
+
+ if (isinstance(cmd_value, list) and isinstance(exist_value, list)):
+ c_list = cmd_value
+ e_list = exist_value
+
+ new_conf_list = list()
+ not_dict_item = False
+ dict_no_key_item = False
+ for c_item in c_list:
+ for e_item in e_list:
+ if (isinstance(c_item, dict) and isinstance(e_item, dict)):
+ if t_key_set:
+ remaining_keys = [t_key_item for t_key_item in test_keys if key not in t_key_item]
+ k_mtchd, new_conf_dict = derive_config_from_deleted_cmd_dict(c_item, e_item,
+ remaining_keys,
+ t_key_set,
+ t_key_match_op,
+ t_delete_op)
+ if k_mtchd:
+ new_conf[key].remove(e_item)
+ if new_conf_dict:
+ new_conf_list.append(new_conf_dict)
+ break
+ else:
+ dict_no_key_item = True
+ break
+
+ else:
+ not_dict_item = True
+ break
+
+ if not_dict_item or dict_no_key_item:
+ break
+
+ if dict_no_key_item:
+ new_conf_list = e_list
+
+ if not_dict_item:
+ c_set = set(c_list)
+ e_set = set(e_list)
+ delete_set = e_set.difference(c_set)
+ if delete_set:
+ new_conf[key] = list(delete_set)
+ else:
+ new_conf[key] = []
+ elif new_conf_list:
+ new_conf[key].extend(new_conf_list)
+
+ elif (isinstance(cmd_value, dict) and isinstance(exist_value, dict)):
+ k_mtchd, new_conf_dict = derive_config_from_deleted_cmd_dict(cmd_value,
+ exist_value,
+ test_keys,
+ None,
+ t_key_match_op,
+ t_delete_op)
+ if k_mtchd:
+ new_conf.pop(key, None)
+ if new_conf_dict:
+ new_conf[key] = new_conf_dict
+
+ elif (isinstance(cmd_value, (list, dict)) or isinstance(exist_value, (list, dict))):
+ new_conf[key] = exist_value
+ break
+
+ else:
+ continue
+
+ return key_matched, new_conf
+
+
+def get_formatted_config_diff(exist_conf, new_conf, verbosity=0):
+
+ exist_conf = json.dumps(exist_conf, sort_keys=True, indent=4, separators=(u',', u': ')) + u'\n'
+ new_conf = json.dumps(new_conf, sort_keys=True, indent=4, separators=(u',', u': ')) + u'\n'
+
+ bfr = exist_conf.replace("\"", "\'")
+ aft = new_conf.replace("\"", "\'")
+
+ bfr_list = bfr.splitlines(True)
+ aft_list = aft.splitlines(True)
+ diffs = context_diff(bfr_list, aft_list, fromfile='before', tofile='after')
+
+ if verbosity >= 3:
+ formatted_diff = list()
+ for diff in diffs:
+ formatted_diff.append(diff.rstrip('\n'))
+
+ else:
+ formatted_diff = {'prepared': u''.join(diffs)}
+
+ return formatted_diff
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/interfaces_util.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/interfaces_util.py
index a7f6e9063..60df9251d 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/interfaces_util.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/interfaces_util.py
@@ -27,10 +27,16 @@ __metaclass__ = type
import traceback
import json
+import re
from ansible.module_utils._text import to_native
try:
+ from urllib import quote
+except ImportError:
+ from urllib.parse import quote
+
+try:
import jinja2
HAS_LIB = True
except Exception as e:
@@ -38,6 +44,29 @@ except Exception as e:
ERR_MSG = to_native(e)
LIB_IMP_ERR = traceback.format_exc()
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+
+intf_speed_map = {
+ 0: 'SPEED_DEFAULT',
+ 10: "SPEED_10MB",
+ 100: "SPEED_100MB",
+ 1000: "SPEED_1GB",
+ 2500: "SPEED_2500MB",
+ 5000: "SPEED_5GB",
+ 10000: "SPEED_10GB",
+ 20000: "SPEED_20GB",
+ 25000: "SPEED_25GB",
+ 40000: "SPEED_40GB",
+ 50000: "SPEED_50GB",
+ 100000: "SPEED_100GB",
+ 200000: "SPEED_200GB",
+ 400000: "SPEED_400GB",
+ 800000: "SPEED_800GB"
+}
+
# To create Loopback, VLAN interfaces
def build_interfaces_create_request(interface_name):
@@ -53,3 +82,60 @@ def build_interfaces_create_request(interface_name):
"method": method,
"data": ret_payload}
return request
+
+
+def retrieve_port_group_interfaces(module):
+ port_group_interfaces = []
+ method = "get"
+ port_num_regex = re.compile(r'[\d]{1,4}$')
+ port_group_url = 'data/openconfig-port-group:port-groups'
+ request = {"path": port_group_url, "method": method}
+ try:
+ response = edit_config(module, to_request(module, request))
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+
+ if 'openconfig-port-group:port-groups' in response[0][1] and "port-group" in response[0][1]['openconfig-port-group:port-groups']:
+ port_groups = response[0][1]['openconfig-port-group:port-groups']['port-group']
+ for pg_config in port_groups:
+ if 'state' in pg_config:
+ member_start = pg_config['state'].get('member-if-start', '')
+ member_start = re.search(port_num_regex, member_start)
+ member_end = pg_config['state'].get('member-if-end', '')
+ member_end = re.search(port_num_regex, member_end)
+ if member_start and member_end:
+ member_start = int(member_start.group(0))
+ member_end = int(member_end.group(0))
+ port_group_interfaces.extend(range(member_start, member_end + 1))
+
+ return port_group_interfaces
+
+
+def retrieve_default_intf_speed(module, intf_name):
+
+ # Read the valid_speeds
+ dft_intf_speed = 'SPEED_DEFAULT'
+ method = "get"
+ sonic_port_url = 'data/sonic-port:sonic-port/PORT/PORT_LIST=%s'
+ sonic_port_vs_url = (sonic_port_url + '/valid_speeds') % quote(intf_name, safe='')
+ request = {"path": sonic_port_vs_url, "method": method}
+ try:
+ response = edit_config(module, to_request(module, request))
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+ if 'sonic-port:valid_speeds' in response[0][1]:
+ v_speeds = response[0][1].get('sonic-port:valid_speeds', '')
+ v_speeds_list = v_speeds.split(",")
+ v_speeds_int_list = []
+ for vs in v_speeds_list:
+ v_speeds_int_list.append(int(vs))
+
+ dft_speed_int = 0
+ if v_speeds_int_list:
+ dft_speed_int = max(v_speeds_int_list)
+ dft_intf_speed = intf_speed_map.get(dft_speed_int, 'SPEED_DEFAULT')
+
+ if dft_intf_speed == 'SPEED_DEFAULT':
+ module.fail_json(msg="Unable to retireve default port speed for the interface {0}".format(intf_name))
+
+ return dft_intf_speed
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/utils.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/utils.py
index 0d6e6d1a0..bc865790b 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/utils.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/utils.py
@@ -13,12 +13,17 @@ __metaclass__ = type
import re
import json
import ast
+from copy import copy
+from itertools import (count, groupby)
from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_empties
+)
+from ansible.module_utils.common.network import (
is_masklen,
to_netmask,
- remove_empties
)
+from ansible.module_utils.common.validation import check_required_arguments
from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
to_request,
edit_config
@@ -31,6 +36,21 @@ GET = 'get'
intf_naming_mode = ""
+def remove_matching_defaults(root, default_entry):
+ if isinstance(root, list):
+ for list_item in root:
+ remove_matching_defaults(list_item, default_entry)
+ elif isinstance(root, dict):
+ nextobj = root.get(default_entry[0]['name'])
+ if nextobj is not None:
+ if len(default_entry) > 1:
+ remove_matching_defaults(nextobj, default_entry[1:])
+ else:
+ # Leaf
+ if nextobj == default_entry[0]['default']:
+ root.pop(default_entry[0]['name'])
+
+
def get_diff(base_data, compare_with_data, test_keys=None, is_skeleton=None):
diff = []
if is_skeleton is None:
@@ -319,10 +339,13 @@ def netmask_to_cidr(netmask):
def remove_empties_from_list(config_list):
ret_config = []
- if not config_list:
+ if not config_list or not isinstance(config_list, list):
return ret_config
for config in config_list:
- ret_config.append(remove_empties(config))
+ if isinstance(config, dict):
+ ret_config.append(remove_empties(config))
+ else:
+ ret_config.append(copy(config))
return ret_config
@@ -432,14 +455,7 @@ def get_normalize_interface_name(intf_name, module):
def get_speed_from_breakout_mode(breakout_mode):
- speed = None
- speed_breakout_mode_map = {
- "4x10G": "SPEED_10GB", "1x100G": "SPEED_100GB", "1x40G": "SPEED_40GB", "4x25G": "SPEED_25GB", "2x50G": "SPEED_50GB",
- "1x400G": "SPEED_400GB", "4x100G": "SPEED_100GB", "4x50G": "SPEED_50GB", "2x100G": "SPEED_100GB", "2x200G": "SPEED_200GB"
- }
- if breakout_mode in speed_breakout_mode_map:
- speed = speed_breakout_mode_map[breakout_mode]
- return speed
+ return 'SPEED_' + breakout_mode.split('x')[1].replace('G', 'GB')
def get_breakout_mode(module, name):
@@ -455,7 +471,7 @@ def get_breakout_mode(module, name):
except ConnectionError as exc:
try:
json_obj = json.loads(str(exc).replace("'", '"'))
- if json_obj and type(json_obj) is dict and 404 == json_obj['code']:
+ if json_obj and isinstance(json_obj, dict) and 404 == json_obj['code']:
response = None
else:
module.fail_json(msg=str(exc), code=exc.code)
@@ -509,3 +525,205 @@ def command_list_str_to_dict(module, warnings, cmd_list_in, exec_cmd=False):
cmd_list_out.append(cmd_out)
return cmd_list_out
+
+
+def send_requests(module, requests):
+
+ reply = dict()
+ response = []
+ if not module.check_mode and requests:
+ try:
+ response = edit_config(module, to_request(module, requests))
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+
+ reply = response[0][1]
+
+ return reply
+
+
+def get_replaced_config(new_conf, exist_conf, test_keys=None):
+
+ replace_conf = []
+ if not new_conf or not exist_conf:
+ return replace_conf
+
+ if isinstance(new_conf, list) and isinstance(exist_conf, list):
+
+ replace_conf_dict = get_replaced_config_dict({"config": new_conf},
+ {"config": exist_conf},
+ test_keys)
+ replaced_conf = replace_conf_dict.get("config", [])
+ else:
+ replaced_conf = get_replaced_config_dict(new_conf, exist_conf, test_keys)
+
+ return replaced_conf
+
+
+def get_replaced_config_dict(new_conf, exist_conf, test_keys=None, key_set=None):
+
+ replaced_conf = dict()
+
+ if test_keys is None:
+ test_keys = []
+ if key_set is None:
+ key_set = []
+
+ if not new_conf:
+ return replaced_conf
+
+ new_key_set = set(new_conf.keys())
+ exist_key_set = set(exist_conf.keys())
+
+ trival_new_key_set = set()
+ dict_list_new_key_set = set()
+ for key in new_key_set:
+ if new_conf[key] not in [None, [], {}]:
+ if isinstance(new_conf[key], (list, dict)):
+ dict_list_new_key_set.add(key)
+ else:
+ trival_new_key_set.add(key)
+
+ trival_exist_key_set = set()
+ dict_list_exist_key_set = set()
+ for key in exist_key_set:
+ if exist_conf[key] not in [None, [], {}]:
+ if isinstance(exist_conf[key], (list, dict)):
+ dict_list_exist_key_set.add(key)
+ else:
+ trival_exist_key_set.add(key)
+
+ common_trival_key_set = trival_new_key_set.intersection(trival_exist_key_set)
+ common_dict_list_key_set = dict_list_new_key_set.intersection(dict_list_exist_key_set)
+
+ key_matched_cnt = 0
+ common_trival_key_matched = True
+ for key in common_trival_key_set:
+ if new_conf[key] == exist_conf[key]:
+ if key in key_set:
+ key_matched_cnt += 1
+ else:
+ if key not in key_set:
+ common_trival_key_matched = False
+
+ for key in common_dict_list_key_set:
+ if new_conf[key] == exist_conf[key]:
+ if key in key_set:
+ key_matched_cnt += 1
+
+ key_matched = (key_matched_cnt == len(key_set))
+ if key_matched:
+ extra_trival_new_key_set = trival_new_key_set - common_trival_key_set
+ extra_trival_exist_key_set = trival_exist_key_set - common_trival_key_set
+ if extra_trival_new_key_set or extra_trival_exist_key_set or \
+ not common_trival_key_matched:
+ # Replace whole dict.
+ replaced_conf = exist_conf
+ return replaced_conf
+ else:
+ replaced_conf = []
+ return replaced_conf
+
+ for key in key_set:
+ common_dict_list_key_set.discard(key)
+
+ replace_whole_dict = False
+ replace_some_list = False
+ replace_some_dict = False
+ for key in common_dict_list_key_set:
+
+ new_value = new_conf[key]
+ exist_value = exist_conf[key]
+
+ if (isinstance(new_value, list) and isinstance(exist_value, list)):
+ n_list = new_value
+ e_list = exist_value
+ t_keys = next((t_key_item[key] for t_key_item in test_keys if key in t_key_item), None)
+ t_key_set = set()
+ if t_keys:
+ t_key_set = set(t_keys.keys())
+
+ replaced_list = list()
+ not_dict_item = False
+ dict_no_key_item = False
+ for n_item in n_list:
+ for e_item in e_list:
+ if (isinstance(n_item, dict) and isinstance(e_item, dict)):
+ if t_keys:
+ remaining_keys = [t_key_item for t_key_item in test_keys if key not in t_key_item]
+ replaced_dict = get_replaced_config_dict(n_item, e_item,
+ remaining_keys, t_key_set)
+ else:
+ dict_no_key_item = True
+ break
+
+ if replaced_dict:
+ replaced_list.append(replaced_dict)
+ break
+ else:
+ not_dict_item = True
+ break
+
+ if not_dict_item or dict_no_key_item:
+ break
+
+ if dict_no_key_item:
+ replaced_list = e_list
+
+ if not_dict_item:
+ n_set = set(n_list)
+ e_set = set(e_list)
+ diff_set = n_set.symmetric_difference(e_set)
+ if diff_set:
+ replaced_conf[key] = e_list
+ replace_some_list = True
+
+ elif replaced_list:
+ replaced_conf[key] = replaced_list
+ replace_some_list = True
+
+ elif (isinstance(new_value, dict) and isinstance(exist_value, dict)):
+ replaced_dict = get_replaced_config_dict(new_conf[key], exist_conf[key], test_keys)
+ if replaced_dict:
+ replaced_conf[key] = replaced_dict
+ replace_some_dict = True
+
+ elif (isinstance(new_value, (list, dict)) or isinstance(exist_value, (list, dict))):
+ # Replace whole dict.
+ replaced_conf = exist_conf
+ replace_whole_dict = True
+ break
+
+ else:
+ continue
+
+ if ((replace_some_dict or replace_some_list) and (not replace_whole_dict)):
+ for key in key_set:
+ replaced_conf[key] = exist_conf[key]
+
+ return replaced_conf
+
+
+def check_required(module, required_parameters, parameters, options_context=None):
+ '''This utility is a wrapper for the Ansible "check_required_arguments"
+ function. The "required_parameters" input list provides a list of
+ key names that are required in the dictionary specified by "parameters".
+ The optional "options_context" parameter specifies the context/path
+ from the top level parent dict to the dict being checked.'''
+ if required_parameters:
+ spec = {}
+ for parameter in required_parameters:
+ spec[parameter] = {'required': True}
+
+ try:
+ check_required_arguments(spec, parameters, options_context)
+ except TypeError as exc:
+ module.fail_json(msg=str(exc))
+
+
+def get_ranges_in_list(num_list):
+ """Returns a generator for list(s) of consecutive numbers
+ present in the given sorted list of numbers
+ """
+ for key, group in groupby(num_list, lambda num, i=count(): num - next(i)):
+ yield list(group)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_aaa.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_aaa.py
index ddc71331f..c17c0f711 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_aaa.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_aaa.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-# Copyright 2021 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -79,8 +79,10 @@ options:
- Specifies the operation to be performed on the aaa parameters configured on the device.
- In case of merged, the input configuration will be merged with the existing aaa configuration on the device.
- In case of deleted the existing aaa configuration will be removed from the device.
+ - In case of replaced, the existing aaa configuration will be replaced with provided configuration.
+ - In case of overridden, the existing aaa configuration will be overridden with the provided configuration.
default: merged
- choices: ['merged', 'deleted']
+ choices: ['merged', 'deleted', 'overridden', 'replaced']
type: str
"""
EXAMPLES = """
@@ -169,6 +171,65 @@ EXAMPLES = """
# login-method : local
+# Using replaced
+#
+# Before state:
+# -------------
+#
+# do show aaa
+# AAA Authentication Information
+# ---------------------------------------------------------
+# failthrough : False
+# login-method : local, radius
+
+- name: Replace aaa configurations
+ dellemc.enterprise_sonic.sonic_aaa:
+ config:
+ authentication:
+ data:
+ group: ldap
+ fail_through: true
+ state: replaced
+
+# After state:
+# ------------
+#
+# do show aaa
+# AAA Authentication Information
+# ---------------------------------------------------------
+# failthrough : True
+# login-method : local, ldap
+
+
+# Using overridden
+#
+# Before state:
+# -------------
+#
+# do show aaa
+# AAA Authentication Information
+# ---------------------------------------------------------
+# failthrough : False
+# login-method : local, radius
+
+- name: Override aaa configurations
+ dellemc.enterprise_sonic.sonic_aaa:
+ config:
+ authentication:
+ data:
+ group: tacacs+
+ fail_through: true
+ state: overridden
+
+# After state:
+# ------------
+#
+# do show aaa
+# AAA Authentication Information
+# ---------------------------------------------------------
+# failthrough : True
+# login-method : tacacs+
+
"""
RETURN = """
before:
@@ -185,6 +246,13 @@ after:
sample: >
The configuration returned will always be in the same format
of the parameters above.
+after(generated):
+ description: The generated configuration model invocation.
+ returned: when C(check_mode)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
commands:
description: The set of commands pushed to the remote device.
returned: always
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_acl_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_acl_interfaces.py
new file mode 100644
index 000000000..883252bc8
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_acl_interfaces.py
@@ -0,0 +1,385 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_acl_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_acl_interfaces
+version_added: '2.1.0'
+notes:
+ - Supports C(check_mode).
+short_description: Manage access control list (ACL) to interface binding on SONiC
+description:
+ - This module provides configuration management of applying access control lists (ACL)
+ to interfaces in devices running SONiC.
+ - ACL needs to be created earlier in the device.
+author: 'Arun Saravanan Balachandran (@ArunSaravananBalachandran)'
+options:
+ config:
+ description:
+ - Specifies interface access-group configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of the interface, i.e. Eth1/1.
+ type: str
+ required: true
+ access_groups:
+ description:
+ - Access-group configurations to be set for the interface.
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - Type of the ACLs to be applied on the interface.
+ type: str
+ required: true
+ choices:
+ - mac
+ - ipv4
+ - ipv6
+ acls:
+ description:
+ - List of ACLs for the given type.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the ACL to be applied on the interface.
+ type: str
+ required: true
+ direction:
+ description:
+ - Specifies the direction of the packets that the ACL will be applied on.
+ type: str
+ required: true
+ choices:
+ - in
+ - out
+ state:
+ description:
+ - The state of the configuration after module completion.
+ - I(merged) - Merges provided interface access-group configuration with on-device configuration.
+ - I(replaced) - Replaces on-device access-group configuration of the specified interfaces with provided configuration.
+ - I(overridden) - Overrides all on-device interface access-group configurations with the provided configuration.
+ - I(deleted) - Deletes on-device interface access-group configuration.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+#
+# Before State:
+# -------------
+#
+# sonic# show mac access-group
+# sonic#
+# sonic# show ip access-group
+# sonic#
+# sonic# show ipv6 access-group
+# Ingress IPV6 access-list ipv6-acl-1 on Eth1/1
+# sonic#
+
+ - name: Merge provided interface access-group configurations
+ dellemc.enterprise_sonic.sonic_acl_interfaces:
+ config:
+ - name: 'Eth1/1'
+ access_groups:
+ - type: 'mac'
+ acls:
+ - name: 'mac-acl-1'
+ direction: 'in'
+ - name: 'mac-acl-2'
+ direction: 'out'
+ - type: 'ipv6'
+ acls:
+ - name: 'ipv6-acl-2'
+ direction: 'out'
+ - name: 'Eth1/2'
+ access_groups:
+ - type: 'ipv4'
+ acls:
+ - name: 'ip-acl-1'
+ direction: 'in'
+ state: merged
+
+# After State:
+# ------------
+#
+# sonic# show mac access-group
+# Ingress MAC access-list mac-acl-1 on Eth1/1
+# Egress MAC access-list mac-acl-2 on Eth1/1
+# sonic#
+# sonic# show ip access-group
+# Ingress IP access-list ip-acl-1 on Eth1/2
+# sonic#
+# sonic# show ipv6 access-group
+# Ingress IPV6 access-list ipv6-acl-1 on Eth1/1
+# Egress IPV6 access-list ipv6-acl-2 on Eth1/1
+# sonic#
+
+
+# Using replaced
+#
+# Before State:
+# -------------
+#
+# sonic# show mac access-group
+# Ingress MAC access-list mac-acl-1 on Eth1/1
+# Egress MAC access-list mac-acl-2 on Eth1/1
+# sonic#
+# sonic# show ip access-group
+# Ingress IP access-list ip-acl-1 on Eth1/2
+# sonic#
+# sonic# show ipv6 access-group
+# Ingress IPV6 access-list ipv6-acl-1 on Eth1/1
+# Egress IPV6 access-list ipv6-acl-2 on Eth1/1
+# sonic#
+
+ - name: Replace device access-group configuration of specified interfaces with provided configuration
+ dellemc.enterprise_sonic.sonic_acl_interfaces:
+ config:
+ - name: 'Eth1/2'
+ access_groups:
+ - type: 'ipv6'
+ acls:
+ - name: 'ipv6-acl-2'
+ direction: 'out'
+ - name: 'Eth1/3'
+ access_groups:
+ - type: 'ipv4'
+ acls:
+ - name: 'ip-acl-2'
+ direction: 'out'
+ state: replaced
+
+# After State:
+# ------------
+#
+# sonic# show mac access-group
+# Ingress MAC access-list mac-acl-1 on Eth1/1
+# Egress MAC access-list mac-acl-2 on Eth1/1
+# sonic#
+# sonic# show ip access-group
+# Egress IP access-list ip-acl-2 on Eth1/3
+# sonic#
+# sonic# show ipv6 access-group
+# Ingress IPV6 access-list ipv6-acl-1 on Eth1/1
+# Egress IPV6 access-list ipv6-acl-2 on Eth1/1
+# Egress IPV6 access-list ipv6-acl-2 on Eth1/2
+# sonic#
+
+
+# Using overridden
+#
+# Before State:
+# -------------
+#
+# sonic# show mac access-group
+# Ingress MAC access-list mac-acl-1 on Eth1/1
+# Egress MAC access-list mac-acl-2 on Eth1/1
+# sonic#
+# sonic# show ip access-group
+# Egress IP access-list ip-acl-2 on Eth1/3
+# sonic#
+# sonic# show ipv6 access-group
+# Ingress IPV6 access-list ipv6-acl-1 on Eth1/1
+# Egress IPV6 access-list ipv6-acl-2 on Eth1/1
+# Egress IPV6 access-list ipv6-acl-2 on Eth1/2
+# sonic#
+
+ - name: Override all interfaces access-group device configuration with provided configuration
+ dellemc.enterprise_sonic.sonic_acl_interfaces:
+ config:
+ - name: 'Eth1/1'
+ access_groups:
+ - type: 'ip'
+ acls:
+ - name: 'ip-acl-2'
+ direction: 'out'
+ - name: 'Eth1/2'
+ access_groups:
+ - type: 'ip'
+ acls:
+ - name: 'ip-acl-2'
+ direction: 'out'
+ state: overridden
+
+# After State:
+# ------------
+#
+# sonic# show mac access-group
+# sonic#
+# sonic# show ip access-group
+# Egress IP access-list ip-acl-2 on Eth1/1
+# Egress IP access-list ip-acl-2 on Eth1/2
+# sonic#
+# sonic# show ipv6 access-group
+# sonic#
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show mac access-group
+# Ingress MAC access-list mac-acl-1 on Eth1/1
+# Egress MAC access-list mac-acl-2 on Eth1/1
+# sonic#
+# sonic# show ip access-group
+# Egress IP access-list ip-acl-2 on Eth1/3
+# sonic#
+# sonic# show ipv6 access-group
+# Ingress IPV6 access-list ipv6-acl-1 on Eth1/1
+# Egress IPV6 access-list ipv6-acl-2 on Eth1/1
+# Egress IPV6 access-list ipv6-acl-2 on Eth1/2
+# sonic#
+
+ - name: Delete specified interfaces access-group configurations
+ dellemc.enterprise_sonic.sonic_l2_acls:
+ config:
+ - name: 'Eth1/1'
+ access_groups:
+ - type: 'mac'
+ acls:
+ - name: 'mac-acl-1'
+ direction: 'in'
+ - type: 'ipv6'
+ - name: 'Eth1/2'
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show mac access-group
+# Egress MAC access-list mac-acl-2 on Eth1/1
+# sonic#
+# sonic# show ip access-group
+# Egress IP access-list ip-acl-2 on Eth1/3
+# sonic#
+# sonic# show ipv6 access-group
+# sonic#
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show mac access-group
+# Ingress MAC access-list mac-acl-1 on Eth1/1
+# Egress MAC access-list mac-acl-2 on Eth1/1
+# sonic#
+# sonic# show ip access-group
+# Egress IP access-list ip-acl-2 on Eth1/3
+# sonic#
+# sonic# show ipv6 access-group
+# Ingress IPV6 access-list ipv6-acl-1 on Eth1/1
+# Egress IPV6 access-list ipv6-acl-2 on Eth1/1
+# Egress IPV6 access-list ipv6-acl-2 on Eth1/2
+# sonic#
+
+ - name: Delete all interface access-group configurations
+ dellemc.enterprise_sonic.sonic_acl_interfaces:
+ config:
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show mac access-group
+# sonic#
+# sonic# show ip access-group
+# sonic#
+# sonic# show ipv6 access-group
+# sonic#
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after(generated):
+ description: The generated configuration model invocation.
+ returned: when C(check_mode)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.acl_interfaces.acl_interfaces import Acl_interfaces
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Acl_interfacesArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Acl_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bfd.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bfd.py
new file mode 100644
index 000000000..c969b1a69
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bfd.py
@@ -0,0 +1,684 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_bfd
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'
+}
+
+DOCUMENTATION = """
+---
+module: sonic_bfd
+version_added: "2.1.0"
+short_description: Manage BFD configuration on SONiC
+description:
+ - This module provides configuration management of BFD for devices running SONiC
+author: "Shade Talabi (@stalabi1)"
+options:
+ config:
+ description:
+ - Specifies BFD configurations
+ type: dict
+ suboptions:
+ profiles:
+ description:
+ - List of preconfiguration profiles
+ type: list
+ elements: dict
+ suboptions:
+ profile_name:
+ description:
+ - BFD profile name
+ type: str
+ required: True
+ enabled:
+ description:
+ - Enables BFD session when set to true
+ type: bool
+ default: True
+ transmit_interval:
+ description:
+ - Specifies peer transmit interval
+ type: int
+ default: 300
+ receive_interval:
+ description:
+ - Specifies peer receive interval
+ type: int
+ default: 300
+ detect_multiplier:
+ description:
+ - Number of missed packets to bring down a BFD session
+ type: int
+ default: 3
+ passive_mode:
+ description:
+ - Specifies BFD peer as passive when set to true
+ type: bool
+ default: False
+ min_ttl:
+ description:
+ - Minimum expected TTL on received packets
+ type: int
+ default: 254
+ echo_interval:
+ description:
+ - Specifies echo interval
+ type: int
+ default: 300
+ echo_mode:
+ description:
+ - Echo mode is enabled when set to true
+ type: bool
+ default: False
+ single_hops:
+ description:
+ - List of single-hop sessions
+ type: list
+ elements: dict
+ suboptions:
+ remote_address:
+ description:
+ - IP address used by the remote system for the BFD session
+ type: str
+ required: True
+ vrf:
+ description:
+ - Name of the configured VRF on the device
+ type: str
+ required: True
+ interface:
+ description:
+ - Interface to use to contact peer
+ type: str
+ required: True
+ local_address:
+ description:
+ - Source IP address to be used for BFD sessions over the interface
+ type: str
+ required: True
+ enabled:
+ description:
+ - Enables BFD session when set to true
+ type: bool
+ default: True
+ transmit_interval:
+ description:
+ - Specifies peer transmit interval
+ type: int
+ default: 300
+ receive_interval:
+ description:
+ - Specifies peer receive interval
+ type: int
+ default: 300
+ detect_multiplier:
+ description:
+ - Number of missed packets to bring down a BFD session
+ type: int
+ default: 3
+ passive_mode:
+ description:
+ - Specifies BFD peer as passive when set to true
+ type: bool
+ default: False
+ echo_interval:
+ description:
+ - Specifies echo interval
+ type: int
+ default: 300
+ echo_mode:
+ description:
+ - Echo mode is enabled when set to true
+ type: bool
+ default: False
+ profile_name:
+ description:
+ - BFD profile name
+ type: str
+ multi_hops:
+ description:
+ - List of multi-hop sessions
+ type: list
+ elements: dict
+ suboptions:
+ remote_address:
+ description:
+ - IP address used by the remote system for the BFD session
+ type: str
+ required: True
+ vrf:
+ description:
+ - Name of the configured VRF on the device
+ type: str
+ required: True
+ local_address:
+ description:
+ - Source IP address to be used for BFD sessions over the interface
+ type: str
+ required: True
+ enabled:
+ description:
+ - Enables BFD session when set to true
+ type: bool
+ default: True
+ transmit_interval:
+ description:
+ - Specifies peer transmit interval
+ type: int
+ default: 300
+ receive_interval:
+ description:
+ - Specifies peer receive interval
+ type: int
+ default: 300
+ detect_multiplier:
+ description:
+ - Number of missed packets to bring down a BFD session
+ type: int
+ default: 3
+ passive_mode:
+ description:
+ - Specifies BFD peer as passive when set to true
+ type: bool
+ default: False
+ min_ttl:
+ description:
+ - Minimum expected TTL on received packets
+ type: int
+ default: 254
+ profile_name:
+ description:
+ - BFD profile name
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices: ['merged', 'deleted', 'replaced', 'overridden']
+ default: merged
+"""
+EXAMPLES = """
+# Using Merged
+#
+# Before state:
+# -------------
+#
+# sonic# show bfd profile
+# (No "bfd profile" configuration present)
+# sonic# show bfd peers
+# (No "bfd peers" configuration present)
+
+ - name: Merge BFD configuration
+ dellemc.enterprise_sonic.sonic_bfd:
+ config:
+ profiles:
+ - profile_name: 'p1'
+ enabled: True
+ transmit_interval: 120
+ receive_interval: 200
+ detect_multiplier: 2
+ passive_mode: True
+ min_ttl: 140
+ echo_interval: 150
+ echo_mode: True
+ single_hops:
+ - remote_address: '196.88.6.1'
+ vrf: 'default'
+ interface: 'Ethernet20'
+ local_address: '1.1.1.1'
+ enabled: True
+ transmit_interval: 50
+ receive_interval: 80
+ detect_multiplier: 4
+ passive_mode: True
+ echo_interval: 110
+ echo_mode: True
+ profile_name: 'p1'
+ multi_hops:
+ - remote_address: '192.40.1.3'
+ vrf: 'default'
+ local_address: '3.3.3.3'
+ enabled: True
+ transmit_interval: 75
+ receive_interval: 100
+ detect_multiplier: 3
+ passive_mode: True
+ min_ttl: 125
+ profile_name: 'p1'
+ state: merged
+
+# After state:
+# ------------
+#
+# sonic# show bfd profile
+# BFD Profile:
+# Profile-name: p1
+# Enabled: True
+# Echo-mode: Enabled
+# Passive-mode: Enabled
+# Minimum-Ttl: 140
+# Detect-multiplier: 2
+# Receive interval: 200ms
+# Transmission interval: 120ms
+# Echo transmission interval: 150ms
+# sonic# show bfd peers
+# BFD Peers:
+#
+# peer 192.40.1.3 multihop local-address 3.3.3.3 vrf default
+# ID: 989720421
+# Remote ID: 0
+# Passive mode: Enabled
+# Profile: p1
+# Minimum TTL: 125
+# Status: down
+# Downtime: 0 day(s), 0 hour(s), 1 min(s), 46 sec(s)
+# Diagnostics: ok
+# Remote diagnostics: ok
+# Peer Type: configured
+# Local timers:
+# Detect-multiplier: 2
+# Receive interval: 100ms
+# Transmission interval: 75ms
+# Echo transmission interval: ms
+# Remote timers:
+# Detect-multiplier: 3
+# Receive interval: 1000ms
+# Transmission interval: 1000ms
+# Echo transmission interval: 0ms
+#
+# peer 196.88.6.1 local-address 1.1.1.1 vrf default interface Ethernet20
+# ID: 1134635660
+# Remote ID: 0
+# Passive mode: Enabled
+# Profile: p1
+# Status: down
+# Downtime: 0 day(s), 1 hour(s), 50 min(s), 48 sec(s)
+# Diagnostics: ok
+# Remote diagnostics: ok
+# Peer Type: configured
+# Local timers:
+# Detect-multiplier: 4
+# Receive interval: 80ms
+# Transmission interval: 50ms
+# Echo transmission interval: 110ms
+# Remote timers:
+# Detect-multiplier: 3
+# Receive interval: 1000ms
+# Transmission interval: 1000ms
+# Echo transmission interval: 0ms
+#
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+# sonic# show bfd profile
+# BFD Profile:
+# Profile-name: p1
+# Enabled: True
+# Echo-mode: Enabled
+# Passive-mode: Enabled
+# Minimum-Ttl: 140
+# Detect-multiplier: 2
+# Receive interval: 200ms
+# Transmission interval: 120ms
+# Echo transmission interval: 150ms
+# Profile-name: p2
+# Enabled: True
+# Echo-mode: Disabled
+# Passive-mode: Disabled
+# Minimum-Ttl: 254
+# Detect-multiplier: 3
+# Receive interval: 300ms
+# Transmission interval: 300ms
+# Echo transmission interval: 300ms
+
+ - name: Replace BFD configuration
+ dellemc.enterprise_sonic.sonic_bfd:
+ config:
+ profiles:
+ - profile_name: 'p1'
+ transmit_interval: 144
+ - profile_name: 'p2'
+ enabled: False
+ transmit_interval: 110
+ receive_interval: 235
+ detect_multiplier: 5
+ passive_mode: True
+ min_ttl: 155
+ echo_interval: 163
+ echo_mode: True
+ state: replaced
+
+# After state:
+# ------------
+#
+# sonic# show bfd profile
+# BFD Profile:
+# Profile-name: p1
+# Enabled: True
+# Echo-mode: Enabled
+# Passive-mode: Enabled
+# Minimum-Ttl: 140
+# Detect-multiplier: 2
+# Receive interval: 200ms
+# Transmission interval: 144ms
+# Echo transmission interval: 150ms
+# Profile-name: p2
+# Enabled: False
+# Echo-mode: Enabled
+# Passive-mode: Enabled
+# Minimum-Ttl: 155
+# Detect-multiplier: 5
+# Receive interval: 235ms
+# Transmission interval: 110ms
+# Echo transmission interval: 163ms
+#
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+# sonic# show bfd peers
+# BFD Peers:
+#
+# peer 192.40.1.3 multihop local-address 3.3.3.3 vrf default
+# ID: 989720421
+# Remote ID: 0
+# Passive mode: Enabled
+# Profile: p1
+# Minimum TTL: 125
+# Status: down
+# Downtime: 0 day(s), 0 hour(s), 1 min(s), 46 sec(s)
+# Diagnostics: ok
+# Remote diagnostics: ok
+# Peer Type: configured
+# Local timers:
+# Detect-multiplier: 2
+# Receive interval: 100ms
+# Transmission interval: 75ms
+# Echo transmission interval: ms
+# Remote timers:
+# Detect-multiplier: 3
+# Receive interval: 1000ms
+# Transmission interval: 1000ms
+# Echo transmission interval: 0ms
+#
+# peer 196.88.6.1 local-address 1.1.1.1 vrf default interface Ethernet20
+# ID: 1134635660
+# Remote ID: 0
+# Passive mode: Enabled
+# Profile: p1
+# Status: down
+# Downtime: 0 day(s), 1 hour(s), 50 min(s), 48 sec(s)
+# Diagnostics: ok
+# Remote diagnostics: ok
+# Peer Type: configured
+# Local timers:
+# Detect-multiplier: 4
+# Receive interval: 80ms
+# Transmission interval: 50ms
+# Echo transmission interval: 110ms
+# Remote timers:
+# Detect-multiplier: 3
+# Receive interval: 1000ms
+# Transmission interval: 1000ms
+# Echo transmission interval: 0ms
+
+ - name: Override BFD configuration
+ dellemc.enterprise_sonic.sonic_bfd:
+ config:
+ single_hops:
+ - remote_address: '172.68.2.1'
+ vrf: 'default'
+ interface: 'Ethernet16'
+ local_address: '2.2.2.2'
+ enabled: True
+ transmit_interval: 60
+ receive_interval: 88
+ detect_multiplier: 6
+ passive_mode: True
+ echo_interval: 112
+ echo_mode: True
+ profile_name: 'p3'
+ multi_hops:
+ - remote_address: '186.42.1.2'
+ vrf: 'default'
+ local_address: '1.1.1.1'
+ enabled: False
+ transmit_interval: 85
+ receive_interval: 122
+ detect_multiplier: 4
+ passive_mode: False
+ min_ttl: 120
+ profile_name: 'p3'
+ state: overridden
+
+# After state:
+# ------------
+#
+# sonic# show bfd peers
+# BFD Peers:
+#
+# peer 186.42.1.2 multihop local-address 1.1.1.1 vrf default
+# ID: 989720421
+# Remote ID: 0
+# Passive mode: Disabled
+# Profile: p3
+# Minimum TTL: 120
+# Status: down
+# Downtime: 0 day(s), 0 hour(s), 1 min(s), 46 sec(s)
+# Diagnostics: ok
+# Remote diagnostics: ok
+# Peer Type: configured
+# Local timers:
+# Detect-multiplier: 4
+# Receive interval: 122ms
+# Transmission interval: 85ms
+# Echo transmission interval: ms
+# Remote timers:
+# Detect-multiplier: 3
+# Receive interval: 1000ms
+# Transmission interval: 1000ms
+# Echo transmission interval: 0ms
+#
+# peer 172.68.2.1 local-address 2.2.2.2 vrf default interface Ethernet16
+# ID: 1134635660
+# Remote ID: 0
+# Passive mode: Enabled
+# Profile: p3
+# Status: down
+# Downtime: 0 day(s), 1 hour(s), 50 min(s), 48 sec(s)
+# Diagnostics: ok
+# Remote diagnostics: ok
+# Peer Type: configured
+# Local timers:
+# Detect-multiplier: 6
+# Receive interval: 88ms
+# Transmission interval: 60ms
+# Echo transmission interval: 112ms
+# Remote timers:
+# Detect-multiplier: 3
+# Receive interval: 1000ms
+# Transmission interval: 1000ms
+# Echo transmission interval: 0ms
+#
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# sonic# show bfd profile
+# BFD Profile:
+# Profile-name: p1
+# Enabled: True
+# Echo-mode: Enabled
+# Passive-mode: Enabled
+# Minimum-Ttl: 140
+# Detect-multiplier: 2
+# Receive interval: 200ms
+# Transmission interval: 120ms
+# Echo transmission interval: 150ms
+# sonic# show bfd peers
+# BFD Peers:
+#
+# peer 192.40.1.3 multihop local-address 3.3.3.3 vrf default
+# ID: 989720421
+# Remote ID: 0
+# Passive mode: Enabled
+# Profile: p1
+# Minimum TTL: 125
+# Status: down
+# Downtime: 0 day(s), 0 hour(s), 1 min(s), 46 sec(s)
+# Diagnostics: ok
+# Remote diagnostics: ok
+# Peer Type: configured
+# Local timers:
+# Detect-multiplier: 2
+# Receive interval: 100ms
+# Transmission interval: 75ms
+# Echo transmission interval: ms
+# Remote timers:
+# Detect-multiplier: 3
+# Receive interval: 1000ms
+# Transmission interval: 1000ms
+# Echo transmission interval: 0ms
+#
+# peer 196.88.6.1 local-address 1.1.1.1 vrf default interface Ethernet20
+# ID: 1134635660
+# Remote ID: 0
+# Passive mode: Enabled
+# Profile: p1
+# Status: down
+# Downtime: 0 day(s), 1 hour(s), 50 min(s), 48 sec(s)
+# Diagnostics: ok
+# Remote diagnostics: ok
+# Peer Type: configured
+# Local timers:
+# Detect-multiplier: 4
+# Receive interval: 80ms
+# Transmission interval: 50ms
+# Echo transmission interval: 110ms
+# Remote timers:
+# Detect-multiplier: 3
+# Receive interval: 1000ms
+# Transmission interval: 1000ms
+# Echo transmission interval: 0ms
+
+ - name: Delete BFD configuration
+ dellemc.enterprise_sonic.sonic_bfd:
+ config:
+ profiles:
+ - profile_name: 'p1'
+ enabled: True
+ transmit_interval: 120
+ receive_interval: 200
+ detect_multiplier: 2
+ passive_mode: True
+ min_ttl: 140
+ echo_interval: 150
+ echo_mode: True
+ single_hops:
+ - remote_address: '196.88.6.1'
+ vrf: 'default'
+ interface: 'Ethernet20'
+ local_address: '1.1.1.1'
+ multi_hops:
+ - remote_address: '192.40.1.3'
+ vrf: 'default'
+ local_address: '3.3.3.3'
+ state: deleted
+
+# After state
+# -----------
+#
+# sonic# show bfd profile
+# BFD Profile:
+# Profile-name: p1
+# Enabled: True
+# Echo-mode: Disabled
+# Passive-mode: Disabled
+# Minimum-Ttl: 254
+# Detect-multiplier: 3
+# Receive interval: 300ms
+# Transmission interval: 300ms
+# Echo transmission interval: 300ms
+# sonic# show bfd peers
+# (No "bfd peers" configuration present)
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.bfd.bfd import BfdArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.bfd.bfd import Bfd
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=BfdArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Bfd(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp.py
index bc53ca40c..aaf52a40c 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-# © Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# © Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -143,13 +143,20 @@ options:
description:
- Allows comparing meds from different neighbors if set to true
type: bool
+ rt_delay:
+ description:
+ - Time in seconds to wait before processing route-map changes.
+ - Range is 0-600. 0 disables the timer and changes to route-map will not be updated.
+ type: int
state:
description:
- Specifies the operation to be performed on the BGP process that is configured on the device.
- In case of merged, the input configuration is merged with the existing BGP configuration on the device.
- In case of deleted, the existing BGP configuration is removed from the device.
+ - In case of replaced, the existing configuration of the specified BGP AS will be replaced with provided configuration.
+ - In case of overridden, the existing BGP configuration will be overridden with the provided configuration.
default: merged
- choices: ['merged', 'deleted']
+ choices: ['merged', 'deleted', 'replaced', 'overridden']
type: str
"""
EXAMPLES = """
@@ -158,30 +165,33 @@ EXAMPLES = """
# Before state:
# -------------
#
-#!
-#router bgp 10 vrf VrfCheck1
-# router-id 10.2.2.32
-# log-neighbor-changes
-#!
-#router bgp 11 vrf VrfCheck2
-# log-neighbor-changes
-# bestpath as-path ignore
-# bestpath med missing-as-worst confed
-# bestpath compare-routerid
-#!
-#router bgp 4
-# router-id 10.2.2.4
-# bestpath as-path ignore
-# bestpath as-path confed
-# bestpath med missing-as-worst confed
-# bestpath compare-routerid
-#!
+# !
+# router bgp 10 vrf VrfCheck1
+# router-id 10.2.2.32
+# route-map delay-timer 20
+# log-neighbor-changes
+# !
+# router bgp 11 vrf VrfCheck2
+# log-neighbor-changes
+# bestpath as-path ignore
+# bestpath med missing-as-worst confed
+# bestpath compare-routerid
+# !
+# router bgp 4
+# router-id 10.2.2.4
+# route-map delay-timer 10
+# bestpath as-path ignore
+# bestpath as-path confed
+# bestpath med missing-as-worst confed
+# bestpath compare-routerid
+# !
#
- name: Delete BGP Global attributes
dellemc.enterprise_sonic.sonic_bgp:
config:
- bgp_as: 4
router_id: 10.2.2.4
+ rt_delay: 10
log_neighbor_changes: False
bestpath:
as_path:
@@ -195,6 +205,7 @@ EXAMPLES = """
missing_as_worst: True
- bgp_as: 10
router_id: 10.2.2.32
+ rt_delay: 20
log_neighbor_changes: True
vrf_name: 'VrfCheck1'
- bgp_as: 11
@@ -215,18 +226,18 @@ EXAMPLES = """
# After state:
# ------------
#
-#!
-#router bgp 10 vrf VrfCheck1
-# log-neighbor-changes
-#!
-#router bgp 11 vrf VrfCheck2
-# log-neighbor-changes
-# bestpath compare-routerid
-#!
-#router bgp 4
-# log-neighbor-changes
-# bestpath compare-routerid
-#!
+# !
+# router bgp 10 vrf VrfCheck1
+# log-neighbor-changes
+# !
+# router bgp 11 vrf VrfCheck2
+# log-neighbor-changes
+# bestpath compare-routerid
+# !
+# router bgp 4
+# log-neighbor-changes
+# bestpath compare-routerid
+# !
# Using deleted
@@ -234,24 +245,26 @@ EXAMPLES = """
# Before state:
# -------------
#
-#!
-#router bgp 10 vrf VrfCheck1
-# router-id 10.2.2.32
-# log-neighbor-changes
-#!
-#router bgp 11 vrf VrfCheck2
-# log-neighbor-changes
-# bestpath as-path ignore
-# bestpath med missing-as-worst confed
-# bestpath compare-routerid
-#!
-#router bgp 4
-# router-id 10.2.2.4
-# bestpath as-path ignore
-# bestpath as-path confed
-# bestpath med missing-as-worst confed
-# bestpath compare-routerid
-#!
+# !
+# router bgp 10 vrf VrfCheck1
+# router-id 10.2.2.32
+# route-map delay-timer 20
+# log-neighbor-changes
+# !
+# router bgp 11 vrf VrfCheck2
+# log-neighbor-changes
+# bestpath as-path ignore
+# bestpath med missing-as-worst confed
+# bestpath compare-routerid
+# !
+# router bgp 4
+# router-id 10.2.2.4
+# route-map delay-timer 10
+# bestpath as-path ignore
+# bestpath as-path confed
+# bestpath med missing-as-worst confed
+# bestpath compare-routerid
+# !
- name: Deletes all the bgp global configurations
dellemc.enterprise_sonic.sonic_bgp:
@@ -261,8 +274,8 @@ EXAMPLES = """
# After state:
# ------------
#
-#!
-#!
+# !
+# !
# Using merged
@@ -270,16 +283,17 @@ EXAMPLES = """
# Before state:
# -------------
#
-#!
-#router bgp 4
-# router-id 10.1.1.4
-#!
+# !
+# router bgp 4
+# router-id 10.1.1.4
+# !
#
- name: Merges provided configuration with device configuration
dellemc.enterprise_sonic.sonic_bgp:
config:
- bgp_as: 4
router_id: 10.2.2.4
+ rt_delay: 10
log_neighbor_changes: False
timers:
holdtime: 20
@@ -301,6 +315,7 @@ EXAMPLES = """
med_val: 7878
- bgp_as: 10
router_id: 10.2.2.32
+ rt_delay: 20
log_neighbor_changes: True
vrf_name: 'VrfCheck1'
- bgp_as: 11
@@ -320,28 +335,172 @@ EXAMPLES = """
# After state:
# ------------
#
+# !
+# router bgp 10 vrf VrfCheck1
+# router-id 10.2.2.32
+# route-map delay-timer 20
+# log-neighbor-changes
+# !
+# router bgp 11 vrf VrfCheck2
+# log-neighbor-changes
+# bestpath as-path ignore
+# bestpath med missing-as-worst confed
+# bestpath compare-routerid
+# !
+# router bgp 4
+# router-id 10.2.2.4
+# route-map delay-timer 10
+# bestpath as-path ignore
+# bestpath as-path confed
+# bestpath med missing-as-worst confed
+# bestpath compare-routerid
+# always-compare-med
+# max-med on-startup 667 7878
+# timers 20 30
+#
+# !
+
+
+# Using replaced
+#
+# Before state:
+# -------------
+#
+#!
+#router bgp 10 vrf VrfCheck1
+# router-id 10.2.2.32
+# log-neighbor-changes
+# timers 60 180
+#!
+#router bgp 4
+# router-id 10.2.2.4
+# max-med on-startup 667 7878
+# bestpath as-path ignore
+# bestpath as-path confed
+# bestpath med missing-as-worst confed
+# bestpath compare-routerid
+# timers 20 30
+#!
+#
+
+- name: Replace device configuration of specified BGP AS with provided
+ dellemc.enterprise_sonic.sonic_bgp:
+ config:
+ - bgp_as: 4
+ router_id: 10.2.2.44
+ log_neighbor_changes: True
+ bestpath:
+ as_path:
+ confed: True
+ compare_routerid: True
+ - bgp_as: 11
+ vrf_name: 'VrfCheck2'
+ router_id: 10.2.2.33
+ log_neighbor_changes: True
+ bestpath:
+ as_path:
+ confed: True
+ ignore: True
+ compare_routerid: True
+ med:
+ confed: True
+ missing_as_worst: True
+ state: replaced
+
+#
+# After state:
+# ------------
+#
#!
#router bgp 10 vrf VrfCheck1
# router-id 10.2.2.32
# log-neighbor-changes
+# timers 60 180
#!
#router bgp 11 vrf VrfCheck2
+# router-id 10.2.2.33
# log-neighbor-changes
# bestpath as-path ignore
+# bestpath as-path confed
# bestpath med missing-as-worst confed
# bestpath compare-routerid
+# timers 60 180
+#!
+#router bgp 4
+# router-id 10.2.2.44
+# log-neighbor-changes
+# bestpath as-path confed
+# bestpath compare-routerid
+# timers 60 180
+#!
+
+
+# Using overridden
+#
+# Before state:
+# -------------
+#
+#!
+#router bgp 10 vrf VrfCheck1
+# router-id 10.2.2.32
+# log-neighbor-changes
+# timers 60 180
#!
#router bgp 4
# router-id 10.2.2.4
+# max-med on-startup 667 7878
# bestpath as-path ignore
# bestpath as-path confed
# bestpath med missing-as-worst confed
# bestpath compare-routerid
-# always-compare-med
-# max-med on-startup 667 7878
# timers 20 30
+#!
+#
+
+- name: Override device configuration of global BGP with provided configuration
+ dellemc.enterprise_sonic.sonic_bgp:
+ config:
+ - bgp_as: 4
+ router_id: 10.2.2.44
+ log_neighbor_changes: True
+ bestpath:
+ as_path:
+ confed: True
+ compare_routerid: True
+ - bgp_as: 11
+ vrf_name: 'VrfCheck2'
+ router_id: 10.2.2.33
+ log_neighbor_changes: True
+ bestpath:
+ as_path:
+ confed: True
+ ignore: True
+ compare_routerid: True
+ timers:
+ holdtime: 90
+ keepalive_interval: 30
+ state: overridden
+
+#
+# After state:
+# ------------
#
#!
+#router bgp 11 vrf VrfCheck2
+# router-id 10.2.2.33
+# log-neighbor-changes
+# bestpath as-path ignore
+# bestpath as-path confed
+# bestpath compare-routerid
+# timers 30 90
+#!
+#router bgp 4
+# router-id 10.2.2.44
+# log-neighbor-changes
+# bestpath as-path confed
+# bestpath compare-routerid
+# timers 60 180
+#!
"""
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_af.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_af.py
index 6d55355c9..af00093c6 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_af.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_af.py
@@ -172,13 +172,62 @@ options:
description:
- Specifies the count of the ebgp multipaths count.
type: int
+ rd:
+ description:
+ - Specifies the route distiguisher to be used by the VRF instance.
+ type: str
+ rt_in:
+ description:
+ - Route-targets to be imported.
+ type: list
+ elements: str
+ rt_out:
+ description:
+ - Route-targets to be exported.
+ type: list
+ elements: str
+ vnis:
+ description:
+ - VNI configuration for the EVPN.
+ type: list
+ elements: dict
+ suboptions:
+ vni_number:
+ description:
+ - Specifies the VNI number.
+ type: int
+ required: True
+ advertise_default_gw:
+ description:
+ - Specifies the advertise default gateway flag.
+ type: bool
+ advertise_svi_ip:
+ description:
+ - Enables advertise SVI MACIP routes
+ type: bool
+ rd:
+ description:
+ - Specifies the route distiguisher to be used by the VRF instance.
+ type: str
+ rt_in:
+ description:
+ - Route-targets to be imported.
+ type: list
+ elements: str
+ rt_out:
+ description:
+ - Route-targets to be exported.
+ type: list
+ elements: str
state:
description:
- Specifies the operation to be performed on the BGP_AF process configured on the device.
- In case of merged, the input configuration is merged with the existing BGP_AF configuration on the device.
- In case of deleted, the existing BGP_AF configuration is removed from the device.
+ - In case of replaced, the existing BGP_AF of specified BGP AS will be replaced with provided configuration.
+ - In case of overridden, the existing BGP_AF configuration will be overridden with the provided configuration.
default: merged
- choices: ['merged', 'deleted']
+ choices: ['merged', 'deleted', 'overridden', 'replaced']
type: str
"""
EXAMPLES = """
@@ -208,8 +257,17 @@ EXAMPLES = """
# address-family l2vpn evpn
# advertise-svi-ip
# advertise ipv6 unicast route-map aa
+# rd 3.3.3.3:33
+# route-target import 22:22
+# route-target export 33:33
# advertise-pip ip 1.1.1.1 peer-ip 2.2.2.2
-#!
+# !
+# vni 1
+# advertise-default-gw
+# advertise-svi-ip
+# rd 5.5.5.5:55
+# route-target import 88:88
+# route-target export 77:77
#
- name: Delete BGP Address family configuration from the device
dellemc.enterprise_sonic.sonic_bgp_af:
@@ -228,6 +286,13 @@ EXAMPLES = """
route_advertise_list:
- advertise_afi: ipv6
route_map: aa
+ rd: "3.3.3.3:33"
+ rt_in:
+ - "22:22"
+ rt_out:
+ - "33:33"
+ vnis:
+ - vni_number: 1
- afi: ipv4
safi: unicast
- afi: ipv6
@@ -320,6 +385,20 @@ EXAMPLES = """
route_advertise_list:
- advertise_afi: ipv4
route_map: bb
+ rd: "1.1.1.1:11"
+ rt_in:
+ - "12:12"
+ rt_out:
+ - "13:13"
+ vnis:
+ - vni_number: 1
+ advertise_default_gw: True
+ advertise_svi_ip: True
+ rd: "5.5.5.5:55"
+ rt_in:
+ - "88:88"
+ rt_out:
+ - "77:77"
- afi: ipv4
safi: unicast
network:
@@ -366,8 +445,300 @@ EXAMPLES = """
# address-family l2vpn evpn
# advertise-svi-ip
# advertise ipv4 unicast route-map bb
+# rd 1.1.1.1:11
+# route-target import 12:12
+# route-target import 13:13
# advertise-pip ip 3.3.3.3 peer-ip 4.4.4.4
+# !
+# vni 1
+# advertise-default-gw
+# advertise-svi-ip
+# rd 5.5.5.5:55
+# route-target import 88:88
+# route-target export 77:77
+#
+
+
+# Using replaced
#
+# Before state:
+# -------------
+#
+#do show running-configuration bgp
+#!
+#router bgp 52 vrf VrfReg1
+# log-neighbor-changes
+# timers 60 180
+# !
+# address-family ipv4 unicast
+# maximum-paths 1
+# maximum-paths ibgp 1
+# network 3.3.3.3/16
+# dampening
+#!
+#router bgp 51
+# router-id 111.2.2.41
+# log-neighbor-changes
+# timers 60 180
+# !
+# address-family ipv4 unicast
+# redistribute connected route-map bb metric 21
+# redistribute ospf route-map bb metric 27
+# maximum-paths 1
+# maximum-paths ibgp 1
+# network 2.2.2.2/16
+# network 192.168.10.1/32
+# dampening
+# !
+# address-family ipv6 unicast
+# redistribute static route-map aa metric 26
+# maximum-paths 4
+# maximum-paths ibgp 5
+# !
+# address-family l2vpn evpn
+# advertise-all-vni
+# advertise-svi-ip
+# advertise ipv4 unicast route-map bb
+# rd 1.1.1.1:11
+# route-target import 12:12
+# route-target export 13:13
+# dup-addr-detection
+# advertise-pip ip 3.3.3.3 peer-ip 4.4.4.4
+# !
+# vni 1
+# advertise-default-gw
+# advertise-svi-ip
+# rd 5.5.5.5:55
+# route-target import 88:88
+# route-target export 77:77
+
+- name: Replace device configuration of address families of specified BGP AS with provided configuration.
+ dellemc.enterprise_sonic.sonic_bgp_af:
+ config:
+ - bgp_as: 51
+ address_family:
+ afis:
+ - afi: l2vpn
+ safi: evpn
+ advertise_pip: True
+ advertise_pip_ip: "3.3.3.3"
+ advertise_pip_peer_ip: "4.4.4.4"
+ advertise_svi_ip: True
+ advertise_all_vni: False
+ advertise_default_gw: False
+ route_advertise_list:
+ - advertise_afi: ipv4
+ route_map: bb
+ rd: "1.1.1.1:11"
+ rt_in:
+ - "22:22"
+ rt_out:
+ - "13:13"
+ vnis:
+ - vni_number: 5
+ advertise_default_gw: True
+ advertise_svi_ip: True
+ rd: "10.10.10.10:55"
+ rt_in:
+ - "88:88"
+ rt_out:
+ - "77:77"
+ - afi: ipv4
+ safi: unicast
+ network:
+ - 2.2.2.2/16
+ - 192.168.10.1/32
+ dampening: True
+ redistribute:
+ - protocol: connected
+ - protocol: ospf
+ metric: 30
+ state: replaced
+
+# After state:
+# ------------
+#
+#do show running-configuration bgp
+#!
+#router bgp 52 vrf VrfReg1
+# log-neighbor-changes
+# timers 60 180
+# !
+# address-family ipv4 unicast
+# maximum-paths 1
+# maximum-paths ibgp 1
+# network 3.3.3.3/16
+# dampening
+#!
+#router bgp 51
+# router-id 111.2.2.41
+# log-neighbor-changes
+# timers 60 180
+# !
+# address-family ipv4 unicast
+# redistribute connected
+# redistribute ospf metric 30
+# maximum-paths 1
+# maximum-paths ibgp 1
+# network 2.2.2.2/16
+# network 192.168.10.1/32
+# dampening
+# !
+# address-family l2vpn evpn
+# advertise-all-vni
+# advertise-svi-ip
+# advertise ipv4 unicast route-map bb
+# rd 1.1.1.1:11
+# route-target import 22:22
+# route-target export 13:13
+# dup-addr-detection
+# advertise-pip ip 3.3.3.3 peer-ip 4.4.4.4
+# !
+# vni 5
+# advertise-default-gw
+# advertise-svi-ip
+# rd 10.10.10.10:55
+# route-target import 88:88
+# route-target export 77:77
+
+
+# Using overridden
+#
+# Before state:
+# -------------
+#
+#do show running-configuration bgp
+#!
+#router bgp 52 vrf VrfReg1
+# log-neighbor-changes
+# timers 60 180
+# !
+# address-family ipv4 unicast
+# maximum-paths 1
+# maximum-paths ibgp 1
+# network 3.3.3.3/16
+# dampening
+#!
+#router bgp 51
+# router-id 111.2.2.41
+# log-neighbor-changes
+# timers 60 180
+# !
+# address-family ipv4 unicast
+# redistribute connected route-map bb metric 21
+# redistribute ospf route-map bb metric 27
+# maximum-paths 1
+# maximum-paths ibgp 1
+# network 2.2.2.2/16
+# network 192.168.10.1/32
+# dampening
+# !
+# address-family ipv6 unicast
+# redistribute static route-map aa metric 26
+# maximum-paths 4
+# maximum-paths ibgp 5
+# !
+# address-family l2vpn evpn
+# advertise-all-vni
+# advertise-svi-ip
+# advertise ipv4 unicast route-map bb
+# rd 1.1.1.1:11
+# route-target import 12:12
+# route-target export 13:13
+# dup-addr-detection
+# advertise-pip ip 3.3.3.3 peer-ip 4.4.4.4
+# !
+# vni 1
+# advertise-default-gw
+# advertise-svi-ip
+# rd 5.5.5.5:55
+# route-target import 88:88
+# route-target export 77:77
+
+- name: Override device configuration of BGP address families with provided configuration.
+ dellemc.enterprise_sonic.sonic_bgp_af:
+ config:
+ - bgp_as: 51
+ address_family:
+ afis:
+ - afi: l2vpn
+ safi: evpn
+ advertise_pip: True
+ advertise_pip_ip: "3.3.3.3"
+ advertise_pip_peer_ip: "4.4.4.4"
+ advertise_svi_ip: True
+ advertise_all_vni: False
+ advertise_default_gw: False
+ route_advertise_list:
+ - advertise_afi: ipv4
+ route_map: bb
+ rd: "1.1.1.1:11"
+ rt_in:
+ - "22:22"
+ rt_out:
+ - "13:13"
+ vnis:
+ - vni_number: 5
+ advertise_default_gw: True
+ advertise_svi_ip: True
+ rd: "10.10.10.10:55"
+ rt_in:
+ - "88:88"
+ rt_out:
+ - "77:77"
+ - afi: ipv4
+ safi: unicast
+ network:
+ - 2.2.2.2/16
+ - 192.168.10.1/32
+ dampening: True
+ redistribute:
+ - protocol: connected
+ - protocol: ospf
+ metric: 30
+ state: overridden
+
+# After state:
+# ------------
+#
+#do show running-configuration bgp
+#!
+#router bgp 52 vrf VrfReg1
+# log-neighbor-changes
+# timers 60 180
+#!
+#router bgp 51
+# router-id 111.2.2.41
+# log-neighbor-changes
+# timers 60 180
+# !
+# address-family ipv4 unicast
+# redistribute connected
+# redistribute ospf metric 30
+# maximum-paths 1
+# maximum-paths ibgp 1
+# network 2.2.2.2/16
+# network 192.168.10.1/32
+# dampening
+# !
+# address-family l2vpn evpn
+# advertise-all-vni
+# advertise-svi-ip
+# advertise ipv4 unicast route-map bb
+# rd 1.1.1.1:11
+# route-target import 22:22
+# route-target export 13:13
+# dup-addr-detection
+# advertise-pip ip 3.3.3.3 peer-ip 4.4.4.4
+# !
+# vni 5
+# advertise-default-gw
+# advertise-svi-ip
+# rd 10.10.10.10:55
+# route-target import 88:88
+# route-target export 77:77
+
+
"""
RETURN = """
before:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_as_paths.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_as_paths.py
index bd2ff74a1..9bc3f43f5 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_as_paths.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_as_paths.py
@@ -62,7 +62,8 @@ options:
required: False
type: bool
description:
- - Permits or denies this as path.
+ - Permits or denies this as-path.
+ - Default value while adding a new as-path-list is C(False).
state:
description:
- The state of the configuration after module completion.
@@ -70,6 +71,8 @@ options:
choices:
- merged
- deleted
+ - replaced
+ - overridden
default: merged
"""
EXAMPLES = """
@@ -83,21 +86,21 @@ EXAMPLES = """
# action: permit
# members: 808.*,909.*
-- name: Delete BGP as path list
- dellemc.enterprise_sonic.sonic_bgp_as_paths:
- config:
- - name: test
- members:
- - 909.*
- permit: true
- state: deleted
+ - name: Delete BGP as path list
+ dellemc.enterprise_sonic.sonic_bgp_as_paths:
+ config:
+ - name: test
+ members:
+ - 909.*
+ permit: true
+ state: deleted
# After state:
# ------------
#
# show bgp as-path-access-list
# AS path list test:
-# action:
+# action: permit
# members: 808.*
@@ -114,12 +117,12 @@ EXAMPLES = """
# action: deny
# members: 608.*,709.*
-- name: Deletes BGP as-path list
- dellemc.enterprise_sonic.sonic_bgp_as_paths:
- config:
- - name: test
- members:
- state: deleted
+ - name: Deletes BGP as-path list
+ dellemc.enterprise_sonic.sonic_bgp_as_paths:
+ config:
+ - name: test
+ members:
+ state: deleted
# After state:
# ------------
@@ -140,10 +143,10 @@ EXAMPLES = """
# action: permit
# members: 808.*,909.*
-- name: Deletes BGP as-path list
- dellemc.enterprise_sonic.sonic_bgp_as_paths:
- config:
- state: deleted
+ - name: Deletes BGP as-path list
+ dellemc.enterprise_sonic.sonic_bgp_as_paths:
+ config:
+ state: deleted
# After state:
# ------------
@@ -158,16 +161,16 @@ EXAMPLES = """
# -------------
#
# show bgp as-path-access-list
-# AS path list test:
+# (No bgp as-path-access-list configuration present)
-- name: Adds 909.* to test as-path list
- dellemc.enterprise_sonic.sonic_bgp_as_paths:
- config:
- - name: test
- members:
- - 909.*
- permit: true
- state: merged
+ - name: Create a BGP as-path list
+ dellemc.enterprise_sonic.sonic_bgp_as_paths:
+ config:
+ - name: test
+ members:
+ - 909.*
+ permit: true
+ state: merged
# After state:
# ------------
@@ -178,6 +181,78 @@ EXAMPLES = """
# members: 909.*
+# Using replaced
+
+# Before state:
+# -------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+# action: permit
+# members: 800.*,808.*
+# AS path list test1:
+# action: deny
+# members: 500.*
+
+ - name: Replace device configuration of specified BGP as-path lists with provided configuration
+ dellemc.enterprise_sonic.sonic_bgp_as_paths:
+ config:
+ - name: test
+ members:
+ - 900.*
+ - 901.*
+ permit: true
+ - name: test1
+ - name: test2
+ members:
+ - 100.*
+ permit: true
+ state: replaced
+
+# After state:
+# ------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+# action: permit
+# members: 900.*,901.*
+# AS path list test2:
+# action: permit
+# members: 100.*
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+# action: permit
+# members: 800.*,808.*
+# AS path list test1:
+# action: deny
+# members: 500.*
+
+ - name: Override device configuration of all BGP as-path lists with provided configuration
+ dellemc.enterprise_sonic.sonic_bgp_as_paths:
+ config:
+ - name: test
+ members:
+ - 900.*
+ - 901.*
+ permit: true
+ state: overridden
+
+# After state:
+# ------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+# action: permit
+# members: 900.*,901.*
+
+
"""
RETURN = """
before:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_communities.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_communities.py
index 08c8dcc7f..dd1c2b083 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_communities.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_communities.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -52,7 +52,7 @@ options:
required: True
type: str
description:
- - Name of the BGP communitylist.
+ - Name of the BGP community-list.
type:
type: str
description:
@@ -67,6 +67,7 @@ options:
type: bool
description:
- Permits or denies this community.
+ - Default value while adding a new community-list is C(False).
aann:
required: False
type: str
@@ -120,6 +121,8 @@ options:
choices:
- merged
- deleted
+ - replaced
+ - overridden
default: merged
"""
EXAMPLES = """
@@ -130,18 +133,21 @@ EXAMPLES = """
#
# show bgp community-list
# Standard community list test: match: ANY
-# 101
-# 201
-# Standard community list test1: match: ANY
-# 301
+# permit local-as
+# permit no-peer
+# Expanded community list test1: match: ANY
+# deny 101
+# deny 302
-- name: Deletes BGP community member
+- name: Delete a BGP community-list member
dellemc.enterprise_sonic.sonic_bgp_communities:
config:
- - name: test
+ - name: test1
+ type: expanded
+ permit: false
members:
regex:
- - 201
+ - 302
state: deleted
# After state:
@@ -149,9 +155,10 @@ EXAMPLES = """
#
# show bgp community-list
# Standard community list test: match: ANY
-# 101
-# Standard community list test1: match: ANY
-# 301
+# permit local-as
+# permit no-peer
+# Expanded community list test1: match: ANY
+# deny 101
# Using deleted
@@ -161,15 +168,17 @@ EXAMPLES = """
#
# show bgp community-list
# Standard community list test: match: ANY
-# 101
+# permit local-as
+# permit no-peer
# Expanded community list test1: match: ANY
-# 201
+# deny 101
+# deny 302
-- name: Deletes a single BGP community
+- name: Delete a single BGP community-list
dellemc.enterprise_sonic.sonic_bgp_communities:
config:
- name: test
- members:
+ type: standard
state: deleted
# After state:
@@ -177,7 +186,8 @@ EXAMPLES = """
#
# show bgp community-list
# Expanded community list test1: match: ANY
-# 201
+# deny 101
+# deny 302
# Using deleted
@@ -187,11 +197,13 @@ EXAMPLES = """
#
# show bgp community-list
# Standard community list test: match: ANY
-# 101
+# permit local-as
+# permit no-peer
# Expanded community list test1: match: ANY
-# 201
+# deny 101
+# deny 302
-- name: Delete All BGP communities
+- name: Delete All BGP community-lists
dellemc.enterprise_sonic.sonic_bgp_communities:
config:
state: deleted
@@ -210,14 +222,17 @@ EXAMPLES = """
#
# show bgp community-list
# Standard community list test: match: ANY
-# 101
+# permit local-as
+# permit no-peer
# Expanded community list test1: match: ANY
-# 201
+# deny 101
+# deny 302
-- name: Deletes all members in a single BGP community
+- name: Delete all members in a single BGP community-list
dellemc.enterprise_sonic.sonic_bgp_communities:
config:
- - name: test
+ - name: test1
+ type: expanded
members:
regex:
state: deleted
@@ -226,9 +241,9 @@ EXAMPLES = """
# ------------
#
# show bgp community-list
-# Expanded community list test: match: ANY
-# Expanded community list test1: match: ANY
-# 201
+# Standard community list test: match: ANY
+# permit local-as
+# permit no-peer
# Using merged
@@ -236,23 +251,105 @@ EXAMPLES = """
# Before state:
# -------------
#
-# show bgp as-path-access-list
-# AS path list test:
+# show bgp community-list
+# Expanded community list test1: match: ANY
+# permit 101
+# permit 302
-- name: Adds 909.* to test as-path list
- dellemc.enterprise_sonic.sonic_bgp_as_paths:
+- name: Add a new BGP community-list
+ dellemc.enterprise_sonic.sonic_bgp_communities:
config:
- - name: test
+ - name: test2
+ type: expanded
+ permit: true
members:
- - 909.*
+ regex:
+ - 909
state: merged
# After state:
# ------------
#
-# show bgp as-path-access-list
-# AS path list test:
-# members: 909.*
+# show bgp community-list
+# Expanded community list test1: match: ANY
+# permit 101
+# permit 302
+# Expanded community list test2: match: ANY
+# permit 909
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# show bgp community-list
+# Standard community list test: match: ANY
+# permit local-as
+# permit no-peer
+# Expanded community list test1: match: ANY
+# deny 101
+# deny 302
+
+- name: Replacing a single BGP community-list
+ dellemc.enterprise_sonic.sonic_bgp_communities:
+ config:
+ - name: test
+ type: expanded
+ members:
+ regex:
+ - 301
+ - name: test3
+ type: standard
+ no_advertise: true
+ no_peer: true
+ permit: false
+ match: ALL
+ state: replaced
+
+# After state:
+# ------------
+#
+# show bgp community-list
+# Expanded community list test: match: ANY
+# deny 301
+# Expanded community list test1: match: ANY
+# deny 101
+# deny 302
+# Standard community list test3: match: ALL
+# deny no-advertise
+# deny no-peer
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# show bgp community-list
+# Standard community list test: match: ANY
+# permit local-as
+# permit no-peer
+# Expanded community list test1: match: ANY
+# deny 101
+# deny 302
+
+- name: Override entire BGP community-lists
+ dellemc.enterprise_sonic.sonic_bgp_communities:
+ config:
+ - name: test3
+ type: expanded
+ members:
+ regex:
+ - 301
+ state: overridden
+
+# After state:
+# ------------
+#
+# show bgp community-list
+# Expanded community list test3: match: ANY
+# deny 301
"""
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_ext_communities.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_ext_communities.py
index c2af0c488..49a30c9f9 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_ext_communities.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_ext_communities.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -66,6 +66,7 @@ options:
type: bool
description:
- Permits or denies this community.
+ - Default value while adding a new ext-community-list is False.
members:
required: False
type: dict
@@ -106,6 +107,8 @@ options:
choices:
- merged
- deleted
+ - replaced
+ - overridden
default: merged
"""
EXAMPLES = """
@@ -116,15 +119,16 @@ EXAMPLES = """
#
# show bgp ext-community-list
# Standard extended community list test: match: ANY
-# rt:101:101
-# rt:201:201
+# permit rt:101:101
+# permit rt:201:201
- name: Deletes a BGP ext community member
dellemc.enterprise_sonic.sonic_bgp_ext_communities:
config:
- name: test
+ type: standard
members:
- regex:
+ route_target:
- 201:201
state: deleted
@@ -133,7 +137,7 @@ EXAMPLES = """
#
# show bgp ext-community-list
# Standard extended community list test: match: ANY
-# rt:101:101
+# permit rt:101:101
#
@@ -144,9 +148,10 @@ EXAMPLES = """
#
# show bgp ext-community-list
# Standard extended community list test: match: ANY
-# 101
-# Expanded extended community list test1: match: ANY
-# 201
+# permit rt:101:101
+# permit rt:201:201
+# Expanded extended community list test1: match: ALL
+# deny 101:102
- name: Deletes a single BGP extended community
dellemc.enterprise_sonic.sonic_bgp_ext_communities:
@@ -160,7 +165,8 @@ EXAMPLES = """
#
# show bgp ext-community-list
# Standard extended community list test: match: ANY
-# 101
+# permit rt:101:101
+# permit rt:201:201
#
@@ -171,9 +177,10 @@ EXAMPLES = """
#
# show bgp ext-community-list
# Standard extended community list test: match: ANY
-# 101
-# Expanded extended community list test1: match: ANY
-# 201
+# permit rt:101:101
+# permit rt:201:201
+# Expanded extended community list test1: match: ALL
+# deny 101:102
- name: Deletes all BGP extended communities
dellemc.enterprise_sonic.sonic_bgp_ext_communities:
@@ -194,9 +201,10 @@ EXAMPLES = """
#
# show bgp ext-community-list
# Standard extended community list test: match: ANY
-# 101
-# Expanded extended community list test1: match: ANY
-# 201
+# permit rt:101:101
+# permit rt:201:201
+# Expanded extended community list test1: match: ALL
+# deny 101:102
- name: Deletes all members in a single BGP extended community
dellemc.enterprise_sonic.sonic_bgp_ext_communities:
@@ -211,8 +219,8 @@ EXAMPLES = """
#
# show bgp ext-community-list
# Standard extended community list test: match: ANY
-# 101
-# Expanded extended community list test1: match: ANY
+# permit rt:101:101
+# permit rt:201:201
#
@@ -221,23 +229,108 @@ EXAMPLES = """
# Before state:
# -------------
#
-# show bgp as-path-access-list
-# AS path list test:
+# show bgp ext-community-list
+# Standard extended community list test: match: ANY
+# permit rt:101:101
+# permit rt:201:201
+# Expanded extended community list test1: match: ALL
+# deny 101:102
-- name: Adds 909.* to test as-path list
- dellemc.enterprise_sonic.sonic_bgp_as_paths:
+- name: Adds new community list
+ dellemc.enterprise_sonic.sonic_bgp_ext_communities:
config:
- - name: test
+ - name: test3
+ type: standard
+ match: any
+ permit: true
members:
- - 909.*
+ route_origin:
+ - "301:301"
+ - "401:401"
state: merged
# After state:
# ------------
#
-# show bgp as-path-access-list
-# AS path list test:
-# members: 909.*
+# show bgp ext-community-list
+# Standard extended community list test: match: ANY
+# permit rt:101:101
+# permit rt:201:201
+# Expanded extended community list test1: match: ALL
+# deny 101:102
+# Standard extended community list test3: match: ANY
+# permit soo:301:301
+# permit soo:401:401
+
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# show bgp ext-community-list
+# Standard extended community list test: match: ANY
+# permit rt:101:101
+# permit rt:201:201
+# Expanded extended community list test1: match: ALL
+# deny 101:102
+
+- name: Replacing a single BGP extended community
+ dellemc.enterprise_sonic.sonic_bgp_ext_communities:
+ config:
+ - name: test
+ type: expanded
+ permit: true
+ match: all
+ members:
+ regex:
+ - 301:302
+ state: replaced
+
+# After state:
+# ------------
+#
+# show bgp ext-community-list
+# Expanded extended community list test: match: ALL
+# permit 301:302
+# Expanded extended community list test1: match: ALL
+# deny 101:102
+#
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# show bgp ext-community-list
+# Standard extended community list test: match: ANY
+# permit rt:101:101
+# permit rt:201:201
+# Expanded extended community list test1: match: ALL
+# deny 101:102
+
+
+- name: Override the entire list of BGP extended community
+ dellemc.enterprise_sonic.sonic_bgp_ext_communities:
+ config:
+ - name: test3
+ type: expanded
+ permit: true
+ match: all
+ members:
+ regex:
+ - 301:302
+ state: overridden
+
+# After state:
+# ------------
+#
+# show bgp ext-community-list
+# Expanded extended community list test3: match: ALL
+# permit 301:302
+#
"""
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors.py
index 19aeb6fc9..47a414b0a 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors.py
@@ -296,7 +296,7 @@ options:
default: False
prefix_limit:
description:
- - Specifies prefix limit attributes.
+ - Specifies prefix limit attributes for ipv4-unicast and ipv6-unicast.
type: dict
suboptions:
max_prefixes:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors_af.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors_af.py
index 10400cfe2..d3b23dfb2 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors_af.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors_af.py
@@ -127,7 +127,7 @@ options:
default: False
prefix_limit:
description:
- - Specifies prefix limit attributes.
+ - Specifies prefix limit attributes for ipv4-unicast and ipv6-unicast.
type: dict
suboptions:
max_prefixes:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_config.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_config.py
index dd054419f..96c0ee1ba 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_config.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_config.py
@@ -318,7 +318,7 @@ def main():
if module.params['save']:
result['changed'] = True
if not module.check_mode:
- cmd = {r'command': ' write memory'}
+ cmd = {r'command': 'write memory'}
run_commands(module, [cmd])
result['saved'] = True
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_copp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_copp.py
new file mode 100644
index 000000000..e4e7d358a
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_copp.py
@@ -0,0 +1,295 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_copp
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'
+}
+
+DOCUMENTATION = """
+---
+module: sonic_copp
+version_added: "2.1.0"
+short_description: Manage CoPP configuration on SONiC
+description:
+ - This module provides configuration management of CoPP for devices running SONiC
+author: "Shade Talabi (@stalabi1)"
+options:
+ config:
+ description:
+ - Specifies CoPP configurations
+ type: dict
+ suboptions:
+ copp_groups:
+ description:
+ - List of CoPP entries that comprise a CoPP group
+ type: list
+ elements: dict
+ suboptions:
+ copp_name:
+ description:
+ - Name of CoPP classifier
+ type: str
+ required: True
+ trap_priority:
+ description:
+ - CoPP trap priority
+ type: int
+ trap_action:
+ description:
+ - CoPP trap action
+ type: str
+ queue:
+ description:
+ - CoPP queue ID
+ type: int
+ cir:
+ description:
+ - Committed information rate in bps or pps (packets per second)
+ type: str
+ cbs:
+ description:
+ - Committed bucket size in packets or bytes
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion
+ type: str
+ choices: ['merged', 'deleted', 'replaced', 'overridden']
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+#
+# Before state:
+# -------------
+#
+# sonic# show copp actions
+# (No "copp actions" configuration present)
+
+ - name: Merge CoPP groups configuration
+ dellemc.enterprise_sonic.sonic_copp:
+ config:
+ copp_groups:
+ - copp_name: 'copp-1'
+ trap_priority: 1
+ trap_action: 'DROP'
+ queue: 1
+ cir: '45'
+ cbs: '45'
+ - copp_name: 'copp-2'
+ trap_priority: 2
+ trap_action: 'FORWARD'
+ queue: 2
+ cir: '90'
+ cbs: '90'
+ state: merged
+
+# After state:
+# ------------
+#
+# sonic# show copp actions
+# CoPP action group copp-1
+# trap-action drop
+# trap-priority 1
+# trap-queue 1
+# police cir 45 cbs 45
+# CoPP action group copp-2
+# trap-action forward
+# trap-priority 2
+# trap-queue 2
+# police cir 90 cbs 90
+#
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+# sonic# show copp actions
+# CoPP action group copp-1
+# trap-action drop
+# trap-priority 1
+# trap-queue 1
+# police cir 45 cbs 45
+
+ - name: Replace CoPP groups configuration
+ dellemc.enterprise_sonic.sonic_copp:
+ config:
+ copp_groups:
+ - copp_name: 'copp-1'
+ trap_priority: 2
+ trap_action: 'FORWARD'
+ queue: 2
+ - copp_name: 'copp-3'
+ trap_priority: 3
+ trap_action: 'DROP'
+ queue: 3
+ cir: '1000'
+ cbs: '1000'
+ state: replaced
+
+# After state:
+# ------------
+#
+# sonic# show copp actions
+# CoPP action group copp-1
+# trap-action forward
+# trap-priority 2
+# trap-queue 2
+# CoPP action group copp-3
+# trap-action drop
+# trap-priority 3
+# trap-queue 3
+# police cir 1000 cbs 1000
+#
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+# sonic# show copp actions
+# CoPP action group copp-1
+# trap-action forward
+# trap-priority 2
+# trap-queue 2
+# CoPP action group copp-3
+# trap-action drop
+# trap-priority 3
+# trap-queue 3
+# police cir 1000 cbs 1000
+
+ - name: Override CoPP groups configuration
+ dellemc.enterprise_sonic.sonic_copp:
+ config:
+ copp_groups:
+ - copp_name: 'copp-4'
+ trap_priority: 4
+ trap_action: 'FORWARD'
+ queue: 4
+ cir: 200
+ cbs: 200
+ state: overridden
+
+# After state:
+# ------------
+#
+# sonic# show copp actions
+# CoPP action group copp-4
+# trap-action forward
+# trap-priority 4
+# trap-queue 4
+# police cir 200 cbs 200
+#
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# sonic# show copp actions
+# CoPP action group copp-1
+# trap-action drop
+# trap-priority 1
+# trap-queue 1
+# police cir 45 cbs 45
+# CoPP action group copp-2
+# trap-action forward
+# trap-priority 2
+# trap-queue 2
+# police cir 90 cbs 90
+
+ - name: Delete CoPP groups configuration
+ dellemc.enterprise_sonic.sonic_copp:
+ config:
+ copp_groups:
+ - copp_name: 'copp-1'
+ trap_action: 'DROP'
+ cir: '45'
+ cbs: '45'
+ - copp_name: 'copp-2'
+ state: deleted
+
+# After state:
+# ------------
+#
+# sonic# show copp actions
+# CoPP action group copp-1
+# trap-action drop
+# police cir 45 cbs 45
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.copp.copp import CoppArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.copp.copp import Copp
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=CoppArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Copp(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_dhcp_relay.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_dhcp_relay.py
new file mode 100644
index 000000000..321a673eb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_dhcp_relay.py
@@ -0,0 +1,781 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_dhcp_relay
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_dhcp_relay
+version_added: '2.1.0'
+short_description: Manage DHCP and DHCPv6 relay configurations on SONiC
+description:
+ - This module provides configuration management of DHCP and DHCPv6 relay
+ parameters on Layer 3 interfaces of devices running SONiC.
+ - Layer 3 interface and VRF name need to be created earlier in the device.
+author: 'Arun Saravanan Balachandran (@ArunSaravananBalachandran)'
+options:
+ config:
+ description:
+ - Specifies the DHCP and DHCPv6 relay configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of the Layer 3 interface, i.e. Eth1/1.
+ type: str
+ required: true
+ ipv4:
+ description:
+ - DHCP relay configurations to be set for the interface mentioned in name option.
+ type: dict
+ suboptions:
+ server_addresses:
+ description:
+ - List of DHCP server IPv4 addresses.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPv4 address of the DHCP server.
+ type: str
+ vrf_name:
+ description:
+ - Specifies name of the VRF in which the DHCP server resides.
+ - This option is not used with state I(deleted).
+ type: str
+ source_interface:
+ description:
+ - Specifies the DHCP relay source interface.
+ type: str
+ max_hop_count:
+ description:
+ - Specifies the maximum hop count for DHCP relay packets.
+ - The range is from 1 to 16.
+ type: int
+ link_select:
+ description:
+ - Enable link selection suboption.
+ type: bool
+ vrf_select:
+ description:
+ - Enable VRF selection suboption.
+ type: bool
+ circuit_id:
+ description:
+ - Specifies the DHCP relay circuit-id format.
+ - C(%h:%p) - Hostname followed by interface name eg. sonic:Vlan100
+ - C(%i) - Name of the physical interface eg. Eth1/2
+ - C(%p) - Name of the interface eg. Vlan100
+ type: str
+ choices:
+ - '%h:%p'
+ - '%i'
+ - '%p'
+ policy_action:
+ description:
+ - Specifies the policy for handling of DHCP relay options.
+ type: str
+ choices:
+ - append
+ - discard
+ - replace
+ ipv6:
+ description:
+ - DHCPv6 relay configurations to be set for the interface mentioned in name option.
+ type: dict
+ suboptions:
+ server_addresses:
+ description:
+ - List of DHCPv6 server IPv6 addresses.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPv6 address of the DHCPv6 server.
+ type: str
+ vrf_name:
+ description:
+ - Specifies name of the VRF in which the DHCPv6 server resides.
+ - This option is used only with state I(merged).
+ type: str
+ source_interface:
+ description:
+ - Specifies the DHCPv6 relay source interface.
+ type: str
+ max_hop_count:
+ description:
+ - Specifies the maximum hop count for DHCPv6 relay packets.
+ - The range is from 1 to 16.
+ type: int
+ vrf_select:
+ description:
+ - Enable VRF selection suboption.
+ type: bool
+ state:
+ description:
+ - The state of the configuration after module completion.
+ - C(merged) - Merges provided DHCP and DHCPv6 relay configuration with on-device configuration.
+ - C(deleted) - Deletes on-device DHCP and DHCPv6 relay configuration.
+ - C(replaced) - Replaces on-device DHCP and DHCPv6 relay configuration of the specified interfaces with provided configuration.
+ - C(overridden) - Overrides all on-device DHCP and DHCPv6 relay configurations with the provided configuration.
+ type: str
+ choices:
+ - merged
+ - deleted
+ - replaced
+ - overridden
+ default: merged
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ip dhcp-relay 91.1.1.1 92.1.1.1 vrf VrfReg1
+# ip dhcp-relay max-hop-count 5
+# ip dhcp-relay vrf-select
+# ip dhcp-relay policy-action append
+# ipv6 address 81::1/24
+# ipv6 dhcp-relay 91::1 92::1
+# ipv6 dhcp-relay max-hop-count 5
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 71.1.1.1 72.1.1.1 73.1.1.1
+# ip dhcp-relay source-interface Vlan100
+# ip dhcp-relay link-select
+# ip dhcp-relay circuit-id %h:%p
+# !
+
+ - name: Delete DHCP and DHCPv6 relay configurations
+ dellemc.enterprise_sonic.sonic_dhcp_relay:
+ config:
+ - name: 'Eth1/1'
+ ipv4:
+ server_addresses:
+ - address: '92.1.1.1'
+ vrf_select: true
+ max_hop_count: 5
+ ipv6:
+ server_addresses:
+ - address: '91::1'
+ - address: '92::1'
+ - name: 'Eth1/2'
+ ipv4:
+ server_addresses:
+ - address: '71.1.1.1'
+ - address: '72.1.1.1'
+ source_interface: 'Vlan100'
+ link_select: true
+ circuit_id: '%h:%p'
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ip dhcp-relay 91.1.1.1 vrf VrfReg1
+# ip dhcp-relay policy-action append
+# ipv6 address 81::1/24
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 73.1.1.1
+# !
+
+
+# Using deleted
+#
+# NOTE: Support is provided in the dhcp_relay resource module for deletion of all attributes for a
+# given address family (IPv4 or IPv6) by using a "special" YAML sequence specifying a server address list
+# containing a single "blank" IP address under the target address family. The following example shows
+# a task using this syntax for deletion of all DHCP (IPv4) configurations for an interface, but the
+# equivalent syntax is supported for DHCPv6 (IPv6) as well.
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ip dhcp-relay 91.1.1.1 92.1.1.1 vrf VrfReg1
+# ip dhcp-relay max-hop-count 5
+# ip dhcp-relay vrf-select
+# ip dhcp-relay policy-action append
+# ipv6 address 81::1/24
+# ipv6 dhcp-relay 91::1 92::1
+# ipv6 dhcp-relay max-hop-count 5
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 71.1.1.1 72.1.1.1 73.1.1.1
+# ip dhcp-relay source-interface Vlan100
+# ip dhcp-relay link-select
+# ip dhcp-relay circuit-id %h:%p
+# !
+
+ - name: Delete all IPv4 DHCP relay configurations for interface Eth1/1
+ dellemc.enterprise_sonic.sonic_dhcp_relay:
+ config:
+ - name: 'Eth1/1'
+ ipv4:
+ server_addresses:
+ - address:
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ipv6 address 81::1/24
+# ipv6 dhcp-relay 91::1 92::1
+# ipv6 dhcp-relay max-hop-count 5
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 71.1.1.1 72.1.1.1 73.1.1.1
+# ip dhcp-relay source-interface Vlan100
+# ip dhcp-relay link-select
+# ip dhcp-relay circuit-id %h:%p
+# !
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ip dhcp-relay 91.1.1.1 92.1.1.1 vrf VrfReg1
+# ip dhcp-relay max-hop-count 5
+# ip dhcp-relay vrf-select
+# ip dhcp-relay policy-action append
+# ipv6 address 81::1/24
+# ipv6 dhcp-relay 91::1 92::1
+# ipv6 dhcp-relay max-hop-count 5
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 71.1.1.1 72.1.1.1 73.1.1.1
+# ip dhcp-relay source-interface Vlan100
+# ip dhcp-relay link-select
+# ip dhcp-relay circuit-id %h:%p
+# !
+
+ - name: Delete all DHCP and DHCPv6 relay configurations for interface Eth1/1
+ dellemc.enterprise_sonic.sonic_dhcp_relay:
+ config:
+ - name: 'Eth1/1'
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ipv6 address 81::1/24
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 71.1.1.1 72.1.1.1 73.1.1.1
+# ip dhcp-relay source-interface Vlan100
+# ip dhcp-relay link-select
+# ip dhcp-relay circuit-id %h:%p
+# !
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ip dhcp-relay 91.1.1.1 92.1.1.1 vrf VrfReg1
+# ip dhcp-relay max-hop-count 5
+# ip dhcp-relay vrf-select
+# ip dhcp-relay policy-action append
+# ipv6 address 81::1/24
+# ipv6 dhcp-relay 91::1 92::1
+# ipv6 dhcp-relay max-hop-count 5
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 71.1.1.1 72.1.1.1 73.1.1.1
+# ip dhcp-relay source-interface Vlan100
+# ip dhcp-relay link-select
+# ip dhcp-relay circuit-id %h:%p
+# !
+
+ - name: Delete all DHCP and DHCPv6 relay configurations
+ dellemc.enterprise_sonic.sonic_dhcp_relay:
+ config:
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ipv6 address 81::1/24
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# !
+
+
+# Using merged
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ipv6 address 81::1/24
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 71.1.1.1 72.1.1.1
+# !
+
+ - name: Add DHCP and DHCPv6 relay configurations
+ dellemc.enterprise_sonic.sonic_dhcp_relay:
+ config:
+ - name: 'Eth1/1'
+ ipv4:
+ server_addresses:
+ - address: '91.1.1.1'
+ - address: '92.1.1.1'
+ vrf_name: 'VrfReg1'
+ vrf_select: true
+ max_hop_count: 5
+ policy_action: 'append'
+ ipv6:
+ server_addresses:
+ - address: '91::1'
+ - address: '92::1'
+ max_hop_count: 5
+ - name: 'Eth1/2'
+ ipv4:
+ server_addresses:
+ - address: '73.1.1.1'
+ source_interface: 'Vlan100'
+ link_select: true
+ circuit_id: '%h:%p'
+ state: merged
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ip dhcp-relay 91.1.1.1 92.1.1.1 vrf VrfReg1
+# ip dhcp-relay max-hop-count 5
+# ip dhcp-relay vrf-select
+# ip dhcp-relay policy-action append
+# ipv6 address 81::1/24
+# ipv6 dhcp-relay 91::1 92::1
+# ipv6 dhcp-relay max-hop-count 5
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 71.1.1.1 72.1.1.1 73.1.1.1
+# ip dhcp-relay source-interface Vlan100
+# ip dhcp-relay link-select
+# ip dhcp-relay circuit-id %h:%p
+# !
+
+
+# Using replaced
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ip dhcp-relay 91.1.1.1 92.1.1.1 vrf VrfReg1
+# ip dhcp-relay max-hop-count 5
+# ip dhcp-relay vrf-select
+# ip dhcp-relay policy-action append
+# ipv6 address 81::1/24
+# ipv6 dhcp-relay 91::1 92::1
+# ipv6 dhcp-relay max-hop-count 5
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 71.1.1.1 72.1.1.1 73.1.1.1
+# ip dhcp-relay source-interface Vlan100
+# ip dhcp-relay link-select
+# ip dhcp-relay circuit-id %h:%p
+# ipv6 address 61::1/24
+# ipv6 dhcp-relay 71::1 72::1
+# !
+# interface Eth1/3
+# mtu 9100
+# speed 400000
+# fec RS
+# shutdown
+# ip address 41.1.1.1/24
+# ip dhcp-relay 51.1.1.1 52.1.1.1
+# ip dhcp-relay circuit-id %h:%p
+# ipv6 address 41::1/24
+# ipv6 dhcp-relay 51::1 52::1
+# !
+
+ - name: Replace DHCP and DHCPv6 relay configurations of specified interfaces
+ dellemc.enterprise_sonic.sonic_dhcp_relay:
+ config:
+ - name: 'Eth1/1'
+ ipv4:
+ server_addresses:
+ - address: '91.1.1.1'
+ - address: '93.1.1.1'
+ - address: '95.1.1.1'
+ vrf_name: 'VrfReg1'
+ vrf_select: true
+ ipv6:
+ server_addresses:
+ - address: '93::1'
+ - address: '94::1'
+ source_interface: 'Vlan100'
+ - name: 'Eth1/2'
+ ipv4:
+ server_addresses:
+ - address: '73.1.1.1'
+ circuit_id: '%h:%p'
+ state: replaced
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ip dhcp-relay 91.1.1.1 93.1.1.1 95.1.1.1 vrf VrfReg1
+# ip dhcp-relay vrf-select
+# ipv6 address 81::1/24
+# ipv6 dhcp-relay 93::1 94::1
+# ipv6 dhcp-relay source-interface Vlan100
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 73.1.1.1
+# ip dhcp-relay circuit-id %h:%p
+# ipv6 address 61::1/24
+# !
+# interface Eth1/3
+# mtu 9100
+# speed 400000
+# fec RS
+# shutdown
+# ip address 41.1.1.1/24
+# ip dhcp-relay 51.1.1.1 52.1.1.1
+# ip dhcp-relay circuit-id %h:%p
+# ipv6 address 41::1/24
+# ipv6 dhcp-relay 51::1 52::1
+# !
+
+
+# Using overridden
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ip dhcp-relay 91.1.1.1 92.1.1.1 vrf VrfReg1
+# ip dhcp-relay max-hop-count 5
+# ip dhcp-relay vrf-select
+# ip dhcp-relay policy-action append
+# ipv6 address 81::1/24
+# ipv6 dhcp-relay 91::1 92::1
+# ipv6 dhcp-relay max-hop-count 5
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 71.1.1.1 72.1.1.1 73.1.1.1
+# ip dhcp-relay source-interface Vlan100
+# ip dhcp-relay link-select
+# ip dhcp-relay circuit-id %h:%p
+# ipv6 address 61::1/24
+# ipv6 dhcp-relay 71::1 72::1
+# !
+# interface Eth1/3
+# mtu 9100
+# speed 400000
+# fec RS
+# shutdown
+# ip address 41.1.1.1/24
+# ip dhcp-relay 51.1.1.1 52.1.1.1
+# ip dhcp-relay circuit-id %h:%p
+# ipv6 address 41::1/24
+# ipv6 dhcp-relay 51::1 52::1
+# !
+
+ - name: Override DHCP and DHCPv6 relay configurations
+ dellemc.enterprise_sonic.sonic_dhcp_relay:
+ config:
+ - name: 'Eth1/1'
+ ipv4:
+ server_addresses:
+ - address: '91.1.1.1'
+ - address: '93.1.1.1'
+ - address: '95.1.1.1'
+ vrf_name: 'VrfReg1'
+ vrf_select: true
+ ipv6:
+ server_addresses:
+ - address: '93::1'
+ - address: '94::1'
+ source_interface: 'Vlan100'
+ - name: 'Eth1/2'
+ ipv4:
+ server_addresses:
+ - address: '73.1.1.1'
+ circuit_id: '%h:%p'
+ state: overridden
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration interface
+# !
+# interface Eth1/1
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 81.1.1.1/24
+# ip dhcp-relay 91.1.1.1 93.1.1.1 95.1.1.1 vrf VrfReg1
+# ip dhcp-relay vrf-select
+# ipv6 address 81::1/24
+# ipv6 dhcp-relay 93::1 94::1
+# ipv6 dhcp-relay source-interface Vlan100
+# !
+# interface Eth1/2
+# mtu 9100
+# speed 400000
+# fec RS
+# no shutdown
+# ip address 61.1.1.1/24
+# ip dhcp-relay 73.1.1.1
+# ip dhcp-relay circuit-id %h:%p
+# ipv6 address 61::1/24
+# !
+# interface Eth1/3
+# mtu 9100
+# speed 400000
+# fec RS
+# shutdown
+# ip address 41.1.1.1/24
+# ipv6 address 41::1/24
+# !
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.dhcp_relay.dhcp_relay import Dhcp_relayArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.dhcp_relay.dhcp_relay import Dhcp_relay
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Dhcp_relayArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Dhcp_relay(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_dhcp_snooping.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_dhcp_snooping.py
new file mode 100644
index 000000000..948ecb891
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_dhcp_snooping.py
@@ -0,0 +1,499 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_dhcp_snooping
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_dhcp_snooping
+version_added: 2.3.0
+notes:
+ - "Tested against Enterprise SONiC Distribution by Dell Technologies."
+short_description: "Manage DHCP Snooping on SONiC"
+description: "This module provides configuration management of DHCP snooping for devices running SONiC."
+author: Simon Nathans (@simon-nathans), Xiao Han (@Xiao_Han2)
+options:
+ config:
+ description: The DHCP snooping configuration.
+ type: dict
+ suboptions:
+ afis:
+ description:
+ - List of address families to configure.
+ - "There can be up to two items in this list: one where I(afi=ipv4) and one where I(afi=ipv6) to configure DHCPv4 and DHCPv6, respectively."
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - The address family to configure.
+ type: str
+ choices: ['ipv4', 'ipv6']
+ required: true
+ enabled:
+ description:
+ - Enable DHCP snooping for I(afi).
+ type: bool
+ vlans:
+ description:
+ - Enable DHCP snooping on a list of VLANs for I(afi).
+ - When I(state=deleted), passing an empty list will disable DHCP snooping in all VLANs
+ type: list
+ elements: str
+ verify_mac:
+ description:
+ - Enable DHCP snooping MAC verification for I(afi).
+ type: bool
+ trusted:
+ description:
+ - Mark interfaces as trusted for DHCP snooping for I(afi).
+ - When I(state=deleted), passing an empty list will delete all trusted interfaces.
+ type: list
+ elements: dict
+ suboptions:
+ intf_name:
+ description:
+ - The interface name.
+ type: str
+ required: true
+ source_bindings:
+ description:
+ - Create a static entry in the DHCP snooping binding database for I(afi).
+ - When I(state=deleted), passing an empty list will delete all source bindings.
+ type: list
+ elements: dict
+ suboptions:
+ mac_addr:
+ description:
+ - The binding's MAC address.
+ type: str
+ required: true
+ ip_addr:
+ description:
+ - The bindings's IP address.
+ type: str
+ intf_name:
+ description:
+ - The binding's interface name.
+ - Can be an Ethernet or a PortChannel interface.
+ type: str
+ vlan_id:
+ description:
+ - The binding's VLAN ID.
+ type: int
+ state:
+ description:
+ - The state of the configuration after module completion.
+ default: merged
+ choices: ['merged', 'deleted', 'overridden', 'replaced']
+ type: str
+"""
+EXAMPLES = """
+# Using merged
+#
+# Before State:
+# -------------
+#
+# sonic# show ip dhcp snooping
+# !
+# DHCP snooping is Disabled
+# DHCP snooping source MAC verification is Disabled
+# DHCP snooping is enabled on the following VLANs:
+# DHCP snooping trusted interfaces:
+# !
+
+- name: Configure DHCPv4 snooping global settings
+ dellemc.enterprise_sonic.sonic_dhcp_snooping:
+ config:
+ afis:
+ - afi: 'ipv4'
+ enabled: true
+ verify_mac: true
+ vlans: ['1', '2', '3', '5']
+ trusted:
+ - intf_name: 'Ethernet8'
+ state: merged
+
+# After State:
+# ------------
+#
+# sonic# show ip dhcp snooping
+# !
+# DHCP snooping is Enabled
+# DHCP snooping source MAC verification is Enabled
+# DHCP snooping is enabled on the following VLANs: 1 2 3 5
+# DHCP snooping trusted interfaces: Ethernet8
+# !
+
+
+# Using merged
+#
+# Before State:
+# -------------
+#
+# sonic# show ipv6 dhcp snooping
+# !
+# DHCPv6 snooping is Disabled
+# DHCPv6 snooping source MAC verification is Disabled
+# DHCPv6 snooping is enabled on the following VLANs:
+# DHCPv6 snooping trusted interfaces:
+# !
+
+- name: Configure DHCPv6 snooping global settings
+ dellemc.enterprise_sonic.sonic_dhcp_snooping:
+ config:
+ afis:
+ - afi: 'ipv6'
+ enabled: true
+ vlans:
+ - '4'
+ trusted:
+ - intf_name: 'Ethernet2'
+ - intf_name: PortChannel1
+ state: merged
+
+# After State:
+# ------------
+#
+# sonic# show ipv6 dhcp snooping
+# !
+# DHCPv6 snooping is Enabled
+# DHCPv6 snooping source MAC verification is Disabled
+# DHCPv6 snooping is enabled on the following VLANs: 4
+# DHCPv6 snooping trusted interfaces: PortChannel1
+# !
+
+
+# Using merged
+#
+# Before State:
+# -------------
+#
+# sonic# show ip dhcp snooping binding
+# !
+# Total number of Dynamic bindings: 0
+# Total number of Static bindings: 0
+# Total number of Tentative bindings: 0
+# MAC Address IP Address VLAN Interface Type Lease (Secs)
+# ----------------- --------------- ---- ----------- ------- -----------
+# !
+
+- name: Add DHCPv4 snooping bindings
+ dellemc.enterprise_sonic.sonic_dhcp_snooping:
+ config:
+ afis:
+ - afi: 'ipv4'
+ source_bindings:
+ - mac_addr: '00:b0:d0:63:c2:26'
+ ip_addr: '192.0.2.146'
+ intf_name: 'Ethernet4'
+ vlan_id: '1'
+ - mac_addr: 'aa:f7:67:fc:f4:9a'
+ ip_addr: '156.33.90.167'
+ intf_name: 'PortChannel1'
+ vlan_id: '2'
+ state: merged
+
+# After State:
+# ------------
+#
+# sonic# show ip dhcp snooping binding
+# !
+# Total number of Dynamic bindings: 0
+# Total number of Static bindings: 2
+# Total number of Tentative bindings: 0
+# MAC Address IP Address VLAN Interface Type Lease (Secs)
+# ----------------- --------------- ---- ----------- ------- -----------
+# 00:b0:d0:63:c2:26 192.0.2.146 1 Ethernet4 static NA
+# aa:f7:67:fc:f4:9a 156.33.90.167 2 PortChannel1 static NA
+# !
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show ip dhcp snooping
+# !
+# DHCP snooping is Enabled
+# DHCP snooping source MAC verification is Enabled
+# DHCP snooping is enabled on the following VLANs: 1 2 3 5
+# DHCP snooping trusted interfaces: Ethernet8
+# !
+
+- name: Disable DHCPv4 snooping on some VLANs
+ dellemc.enterprise_sonic.sonic_dhcp_snooping:
+ config:
+ afis:
+ - afi: 'ipv4'
+ vlans:
+ - '3'
+ - '5'
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show ip dhcp snooping
+# !
+# DHCP snooping is Enabled
+# DHCP snooping source MAC verification is Enabled
+# DHCP snooping is enabled on the following VLANs: 1 2
+# DHCP snooping trusted interfaces:
+# !
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show ipv6 dhcp snooping
+# !
+# DHCPv6 snooping is Enabled
+# DHCPv6 snooping source MAC verification is Disabled
+# DHCPv6 snooping is enabled on the following VLANs: 4
+# DHCPv6 snooping trusted interfaces: PortChannel1 PortChannel2 PortChannel3 PortChannel4
+# !
+
+- name: Disable DHCPv6 snooping on all VLANs
+ dellemc.enterprise_sonic.sonic_dhcp_snooping:
+ config:
+ afis:
+ - afi: 'ipv6'
+ vlans: []
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show ipv6 dhcp snooping
+# !
+# DHCPv6 snooping is Enabled
+# DHCPv6 snooping source MAC verification is Disabled
+# DHCPv6 snooping is enabled on the following VLANs:
+# DHCPv6 snooping trusted interfaces: PortChannel1 PortChannel2 PortChannel3 PortChannel4
+# !
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show ipv6 dhcp snooping
+# !
+# DHCPv6 snooping is Enabled
+# DHCPv6 snooping source MAC verification is Disabled
+# DHCPv6 snooping is enabled on the following VLANs: 4
+# DHCPv6 snooping trusted interfaces: PortChannel1 PortChannel2 PortChannel3 PortChannel4
+# !
+
+- name: Delete all DHCPv6 configuration
+ dellemc.enterprise_sonic.sonic_dhcp_snooping:
+ config:
+ afis:
+ - afi: 'ipv6'
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show ipv6 dhcp snooping
+# !
+# DHCPv6 snooping is Disabled
+# DHCPv6 snooping source MAC verification is Disabled
+# DHCPv6 snooping is enabled on the following VLANs:
+# DHCPv6 snooping trusted interfaces:
+# !
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show ip dhcp snooping binding
+# !
+# Total number of Dynamic bindings: 0
+# Total number of Static bindings: 2
+# Total number of Tentative bindings: 0
+# MAC Address IP Address VLAN Interface Type Lease (Secs)
+# ----------------- --------------- ---- ----------- ------- -----------
+# 00:b0:d0:63:c2:26 192.0.2.146 1 Ethernet4 static NA
+# aa:f7:67:fc:f4:9a 156.33.90.167 2 PortChannel1 static NA
+# !
+
+- name: Delete a DHCPv4 snooping binding
+ dellemc.enterprise_sonic.sonic_dhcp_snooping:
+ config:
+ afis:
+ - afi: 'ipv4'
+ source_bindings:
+ - mac_addr: '00:b0:d0:63:c2:26'
+ ip_addr: '192.0.2.146'
+ intf_name: 'Ethernet4'
+ vlan_id: '1'
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show ip dhcp snooping binding
+# !
+# Total number of Dynamic bindings: 0
+# Total number of Static bindings: 2
+# Total number of Tentative bindings: 0
+# MAC Address IP Address VLAN Interface Type Lease (Secs)
+# ----------------- --------------- ---- ----------- ------- -----------
+# aa:f7:67:fc:f4:9a 156.33.90.167 2 PortChannel1 static NA
+# !
+
+
+# Using overridden
+#
+# Before State:
+# -------------
+#
+# sonic# show ipv4 dhcp snooping binding
+# !
+# MAC Address IP Address VLAN Interface Type Lease (Secs)
+# ----------------- --------------- ---- ----------- ------- -----------
+# 00:b0:d0:63:c2:26 192.0.2.146 1 Ethernet4 static NA
+# 28:21:28:15:c1:1b 141.202.222.118 1 Ethernet2 static NA
+# aa:f7:67:fc:f4:9a 156.33.90.167 2 PortChannel1 static NA
+# !
+
+- name: Override DHCPv4 snooping bindings
+ dellemc.enterprise_sonic.sonic_dhcp_snooping:
+ config:
+ afis:
+ - afi: 'ipv4'
+ source_bindings:
+ - mac_addr: '00:b0:d0:63:c2:26'
+ ip_addr: '192.0.2.146'
+ intf_name: 'Ethernet4'
+ vlan_id: '3'
+ state: overridden
+
+# After State:
+# ------------
+#
+# sonic# show ipv4 dhcp snooping binding
+# !
+# MAC Address IP Address VLAN Interface Type Lease (Secs)
+# ----------------- --------------- ---- ----------- ------- -----------
+# 00:b0:d0:63:c2:26 192.0.2.146 3 Ethernet4 static NA
+# !
+
+
+# Using replaced
+#
+# Before State:
+# -------------
+#
+# sonic# show ipv4 dhcp snooping binding
+# !
+# MAC Address IP Address VLAN Interface Type Lease (Secs)
+# ----------------- --------------- ---- ----------- ------- -----------
+# 00:b0:d0:63:c2:26 192.0.2.146 1 Ethernet4 static NA
+# 28:21:28:15:c1:1b 141.202.222.118 1 Ethernet2 static NA
+# aa:f7:67:fc:f4:9a 156.33.90.167 2 PortChannel1 static NA
+# !
+
+- name: Replace DHCPv4 snooping bindings
+ dellemc.enterprise_sonic.sonic_dhcp_snooping:
+ config:
+ afis:
+ - afi: 'ipv4'
+ source_bindings:
+ - mac_addr: '00:b0:d0:63:c2:26'
+ ip_addr: '192.0.2.146'
+ intf_name: 'Ethernet4'
+ vlan_id: '3'
+ state: replaced
+
+# After State:
+# ------------
+#
+# sonic# show ipv4 dhcp snooping binding
+# !
+# MAC Address IP Address VLAN Interface Type Lease (Secs)
+# ----------------- --------------- ---- ----------- ------- -----------
+# 00:b0:d0:63:c2:26 192.0.2.146 3 Ethernet4 static NA
+# 28:21:28:15:c1:1b 141.202.222.118 1 Ethernet2 static NA
+# aa:f7:67:fc:f4:9a 156.33.90.167 2 PortChannel1 static NA
+# !
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.dhcp_snooping.dhcp_snooping import Dhcp_snoopingArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.dhcp_snooping.dhcp_snooping import Dhcp_snooping
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Dhcp_snoopingArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Dhcp_snooping(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_facts.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_facts.py
index f13e9defd..3fa261381 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_facts.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_facts.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
@@ -67,6 +67,7 @@ options:
- bgp_ext_communities
- mclag
- prefix_lists
+ - vlan_mapping
- vrfs
- vxlans
- users
@@ -77,6 +78,21 @@ options:
- radius_server
- static_routes
- ntp
+ - logging
+ - pki
+ - ip_neighbor
+ - port_group
+ - dhcp_relay
+ - acl_interfaces
+ - l2_acls
+ - l3_acls
+ - lldp_global
+ - mac
+ - bfd
+ - copp
+ - route_maps
+ - stp
+ - dhcp_snooping
"""
EXAMPLES = """
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_interfaces.py
index 0cd6a1896..09a5d0e18 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_interfaces.py
@@ -63,12 +63,53 @@ options:
description:
- MTU of the interface.
type: int
+ speed:
+ description:
+ - Interface speed.
+ - Supported speeds are dependent on the type of switch.
+ type: str
+ choices:
+ - SPEED_10MB
+ - SPEED_100MB
+ - SPEED_1GB
+ - SPEED_2500MB
+ - SPEED_5GB
+ - SPEED_10GB
+ - SPEED_20GB
+ - SPEED_25GB
+ - SPEED_40GB
+ - SPEED_50GB
+ - SPEED_100GB
+ - SPEED_400GB
+ auto_negotiate:
+ description:
+ - auto-negotiate transmission parameters with peer interface.
+ type: bool
+ advertised_speed:
+ description:
+ - Advertised speeds of the interface.
+ - Supported speeds are dependent on the type of switch.
+ - Speeds may be 10, 100, 1000, 2500, 5000, 10000, 20000, 25000, 40000, 50000, 100000 or 400000.
+ type: list
+ elements: str
+ fec:
+ description:
+ - Interface FEC (Forward Error Correction).
+ type: str
+ choices:
+ - FEC_RS
+ - FEC_FC
+ - FEC_DISABLED
+ - FEC_DEFAULT
+ - FEC_AUTO
state:
description:
- The state the configuration should be left in.
type: str
choices:
- merged
+ - replaced
+ - overridden
- deleted
default: merged
"""
@@ -80,18 +121,28 @@ EXAMPLES = """
#
# show interface status | no-more
#------------------------------------------------------------------------------------------
-#Name Description Admin Oper Speed MTU
+#Name Description Admin Oper AutoNeg Speed MTU
#------------------------------------------------------------------------------------------
-#Eth1/1 - up 100000 9100
-#Eth1/2 - up 100000 9100
-#Eth1/3 - down 100000 9100
-#Eth1/3 - down 1000 5000
-#Eth1/5 - down 100000 9100
+#Ethernet0 - up 100000 9100
+#Ethernet4 - up 100000 9100
+#Ethernet8 Ethernet-8 down 100000 9100
+#Ethernet12 Ethernet-12 down on - 5000
+#Ethernet16 - down 40000 9100
#
-- name: Configures interfaces
- dellemc.enterprise_sonic.sonic_interfaces:
+# show running-configuration interface Ethernet 8
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 100000
+# fec AUTO
+# shutdown
+#
+- name: Configure interfaces
+ sonic_interfaces:
config:
- name: Eth1/3
+ - name: Ethernet8
+ - name: Ethernet12
+ - name: Ethernet16
state: deleted
#
# After state:
@@ -99,14 +150,20 @@ EXAMPLES = """
#
# show interface status | no-more
#------------------------------------------------------------------------------------------
-#Name Description Admin Oper Speed MTU
+#Name Description Admin Oper AutoNeg Speed MTU
#------------------------------------------------------------------------------------------
-#Eth1/1 - up 100000 9100
-#Eth1/2 - up 100000 9100
-#Eth1/3 - down 100000 9100
-#Eth1/3 - up 100000 9100
-#Eth1/5 - down 100000 9100
+#Ethernet0 - up 100000 9100
+#Ethernet4 - up 100000 9100
+#Ethernet8 - up 100000 9100
+#Ethernet12 - up 100000 9100
+#Ethernet16 - up 100000 9100
#
+# show running-configuration interface Ethernet 8
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 100000
+# shutdown
#
# Using deleted
#
@@ -115,33 +172,33 @@ EXAMPLES = """
#
# show interface status | no-more
#------------------------------------------------------------------------------------------
-#Name Description Admin Oper Speed MTU
+#Name Description Admin Oper AutoNeg Speed MTU
#------------------------------------------------------------------------------------------
-#Eth1/1 - up 100000 9100
-#Eth1/2 - up 100000 9100
-#Eth1/3 - down 100000 9100
-#Eth1/3 - down 1000 9100
-#Eth1/5 - down 100000 9100
+#Ethernet0 - up 100000 9100
+#Ethernet4 - up 100000 9100
+#Ethernet8 - down 100000 9100
+#Ethernet12 - down 1000 9100
+#Ethernet16 - down 100000 9100
#
-
-- name: Configures interfaces
- dellemc.enterprise_sonic.sonic_interfaces:
+- name: Configure interfaces
+ sonic_interfaces:
config:
- state: deleted
+ state: deleted
#
# After state:
# -------------
#
# show interface status | no-more
#------------------------------------------------------------------------------------------
-#Name Description Admin Oper Speed MTU
+#Name Description Admin Oper AutoNeg Speed MTU
#------------------------------------------------------------------------------------------
-#Eth1/1 - up 100000 9100
-#Eth1/2 - up 100000 9100
-#Eth1/3 - up 100000 9100
-#Eth1/3 - up 100000 9100
-#Eth1/5 - up 100000 9100
+#Ethernet0 - up 100000 9100
+#Ethernet4 - up 100000 9100
+#Ethernet8 - up 100000 9100
+#Ethernet12 - up 100000 9100
+#Ethernet16 - up 100000 9100
+#
#
#
# Using merged
@@ -151,38 +208,177 @@ EXAMPLES = """
#
# show interface status | no-more
#------------------------------------------------------------------------------------------
-#Name Description Admin Oper Speed MTU
+#Name Description Admin Oper AutoNeg Speed MTU
#------------------------------------------------------------------------------------------
-#Eth1/1 - up 100000 9100
-#Eth1/2 - up 100000 9100
-#Eth1/3 - down 100000 9100
-#Eth1/3 - down 1000 9100
+#Ethernet0 - up 100000 9100
+#Ethernet4 - up 100000 9100
+#Ethernet8 - down 100000 9100
+#Ethernet12 - down 100000 9100
+#Ethernet16 - down 100000 9100
+#
+# show running-configuration interface Ethernet 8
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 100000
+# shutdown
#
-- name: Configures interfaces
- dellemc.enterprise_sonic.sonic_interfaces:
+- name: Configure interfaces
+ sonic_interfaces:
config:
- - name: Eth1/3
- description: 'Ethernet Twelve'
- - name: Eth1/5
- description: 'Ethernet Sixteen'
- enable: True
- mtu: 3500
+ - name: Ethernet8
+ fec: FEC_AUTO
+ - name: Ethernet12
+ description: 'Ethernet Twelve'
+ auto_negotiate: True
+ - name: Ethernet16
+ description: 'Ethernet Sixteen'
+ enabled: True
+ mtu: 3500
+ speed: SPEED_40GB
state: merged
#
+# After state:
+# ------------
+#
+# show interface status | no-more
+#------------------------------------------------------------------------------------------
+#Name Description Admin Oper AutoNeg Speed MTU
+#------------------------------------------------------------------------------------------
+#Ethernet0 - up 100000 9100
+#Ethernet4 - up 100000 9100
+#Ethernet8 - down 100000 9100
+#Ethernet12 Ethernet Twelve down on 100000 9100
+#Ethernet16 Ethernet Sixteen up 40000 3500
+#
+# show running-configuration interface Ethernet 8
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 100000
+# fec AUTO
+# shutdown
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+# show interface status | no-more
+#------------------------------------------------------------------------------------------
+#Name Description Admin Oper AutoNeg Speed MTU
+#------------------------------------------------------------------------------------------
+#Ethernet0 E0 up 100000 9100
+#Ethernet4 E4 up 100000 9100
+#Ethernet8 E8 down 100000 9100
+#Ethernet12 - down 1000 9100
+#Ethernet16 - down 100000 9100
+#
+# show running-configuration interface Ethernet 8
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 100000
+# shutdown
+#
+- name: Configure interfaces
+ sonic_interfaces:
+ config:
+ - name: Ethernet8
+ fec: FEC_AUTO
+ - name: Ethernet12
+ description: 'Ethernet Twelve'
+ mtu: 3500
+ enabled: True
+ auto_negotiate: True
+ - name: Ethernet16
+ description: 'Ethernet Sixteen'
+ mtu: 3000
+ enabled: False
+ speed: SPEED_40GB
+ state: overridden
+#
+# After state:
+# ------------
+#
+# show interface status | no-more
+#------------------------------------------------------------------------------------------
+#Name Description Admin Oper AutoNeg Speed MTU
+#------------------------------------------------------------------------------------------
+#Ethernet0 - up 100000 9100
+#Ethernet4 - up 100000 9100
+#Ethernet8 - up 100000 9100
+#Ethernet12 Ethernet Twelve up on 100000 3500
+#Ethernet16 Ethernet Sixteen down 40000 3000
+#
+# show running-configuration interface Ethernet 8
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 100000
+# fec AUTO
+# no shutdown
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+# show interface status | no-more
+#------------------------------------------------------------------------------------------
+#Name Description Admin Oper AutoNeg Speed MTU
+#------------------------------------------------------------------------------------------
+#Ethernet0 - up 100000 9100
+#Ethernet4 - up 100000 9100
+#Ethernet8 - down on 100000 9100
+#Ethernet12 - down 1000 9100
+#Ethernet16 - down 100000 9100
+#
+# show running-configuration interface Ethernet 8
+#!
+#interface Ethernet8
+# mtu 9100
+# speed auto 40000
+# shutdown
+#
+- name: Configure interfaces
+ sonic_interfaces:
+ config:
+ - name: Ethernet8
+ advertised_speed:
+ - "100000"
+ - name: Ethernet12
+ description: 'Ethernet Twelve'
+ mtu: 3500
+ enabled: True
+ auto_negotiate: True
+ - name: Ethernet16
+ description: 'Ethernet Sixteen'
+ mtu: 3000
+ enabled: False
+ speed: SPEED_40GB
+ state: replaced
#
# After state:
# ------------
#
# show interface status | no-more
#------------------------------------------------------------------------------------------
-#Name Description Admin Oper Speed MTU
+#Name Description Admin Oper AutoNeg Speed MTU
#------------------------------------------------------------------------------------------
-#Eth1/1 - up 100000 9100
-#Eth1/2 - up 100000 9100
-#Eth1/3 - down 100000 9100
-#Eth1/4 - down 1000 9100
-#Eth1/5 - down 100000 3500
+#Ethernet0 - up 100000 9100
+#Ethernet4 - up 100000 9100
+#Ethernet8 - down on 100000 9100
+#Ethernet12 Ethernet Twelve up on 100000 3500
+#Ethernet16 Ethernet Sixteen down 40000 3000
#
+# show running-configuration interface Ethernet 8
+#!
+#interface Ethernet8
+# mtu 9100
+# speed auto 100000
+# fec AUTO
+# shutdown
#
"""
RETURN = """
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ip_neighbor.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ip_neighbor.py
new file mode 100644
index 000000000..f1e4acc82
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ip_neighbor.py
@@ -0,0 +1,300 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_ip_neighbor
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: sonic_ip_neighbor
+version_added: 2.1.0
+notes:
+ - Supports C(check_mode).
+short_description: Manage IP neighbor global configuration on SONiC.
+description:
+ - This module provides configuration management of IP neighbor global for devices running SONiC.
+author: "M. Zhang (@mingjunzhang2019)"
+options:
+ config:
+ description:
+ - Specifies IP neighbor global configurations.
+ type: dict
+ suboptions:
+ ipv4_arp_timeout:
+ type: int
+ description:
+ - IPv4 ARP timeout.
+ - The range is from 60 to 14400.
+ ipv6_nd_cache_expiry:
+ type: int
+ description:
+ - IPv6 ND cache expiry.
+ - The range is from 60 to 14400.
+ num_local_neigh:
+ type: int
+ description:
+ - The number of reserved local neighbors.
+ - The range is from 0 to 32000.
+ ipv4_drop_neighbor_aging_time:
+ type: int
+ description:
+ - IPv4 drop neighbor aging time.
+ - The range is from 60 to 14400.
+ ipv6_drop_neighbor_aging_time:
+ type: int
+ description:
+ - IPv6 drop neighbor aging time.
+ - The range is from 60 to 14400.
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#sonic# show running-configuration
+#!
+#ip arp timeout 180
+#ip drop-neighbor aging-time 300
+#ipv6 drop-neighbor aging-time 300
+#ip reserve local-neigh 0
+#ipv6 nd cache expire 180
+#!
+- name: Configure IP neighbor global
+ sonic_ip_neighbor:
+ config:
+ ipv4_arp_timeout: 1200
+ ipv4_drop_neighbor_aging_time: 600
+ ipv6_drop_neighbor_aging_time: 600
+ ipv6_nd_cache_expiry: 1200
+ num_local_neigh: 1000
+ state: merged
+
+# After state:
+# ------------
+#
+#sonic# show running-configuration
+#!
+#ip arp timeout 1200
+#ip drop-neighbor aging-time 600
+#ipv6 drop-neighbor aging-time 600
+#ip reserve local-neigh 1000
+#ipv6 nd cache expire 1200
+#!
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#sonic# show running-configuration
+#!
+#ip arp timeout 1200
+#ip drop-neighbor aging-time 600
+#ipv6 drop-neighbor aging-time 600
+#ip reserve local-neigh 1000
+#ipv6 nd cache expire 1200
+#!
+- name: Delete some IP neighbor configuration
+ sonic_ip_neighbor:
+ config:
+ ipv4_arp_timeout: 0
+ ipv4_drop_neighbor_aging_time: 0
+ state: deleted
+
+# After state:
+# ------------
+#
+#sonic# show running-configuration
+#!
+#ip arp timeout 180
+#ip drop-neighbor aging-time 300
+#ipv6 drop-neighbor aging-time 600
+#ip reserve local-neigh 1000
+#ipv6 nd cache expire 1200
+#!
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#sonic# show running-configuration
+#!
+#ip arp timeout 1200
+#ip drop-neighbor aging-time 600
+#ipv6 drop-neighbor aging-time 600
+#ip reserve local-neigh 1000
+#ipv6 nd cache expire 1200
+#!
+- name: Delete all IP neighbor configuration
+ sonic_ip_neighbor:
+ config: {}
+ state: deleted
+
+# After state:
+# ------------
+#
+#sonic# show running-configuration
+#!
+#ip arp timeout 180
+#ip drop-neighbor aging-time 300
+#ipv6 drop-neighbor aging-time 300
+#ip reserve local-neigh 0
+#ipv6 nd cache expire 180
+#!
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+#sonic# show running-configuration
+#!
+#ip arp timeout 1200
+#ip drop-neighbor aging-time 600
+#ipv6 drop-neighbor aging-time 300
+#ip reserve local-neigh 0
+#ipv6 nd cache expire 180
+#!
+- name: Change some IP neighbor configuration
+ sonic_ip_neighbor:
+ config:
+ ipv6_drop_neighbor_aging_time: 600
+ ipv6_nd_cache_expiry: 1200
+ num_local_neigh: 1000
+ state: replaced
+
+# After state:
+# ------------
+#
+#sonic# show running-configuration
+#!
+#ip arp timeout 1200
+#ip drop-neighbor aging-time 600
+#ipv6 drop-neighbor aging-time 600
+#ip reserve local-neigh 1000
+#ipv6 nd cache expire 1200
+#!
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+#sonic# show running-configuration
+#!
+#ip arp timeout 1200
+#ip drop-neighbor aging-time 600
+#ipv6 drop-neighbor aging-time 300
+#ip reserve local-neigh 0
+#ipv6 nd cache expire 180
+#!
+- name: Reset IP neighbor configuration, then configure some
+ sonic_ip_neighbor:
+ config:
+ ipv6_drop_neighbor_aging_time: 600
+ ipv6_nd_cache_expiry: 1200
+ num_local_neigh: 1000
+ state: overridden
+
+# After state:
+# ------------
+#
+#sonic# show running-configuration
+#!
+#ip arp timeout 180
+#ip drop-neighbor aging-time 300
+#ipv6 drop-neighbor aging-time 600
+#ip reserve local-neigh 1000
+#ipv6 nd cache expire 1200
+#!
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after(generated):
+ description: The generated configuration model invocation.
+ returned: when C(check_mode)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.ip_neighbor.ip_neighbor import Ip_neighborArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.ip_neighbor.ip_neighbor import Ip_neighbor
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Ip_neighborArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Ip_neighbor(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l2_acls.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l2_acls.py
new file mode 100644
index 000000000..cda50242b
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l2_acls.py
@@ -0,0 +1,582 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_l2_acls
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_l2_acls
+version_added: '2.1.0'
+notes:
+ - Supports C(check_mode).
+short_description: Manage Layer 2 access control lists (ACL) configurations on SONiC
+description:
+ - This module provides configuration management of Layer 2 access control lists (ACL)
+ in devices running SONiC.
+author: 'Arun Saravanan Balachandran (@ArunSaravananBalachandran)'
+options:
+ config:
+ description:
+ - Specifies Layer 2 ACL configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Specifies the ACL name.
+ type: str
+ required: true
+ remark:
+ description:
+ - Specifies remark for the ACL.
+ type: str
+ rules:
+ description:
+ - List of rules with the ACL.
+ - I(sequence_num), I(action), I(source) & I(destination) are required for adding a new rule.
+ - If I(state=deleted), options other than I(sequence_num) are not considered.
+ - I(ethertype) and I(vlan_tag_format) are mutually exclusive.
+ type: list
+ elements: dict
+ suboptions:
+ sequence_num:
+ description:
+ - Specifies the sequence number of the rule.
+ - The range is from 1 to 65535.
+ type: int
+ required: true
+ action:
+ description:
+ - Specifies the action taken on the matched Ethernet frame.
+ type: str
+ choices:
+ - deny
+ - discard
+ - do-not-nat
+ - permit
+ - transit
+ source:
+ description:
+ - Specifies the source of the Ethernet frame.
+ - I(address) and I(address_mask) are required together.
+ - I(any), I(host) and I(address) are mutually exclusive.
+ type: dict
+ suboptions:
+ any:
+ description:
+ - Match any source MAC address.
+ type: bool
+ host:
+ description:
+ - MAC address of a single source host.
+ type: str
+ address:
+ description:
+ - Source MAC address.
+ type: str
+ address_mask:
+ description:
+ - Source MAC address mask.
+ type: str
+ destination:
+ description:
+ - Specifies the destination of the Ethernet frame.
+ - I(address) and I(address_mask) are required together.
+ - I(any), I(host) and I(address) are mutually exclusive.
+ type: dict
+ suboptions:
+ any:
+ description:
+ - Match any destination MAC address.
+ type: bool
+ host:
+ description:
+ - MAC address of a single destination host.
+ type: str
+ address:
+ description:
+ - Destination MAC address.
+ type: str
+ address_mask:
+ description:
+ - Destination MAC address mask.
+ type: str
+ ethertype:
+ description:
+ - Specifies the EtherType of the Ethernet frame.
+ - Only one suboption can be specified for ethertype in a rule.
+ type: dict
+ suboptions:
+ value:
+ description:
+ - Specifies the EtherType value to match as a hexadecimal string.
+ - The range is from 0x600 to 0xffff.
+ type: str
+ arp:
+ description:
+ - Match Ethernet frame with ARP EtherType (0x806).
+ type: bool
+ ipv4:
+ description:
+ - Match Ethernet frame with IPv4 EtherType (0x800).
+ type: bool
+ ipv6:
+ description:
+ - Match Ethernet frame with IPv6 EtherType (0x86DD).
+ type: bool
+ vlan_id:
+ description:
+ - Match Ethernet frame with the given VLAN ID.
+ type: int
+ vlan_tag_format:
+ description:
+ - Match Ethernet frame with the given VLAN tag format.
+ type: dict
+ suboptions:
+ multi_tagged:
+ description:
+ - Match three of more VLAN tagged Ethernet frame.
+ type: bool
+ dei:
+ description:
+ - Match Ethernet frame with the given Drop Eligible Indicator (DEI) value.
+ type: int
+ choices:
+ - 0
+ - 1
+ pcp:
+ description:
+ - Match Ethernet frames using Priority Code Point (PCP) value.
+ - I(mask) is valid only when I(value) is specified.
+ - I(value) and I(traffic_type) are mutually exclusive.
+ type: dict
+ suboptions:
+ value:
+ description:
+ - Match Ethernet frame with the given PCP value.
+ - The range is from 0 to 7
+ type: int
+ mask:
+ description:
+ - Match Ethernet frame with given PCP value and mask.
+ - The range is from 0 to 7.
+ type: int
+ traffic_type:
+ description:
+ - Match Ethernet frame with PCP value for the given traffic type.
+ - C(be) - Match Ethernet frame with Best effort PCP (0).
+ - C(bk) - Match Ethernet frame with Background PCP (1).
+ - C(ee) - Match Ethernet frame with Excellent effort PCP (2).
+ - C(ca) - Match Ethernet frame with Critical applications PCP (3).
+ - C(vi) - Match Ethernet frame with Video, < 100 ms latency and jitter PCP (4).
+ - C(vo) - Match Ethernet frame with Voice, < 10 ms latency and jitter PCP (5).
+ - C(ic) - Match Ethernet frame with Internetwork control PCP (6).
+ - C(nc) - Match Ethernet frame with Network control PCP (7).
+ type: str
+ choices:
+ - be
+ - bk
+ - ee
+ - ca
+ - vi
+ - vo
+ - ic
+ - nc
+ remark:
+ description:
+ - Specifies remark for the ACL rule.
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion.
+ - C(merged) - Merges provided L2 ACL configuration with on-device configuration.
+ - C(replaced) - Replaces on-device configuration of the specified L2 ACLs with provided configuration.
+ - C(overridden) - Overrides all on-device L2 ACL configurations with the provided configuration.
+ - C(deleted) - Deletes on-device L2 ACL configuration.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration mac access-list
+# !
+# mac access-list test
+# seq 1 permit host 22:22:22:22:22:22 any vlan 20
+# sonic#
+
+ - name: Merge provided Layer 2 ACL configurations
+ dellemc.enterprise_sonic.sonic_l2_acls:
+ config:
+ - name: 'test'
+ rules:
+ - sequence_num: 2
+ action: 'permit'
+ source:
+ any: true
+ destination:
+ any: true
+ ethertype:
+ value: '0x88cc'
+ remark: 'LLDP'
+ - sequence_num: 3
+ action: 'permit'
+ source:
+ any: true
+ destination:
+ address: '00:00:10:00:00:00'
+ address_mask: '00:00:ff:ff:00:00'
+ pcp:
+ value: 4
+ mask: 6
+ - sequence_num: 4
+ action: 'deny'
+ source:
+ any: true
+ destination:
+ any: true
+ vlan_tag_format:
+ multi_tagged: true
+ - name: 'test1'
+ remark: 'test_mac_acl'
+ rules:
+ - sequence_num: 1
+ action: 'permit'
+ source:
+ host: '11:11:11:11:11:11'
+ destination:
+ any: true
+ - sequence_num: 2
+ action: 'permit'
+ source:
+ any: true
+ destination:
+ any: true
+ ethertype:
+ arp: true
+ vlan_id: 100
+ - sequence_num: 3
+ action: 'deny'
+ source:
+ any: true
+ destination:
+ any: true
+ dei: 0
+ state: merged
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration mac access-list
+# !
+# mac access-list test
+# seq 1 permit host 22:22:22:22:22:22 any vlan 20
+# seq 2 permit any any 0x88cc remark LLDP
+# seq 3 permit any 00:00:10:00:00:00 00:00:ff:ff:00:00 pcp vi pcp-mask 6
+# seq 4 deny any any vlan-tag-format multi-tagged
+# !
+# mac access-list test1
+# remark test_mac_acl
+# seq 1 permit host 11:11:11:11:11:11 any
+# seq 2 permit any any arp vlan 100
+# seq 3 deny any any dei 0
+# sonic#
+
+
+# Using replaced
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration mac access-list
+# !
+# mac access-list test
+# seq 1 permit host 22:22:22:22:22:22 any vlan 20
+# seq 2 permit any any 0x88cc remark LLDP
+# seq 3 permit any 00:00:10:00:00:00 00:00:ff:ff:00:00 pcp vi pcp-mask 6
+# !
+# mac access-list test1
+# remark test_mac_acl
+# seq 1 permit host 11:11:11:11:11:11 any
+# seq 2 permit any any arp vlan 100
+# seq 3 deny any any dei 0
+# sonic#
+
+ - name: Replace device configuration of specified Layer 2 ACLs with provided configuration
+ dellemc.enterprise_sonic.sonic_l2_acls:
+ config:
+ - name: 'test1'
+ rules:
+ - sequence_num: 1
+ action: 'permit'
+ source:
+ any: true
+ destination:
+ any: true
+ ethertype:
+ arp: true
+ vlan_id: 200
+ - sequence_num: 2
+ action: 'discard'
+ source:
+ any: true
+ destination:
+ any: true
+ - name: 'test2'
+ rules:
+ - sequence_num: 1
+ action: 'permit'
+ source:
+ host: '33:33:33:33:33:33'
+ destination:
+ host: '44:44:44:44:44:44'
+ state: replaced
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration mac access-list
+# !
+# mac access-list test
+# seq 1 permit host 22:22:22:22:22:22 any vlan 20
+# seq 2 permit any any 0x88cc remark LLDP
+# seq 3 permit any 00:00:10:00:00:00 00:00:ff:ff:00:00 pcp vi pcp-mask 6
+# !
+# mac access-list test1
+# seq 1 permit any any arp vlan 200
+# seq 2 discard any any
+# !
+# mac access-list test2
+# seq 1 permit host 33:33:33:33:33:33 host 44:44:44:44:44:44
+# sonic#
+
+
+# Using overridden
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration mac access-list
+# !
+# mac access-list test
+# seq 1 permit host 22:22:22:22:22:22 any vlan 20
+# seq 2 permit any any 0x88cc remark LLDP
+# seq 3 permit any 00:00:10:00:00:00 00:00:ff:ff:00:00 pcp vi pcp-mask 6
+# !
+# mac access-list test1
+# seq 1 permit any any arp vlan 200
+# seq 2 discard any any
+# !
+# mac access-list test2
+# seq 1 permit host 33:33:33:33:33:33 host 44:44:44:44:44:44
+# sonic#
+
+ - name: Override device configuration of all Layer 2 ACLs with provided configuration
+ dellemc.enterprise_sonic.sonic_l2_acls:
+ config:
+ - name: 'test1'
+ remark: 'test_mac_acl'
+ rules:
+ - sequence_num: 1
+ action: 'permit'
+ source:
+ host: '11:11:11:11:11:11'
+ destination:
+ any: true
+ vlan_id: 100
+ - sequence_num: 2
+ action: 'permit'
+ source:
+ any: true
+ destination:
+ any: true
+ pcp:
+ traffic_type: 'ca'
+ - sequence_num: 3
+ action: 'deny'
+ source:
+ any: true
+ destination:
+ any: true
+ ethertype:
+ ipv4: true
+ state: overridden
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration mac access-list
+# !
+# mac access-list test1
+# remark test_mac_acl
+# seq 1 permit host 11:11:11:11:11:11 any vlan 100
+# seq 2 permit any any pcp ca
+# seq 3 deny any any ip
+# sonic#
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration mac access-list
+# !
+# mac access-list test
+# seq 1 permit host 22:22:22:22:22:22 any vlan 20
+# seq 2 permit any any 0x88cc remark LLDP
+# seq 3 permit any 00:00:10:00:00:00 00:00:ff:ff:00:00 pcp vi pcp-mask 6
+# !
+# mac access-list test1
+# remark test_mac_acl
+# seq 1 permit host 11:11:11:11:11:11 any vlan 100
+# seq 2 deny any any ip
+# !
+# mac access-list test2
+# seq 1 permit host 33:33:33:33:33:33 host 44:44:44:44:44:44
+# sonic#
+
+ - name: Delete specified Layer 2 ACLs, ACL remark and ACL rule entries
+ dellemc.enterprise_sonic.sonic_l2_acls:
+ config:
+ - name: 'test'
+ rules:
+ - sequence_num: 3
+ - name: 'test1'
+ remark: 'test_mac_acl'
+ - name: 'test2'
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration mac access-list
+# !
+# mac access-list test
+# seq 1 permit host 22:22:22:22:22:22 any vlan 20
+# seq 2 permit any any 0x88cc remark LLDP
+# !
+# mac access-list test1
+# seq 1 permit host 11:11:11:11:11:11 any vlan 100
+# seq 2 deny any any ip
+# sonic#
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration mac access-list
+# !
+# mac access-list test
+# seq 1 permit host 22:22:22:22:22:22 any vlan 20
+# seq 2 permit any any 0x88cc remark LLDP
+# seq 3 permit any 00:00:10:00:00:00 00:00:ff:ff:00:00 pcp vi pcp-mask 6
+# !
+# mac access-list test1
+# remark test_mac_acl
+# seq 1 permit host 11:11:11:11:11:11 any vlan 100
+# seq 2 deny any any ip
+# !
+# mac access-list test2
+# seq 1 permit host 33:33:33:33:33:33 host 44:44:44:44:44:44
+# sonic#
+
+ - name: Delete all Layer 2 ACL configurations
+ dellemc.enterprise_sonic.sonic_l2_acls:
+ config:
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration mac access-list
+# sonic#
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after(generated):
+ description: The generated configuration model invocation.
+ returned: when C(check_mode)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.l2_acls.l2_acls import L2_aclsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.l2_acls.l2_acls import L2_acls
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=L2_aclsArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = L2_acls(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l2_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l2_interfaces.py
index 34a8ff720..8d70f8a3f 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l2_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l2_interfaces.py
@@ -54,13 +54,13 @@ options:
description: Configures trunking parameters on an interface.
suboptions:
allowed_vlans:
- description: Specifies list of allowed VLANs of trunk mode on the interface.
+ description: Specifies a list of allowed trunk mode VLANs and VLAN ranges for the interface.
type: list
elements: dict
suboptions:
vlan:
- type: int
- description: Configures the specified VLAN in trunk mode.
+ type: str
+ description: Configures the specified trunk mode VLAN or VLAN range.
access:
type: dict
description: Configures access mode characteristics of the interface.
@@ -74,6 +74,8 @@ options:
choices:
- merged
- deleted
+ - replaced
+ - overridden
default: merged
"""
EXAMPLES = """
@@ -145,6 +147,47 @@ EXAMPLES = """
#15 Inactive
#
#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#11 Inactive T Ethernet12
+#12 Inactive A Ethernet12
+#13 Inactive T Ethernet12
+#14 Inactive T Ethernet12
+#15 Inactive T Ethernet12
+#16 Inactive T Ethernet12
+
+- name: Delete the access vlan and a range of trunk vlans for an interface
+ sonic_l2_interfaces:
+ config:
+ - name: Ethernet12
+ access:
+ vlan: 12
+ trunk:
+ allowed_vlans:
+ - vlan: 13-16
+ state: deleted
+
+# After state:
+# ------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#11 Inactive T Ethernet12
+#12 Inactive
+#13 Inactive
+#14 Inactive
+#15 Inactive
+#16 Inactive
+#
+#
+#
# Using merged
#
# Before state:
@@ -153,10 +196,11 @@ EXAMPLES = """
#do show Vlan
#Q: A - Access (Untagged), T - Tagged
#NUM Status Q Ports
+#10 Inactive
#11 Inactive T Eth1/7
#12 Inactive T Eth1/7
#
-- name: Configures switch port of interfaces
+- name: Configures an access vlan for an interface
dellemc.enterprise_sonic.sonic_l2_interfaces:
config:
- name: Eth1/3
@@ -184,15 +228,23 @@ EXAMPLES = """
#Q: A - Access (Untagged), T - Tagged
#NUM Status Q Ports
#10 Inactive A Eth1/3
+#12 Inactive
+#13 Inactive
+#14 Inactive
+#15 Inactive
+#16 Inactive
+#18 Inactive
#
-- name: Configures switch port of interfaces
+- name: Modify the access vlan, add a range of trunk vlans and a single trunk vlan for an interface
dellemc.enterprise_sonic.sonic_l2_interfaces:
config:
- name: Eth1/3
+ access:
+ vlan: 12
trunk:
allowed_vlans:
- - vlan: 11
- - vlan: 12
+ - vlan: 13-16
+ - vlan: 18
state: merged
#
# After state:
@@ -201,9 +253,13 @@ EXAMPLES = """
#do show Vlan
#Q: A - Access (Untagged), T - Tagged
#NUM Status Q Ports
-#10 Inactive A Eth1/3
-#11 Inactive T Eth1/7
-#12 Inactive T Eth1/7
+#10 Inactive
+#12 Inactive A Eth1/3
+#13 Inactive T Eth1/3
+#14 Inactive T Eth1/3
+#15 Inactive T Eth1/3
+#16 Inactive T Eth1/3
+#18 Inactive T Eth1/3
#
#
# Using merged
@@ -250,6 +306,89 @@ EXAMPLES = """
#15 Inactive T Eth1/5
#
#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive A Ethernet12
+# A Ethernet13
+#11 Inactive T Ethernet12
+# T Ethernet13
+
+- name: Replace access vlan and trunk vlans for specified interfaces
+ sonic_l2_interfaces:
+ config:
+ - name: Ethernet12
+ access:
+ vlan: 12
+ trunk:
+ allowed_vlans:
+ - vlan: 13-14
+ - name: Ethernet14
+ access:
+ vlan: 10
+ trunk:
+ allowed_vlans:
+ - vlan: 11
+ - vlan: 13-14
+ state: replaced
+
+# After state:
+# ------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive A Ethernet13
+# A Ethernet14
+#11 Inactive T Ethernet13
+# T Ethernet14
+#12 Inactive A Ethernet12
+#13 Inactive T Ethernet12
+# T Ethernet14
+#14 Inactive T Ethernet12
+# T Ethernet14
+#
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive A Ethernet11
+#11 Inactive T Ethernet11
+#12 Inactive A Ethernet12
+#13 Inactive T Ethernet12
+
+- name: Override L2 interfaces configuration in device with provided configuration
+ sonic_l2_interfaces:
+ config:
+ - name: Ethernet13
+ access:
+ vlan: 12
+ trunk:
+ allowed_vlans:
+ - vlan: 13-14
+ state: overridden
+
+# After state:
+# ------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#12 Inactive A Ethernet13
+#13 Inactive T Ethernet13
+#14 Inactive T Ethernet13
+#
+#
"""
RETURN = """
before:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l3_acls.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l3_acls.py
new file mode 100644
index 000000000..ad34025df
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l3_acls.py
@@ -0,0 +1,1058 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_l3_acls
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_l3_acls
+version_added: '2.1.0'
+notes:
+ - Supports C(check_mode).
+short_description: Manage Layer 3 access control lists (ACL) configurations on SONiC
+description:
+ - This module provides configuration management of Layer 3 access control lists (ACL)
+ in devices running SONiC.
+author: 'Arun Saravanan Balachandran (@ArunSaravananBalachandran)'
+options:
+ config:
+ description:
+ - Specifies Layer 3 ACL configurations.
+ type: list
+ elements: dict
+ suboptions:
+ address_family:
+ description:
+ - Specifies the address family of the ACLs.
+ type: str
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ acls:
+ description:
+ - List of ACL configuration for the given address family.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Specifies the ACL name.
+ type: str
+ required: true
+ remark:
+ description:
+ - Specifies remark for the ACL.
+ type: str
+ rules:
+ description:
+ - List of rules with the ACL.
+ - I(sequence_num), I(action), I(protocol), I(source) & I(destination) are required for adding a new rule.
+ - If I(state=deleted), options other than I(sequence_num) are not considered.
+ type: list
+ elements: dict
+ suboptions:
+ sequence_num:
+ description:
+ - Specifies the sequence number of the rule.
+ - The range is from 1 to 65535.
+ type: int
+ required: true
+ action:
+ description:
+ - Specifies the action taken on the matched packet.
+ type: str
+ choices:
+ - deny
+ - discard
+ - do-not-nat
+ - permit
+ - transit
+ protocol:
+ description:
+ - Specifies the protocol to match.
+ - Only one suboption can be specified for protocol in a rule.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - Match packets with the given protocol.
+ - C(ip) - Match any IPv4 packets.
+ - C(ipv6) - Match any IPv6 packets.
+ - C(icmp) - Match ICMP packets.
+ - C(icmpv6) - Match ICMPv6 packets.
+ - C(tcp) - Match TCP packets.
+ - C(udp) - Match UDP packets.
+ - C(ip) and C(icmp) are valid only for IPv4 ACLs.
+ - C(ipv6) and C(icmpv6) are valid only for IPv6 ACLs.
+ type: str
+ choices:
+ - ip
+ - ipv6
+ - icmp
+ - icmpv6
+ - tcp
+ - udp
+ number:
+ description:
+ - Match packets with given protocol number.
+ - The range is from 0 to 255.
+ type: int
+ source:
+ description:
+ - Specifies the source of the packet.
+ - I(any), I(host) and I(prefix) are mutually exclusive.
+ type: dict
+ suboptions:
+ any:
+ description:
+ - Match any source network address.
+ type: bool
+ host:
+ description:
+ - Network address of a single source host.
+ type: str
+ prefix:
+ description:
+ - Source network prefix in the format A.B.C.D/mask (ipv4) or A::B/mask (ipv6).
+ type: str
+ port_number:
+ description:
+ - Specifies the source port (valid only for TCP or UDP)
+ - Only one suboption can be specified for port_number in a rule.
+ type: dict
+ suboptions:
+ eq:
+ description:
+ - Match packets with source port equal to the given port number.
+ - The range is from 0 to 65535.
+ type: int
+ gt:
+ description:
+ - Match packets with source port greater than the given port number.
+ - The range is from 0 to 65534.
+ type: int
+ lt:
+ description:
+ - Match packets with source port lesser than the given port number.
+ - The range is from 1 to 65535.
+ type: int
+ range:
+ description:
+ - Match packets with source port in the given range.
+ - I(begin) and I(end) are required together.
+ type: dict
+ suboptions:
+ begin:
+ description:
+ - Specifies the beginning of the port range.
+ - The range is from 0 to 65534.
+ type: int
+ end:
+ description:
+ - Specifies the end of the port range.
+ - The range is from 1 to 65535.
+ type: int
+ destination:
+ description:
+ - Specifies the destination of the packet.
+ - I(any), I(host) and I(prefix) are mutually exclusive.
+ type: dict
+ suboptions:
+ any:
+ description:
+ - Match any destination network address.
+ type: bool
+ host:
+ description:
+ - Network address of a single destination host.
+ type: str
+ prefix:
+ description:
+ - Destination network prefix in the format A.B.C.D/mask (ipv4) or A::B/mask (ipv6).
+ type: str
+ port_number:
+ description:
+ - Specifies the destination port (valid only for TCP or UDP)
+ - Only one suboption can be specified for port_number in a rule.
+ type: dict
+ suboptions:
+ eq:
+ description:
+ - Match packets with destination port equal to the given port number.
+ - The range is from 0 to 65535.
+ type: int
+ gt:
+ description:
+ - Match packets with destination port greater than the given port number.
+ - The range is from 0 to 65534.
+ type: int
+ lt:
+ description:
+ - Match packets with destination port lesser than the given port number.
+ - The range is from 1 to 65535.
+ type: int
+ range:
+ description:
+ - Match packets with destination port in the given range.
+ - I(begin) and I(end) are required together.
+ type: dict
+ suboptions:
+ begin:
+ description:
+ - Specifies the beginning of the port range.
+ - The range is from 0 to 65534.
+ type: int
+ end:
+ description:
+ - Specifies the end of the port range.
+ - The range is from 1 to 65535.
+ type: int
+ protocol_options:
+ description:
+ - Specifies the additional packet match options for the chosen protocol.
+ - I(icmp), I(icmpv6) and I(tcp) are mutually exclusive.
+ type: dict
+ suboptions:
+ icmp:
+ description:
+ - Packet match options for ICMP.
+ type: dict
+ suboptions:
+ code:
+ description:
+ - Match packets with given ICMP code.
+ - The range is from 0 to 255.
+ type: int
+ type:
+ description:
+ - Match packets with given ICMP type.
+ - The range is from 0 to 255.
+ type: int
+ icmpv6:
+ description:
+ - Packet match options for ICMPv6.
+ type: dict
+ suboptions:
+ code:
+ description:
+ - Match packets with given ICMPv6 code.
+ - The range is from 0 to 255.
+ type: int
+ type:
+ description:
+ - Match packets with given ICMPv6 type.
+ - The range is from 0 to 255.
+ type: int
+ tcp:
+ description:
+ - Packet match options for TCP.
+ - I(established) and other TCP flag options are mutually exclusive.
+ type: dict
+ suboptions:
+ established:
+ description:
+ - Match packets which are part of established TCP session.
+ type: bool
+ ack:
+ description:
+ - Match packets with ACK flag set.
+ type: bool
+ not_ack:
+ description:
+ - Match packets with ACK flag cleared.
+ type: bool
+ fin:
+ description:
+ - Match packets with FIN flag set.
+ type: bool
+ not_fin:
+ description:
+ - Match packets with FIN flag cleared.
+ type: bool
+ psh:
+ description:
+ - Match packets with PSH flag set.
+ type: bool
+ not_psh:
+ description:
+ - Match packets with PSH flag cleared.
+ type: bool
+ rst:
+ description:
+ - Match packets with RST flag set.
+ type: bool
+ not_rst:
+ description:
+ - Match packets with RST flag cleared.
+ type: bool
+ syn:
+ description:
+ - Match packets with SYN flag set.
+ type: bool
+ not_syn:
+ description:
+ - Match packets with SYN flag cleared.
+ type: bool
+ urg:
+ description:
+ - Match packets with URG flag set.
+ type: bool
+ not_urg:
+ description:
+ - Match packets with URG flag cleared.
+ type: bool
+ vlan_id:
+ description:
+ - Match packets with the given VLAN ID value.
+ type: int
+ dscp:
+ description:
+ - Match packets using DSCP value.
+ - Only one suboption can be specified for dscp in a rule.
+ type: dict
+ suboptions:
+ value:
+ description:
+ - Match packets with given DSCP value.
+ - The range is from 0 to 63.
+ type: int
+ af11:
+ description:
+ - Match packets with AF11 DSCP (001010 - Decimal value 10).
+ type: bool
+ af12:
+ description:
+ - Match packets with AF12 DSCP (001100 - Decimal value 12).
+ type: bool
+ af13:
+ description:
+ - Match packets with AF13 DSCP (001110 - Decimal value 14).
+ type: bool
+ af21:
+ description:
+ - Match packets with AF21 DSCP (010010 - Decimal value 18).
+ type: bool
+ af22:
+ description:
+ - Match packets with AF22 DSCP (010100 - Decimal value 20).
+ type: bool
+ af23:
+ description:
+ - Match packets with AF23 DSCP (010110 - Decimal value 22).
+ type: bool
+ af31:
+ description:
+ - Match packets with AF31 DSCP (011010 - Decimal value 26).
+ type: bool
+ af32:
+ description:
+ - Match packets with AF32 DSCP (011100 - Decimal value 28).
+ type: bool
+ af33:
+ description:
+ - Match packets with AF33 DSCP (011110 - Decimal value 30).
+ type: bool
+ af41:
+ description:
+ - Match packets with AF41 DSCP (100010 - Decimal value 34).
+ type: bool
+ af42:
+ description:
+ - Match packets with AF42 DSCP (100100 - Decimal value 36).
+ type: bool
+ af43:
+ description:
+ - Match packets with AF43 DSCP (100110 - Decimal value 38).
+ type: bool
+ cs1:
+ description:
+ - Match packets with CS1 DSCP (001000 - Decimal value 8).
+ type: bool
+ cs2:
+ description:
+ - Match packets with CS2 DSCP (010000 - Decimal value 16).
+ type: bool
+ cs3:
+ description:
+ - Match packets with CS3 DSCP (011000 - Decimal value 24).
+ type: bool
+ cs4:
+ description:
+ - Match packets with CS4 DSCP (100000 - Decimal value 32).
+ type: bool
+ cs5:
+ description:
+ - Match packets with CS5 DSCP (101000 - Decimal value 40).
+ type: bool
+ cs6:
+ description:
+ - Match packets with CS6 DSCP (110000 - Decimal value 48).
+ type: bool
+ cs7:
+ description:
+ - Match packets with CS7 DSCP (111000 - Decimal value 56).
+ type: bool
+ default:
+ description:
+ - Match packets with CS0 DSCP (000000 - Decimal value 0).
+ type: bool
+ ef:
+ description:
+ - Match packets with EF DSCP (101110 - Decimal value 46).
+ type: bool
+ voice_admit:
+ description:
+ - Match packets with VOICE-ADMIT DSCP (101100 - Decimal value 44).
+ type: bool
+ remark:
+ description:
+ - Specifies remark for the ACL rule.
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion.
+ - C(merged) - Merges provided L3 ACL configuration with on-device configuration.
+ - C(replaced) - Replaces on-device configuration of the specified L3 ACLs with provided configuration.
+ - C(overridden) - Overrides all on-device L3 ACL configurations with the provided configuration.
+ - C(deleted) - Deletes on-device L3 ACL configuration.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration ip access-list
+# !
+# ip access-list test
+# seq 1 permit ip host 192.168.1.2 any
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# !
+# ipv6 access-list testv6
+# seq 1 permit ipv6 host 192:168:1::2 any
+# sonic#
+
+ - name: Merge provided Layer 3 ACL configurations
+ dellemc.enterprise_sonic.sonic_l3_acls:
+ config:
+ - address_family: 'ipv4'
+ acls:
+ - name: 'test'
+ rules:
+ - sequence_num: 2
+ action: 'permit'
+ protocol:
+ name: 'icmp'
+ source:
+ any: true
+ destination:
+ host: '192.168.1.2'
+ protocol_options:
+ icmp:
+ type: 8
+ - sequence_num: 3
+ action: 'deny'
+ protocol:
+ number: 2
+ source:
+ any: true
+ destination:
+ any: true
+ - sequence_num: 4
+ action: 'deny'
+ protocol:
+ name: 'ip'
+ source:
+ any: true
+ destination:
+ any: true
+ vlan_id: 10
+ remark: 'Vlan10'
+ - name: 'test1'
+ remark: 'test_ip_acl'
+ rules:
+ - sequence_num: 1
+ action: 'permit'
+ protocol:
+ name: 'tcp'
+ source:
+ prefix: '10.0.0.0/8'
+ destination:
+ any: true
+ - sequence_num: 2
+ action: 'deny'
+ protocol:
+ name: 'udp'
+ source:
+ any: true
+ destination:
+ prefix: '20.1.0.0/16'
+ port_number:
+ gt: 1024
+ - sequence_num: 3
+ action: 'deny'
+ protocol:
+ name: 'ip'
+ source:
+ any: true
+ destination:
+ any: true
+ dscp:
+ value: 63
+ - address_family: 'ipv6'
+ acls:
+ - name: 'testv6'
+ rules:
+ - sequence_num: 2
+ action: 'deny'
+ protocol:
+ name: 'icmpv6'
+ source:
+ any: true
+ destination:
+ any: true
+ - name: 'testv6-1'
+ remark: 'test_ipv6_acl'
+ rules:
+ - sequence_num: 1
+ action: 'permit'
+ protocol:
+ name: 'ipv6'
+ source:
+ prefix: '1000::/16'
+ destination:
+ any: true
+ dscp:
+ af22: true
+ - sequence_num: 2
+ action: 'deny'
+ protocol:
+ name: 'tcp'
+ source:
+ any: true
+ destination:
+ prefix: '2000::1000:0/112'
+ port_number:
+ range:
+ begin: 100
+ end: 1000
+ - sequence_num: 3
+ action: 'permit'
+ protocol:
+ name: 'tcp'
+ source:
+ any: true
+ destination:
+ any: true
+ protocol_options:
+ tcp:
+ established: true
+ - sequence_num: 4
+ action: 'deny'
+ protocol:
+ name: 'udp'
+ source:
+ any: true
+ port_number:
+ eq: 3000
+ destination:
+ any: true
+ state: merged
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration ip access-list
+# !
+# ip access-list test
+# seq 1 permit ip host 192.168.1.2 any
+# seq 2 permit icmp any host 192.168.1.2 type 8
+# seq 3 deny 2 any any
+# seq 4 deny ip any any vlan 10 remark Vlan10
+# !
+# ip access-list test1
+# remark test_ip_acl
+# seq 1 permit tcp 10.0.0.0/8 any
+# seq 2 deny udp any 20.1.0.0/16 gt 1024
+# seq 3 deny ip any any dscp 63
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# !
+# ipv6 access-list testv6
+# seq 1 permit ipv6 host 192:168:1::2 any
+# seq 2 deny icmpv6 any any
+# !
+# ipv6 access-list testv6-1
+# remark test_ipv6_acl
+# seq 1 permit ipv6 1000::/16 any dscp af22
+# seq 2 deny tcp any 2000::1000:0/112 range 100 1000
+# seq 3 permit tcp any any established
+# seq 4 deny udp any eq 3000 any
+# sonic#
+
+
+# Using replaced
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration ip access-list
+# !
+# ip access-list test
+# seq 1 permit ip host 192.168.1.2 any
+# seq 2 permit icmp any host 192.168.1.2 type 8
+# seq 3 deny 2 any any
+# seq 4 deny ip any any vlan 10 remark Vlan10
+# !
+# ip access-list test1
+# remark test_ip_acl
+# seq 1 permit tcp 10.0.0.0/8 any
+# seq 2 deny udp any 20.1.0.0/16 gt 1024
+# seq 3 deny ip any any dscp 63
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# !
+# ipv6 access-list testv6
+# seq 1 permit tcp host 3000::1 any established
+# seq 2 permit udp any any
+# seq 3 deny icmpv6 any any
+# !
+# ipv6 access-list testv6-1
+# remark test_ipv6_acl
+# seq 1 permit ipv6 1000::/16 any dscp af22
+# seq 2 deny tcp any 2000::1000:0/112 range 100 1000
+# seq 3 permit tcp any any established
+# seq 4 deny udp any eq 3000 any
+# sonic#
+
+ - name: Replace device configuration of specified Layer 3 ACLs with provided configuration
+ dellemc.enterprise_sonic.sonic_l3_acls:
+ config:
+ - address_family: 'ipv4'
+ acls:
+ - name: 'test2'
+ rules:
+ - sequence_num: 1
+ action: 'permit'
+ protocol:
+ name: 'tcp'
+ source:
+ prefix: '192.168.1.0/24'
+ destination:
+ any: true
+ - address_family: 'ipv6'
+ acls:
+ - name: 'testv6'
+ rules:
+ - sequence_num: 1
+ action: 'permit'
+ protocol:
+ name: 'tcp'
+ source:
+ host: '3000::1'
+ destination:
+ any: true
+ protocol_options:
+ tcp:
+ ack: true
+ syn: true
+ fin: true
+ - sequence_num: 2
+ action: 'deny'
+ protocol:
+ name: 'ipv6'
+ source:
+ any: true
+ destination:
+ any: true
+ state: replaced
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration ip access-list
+# !
+# ip access-list test
+# seq 1 permit ip host 192.168.1.2 any
+# seq 2 permit icmp any host 192.168.1.3 type 8
+# seq 3 deny 2 any any
+# seq 4 deny ip any any vlan 10 remark Vlan10
+# !
+# ip access-list test1
+# remark test_ip_acl
+# seq 1 permit tcp 10.0.0.0/8 any
+# seq 2 deny udp any 20.1.0.0/16 gt 1024
+# seq 3 deny ip any any dscp 63
+# !
+# ip access-list test2
+# seq 1 permit tcp 192.168.1.0/24 any
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# !
+# ipv6 access-list testv6
+# seq 1 permit tcp host 3000::1 any fin syn ack
+# seq 2 deny ipv6 any any
+# !
+# ipv6 access-list testv6-1
+# remark test_ipv6_acl
+# seq 1 permit ipv6 1000::/16 any dscp af22
+# seq 2 deny tcp any 2000::1000:0/112 range 100 1000
+# seq 3 permit tcp any any established
+# seq 4 deny udp any eq 3000 any
+# sonic#
+
+
+# Using overridden
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration ip access-list
+# !
+# ip access-list test
+# seq 1 permit ip host 192.168.1.2 any
+# seq 2 permit icmp any host 192.168.1.3 type 8
+# seq 3 deny 2 any any
+# seq 4 deny ip any any vlan 10 remark Vlan10
+# !
+# ip access-list test1
+# remark test_ip_acl
+# seq 1 permit tcp 10.0.0.0/8 any
+# seq 2 deny udp any 20.1.0.0/16 gt 1024
+# seq 3 deny ip any any dscp 63
+# !
+# ip access-list test2
+# seq 1 permit tcp 192.168.1.0/24 any
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# !
+# ipv6 access-list testv6
+# seq 1 permit tcp 3000::/16 any
+# seq 2 deny ipv6 any any
+# !
+# ipv6 access-list testv6-1
+# remark test_ipv6_acl
+# seq 1 permit ipv6 1000::/16 any dscp af22
+# seq 2 deny tcp any 2000::1000:0/112 range 100 1000
+# seq 3 permit tcp any any established
+# seq 4 deny udp any eq 3000 any
+# sonic#
+
+ - name: Override device configuration of all Layer 3 ACLs with provided configuration
+ dellemc.enterprise_sonic.sonic_l3_acls:
+ config:
+ - address_family: 'ipv4'
+ acls:
+ - name: 'test_acl'
+ rules:
+ - sequence_num: 1
+ action: 'permit'
+ protocol:
+ name: 'ip'
+ source:
+ prefix: '100.1.1.0/24'
+ destination:
+ prefix: '100.1.2.0/24'
+ - sequence_num: 2
+ action: 'deny'
+ protocol:
+ name: 'udp'
+ source:
+ any: true
+ destination:
+ any: true
+ state: overridden
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration ip access-list
+# !
+# ip access-list test_acl
+# seq 1 permit ip 100.1.1.0/24 100.1.2.0/24
+# seq 2 deny udp any any
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# sonic#
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration ip access-list
+# !
+# ip access-list test
+# seq 1 permit ip host 192.168.1.2 any
+# seq 2 permit icmp any host 192.168.1.3 type 8
+# seq 3 deny 2 any any
+# seq 4 deny ip any any vlan 10 remark Vlan10
+# !
+# ip access-list test1
+# remark test_ip_acl
+# seq 1 permit tcp 10.0.0.0/8 any
+# seq 2 deny udp any 20.1.0.0/16 gt 1024
+# seq 3 deny ip any any dscp 63
+# !
+# ip access-list test2
+# seq 1 permit tcp 192.168.1.0/24 any
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# !
+# ipv6 access-list testv6
+# seq 1 permit tcp 3000::/16 any
+# seq 2 deny ipv6 any any
+# !
+# ipv6 access-list testv6-1
+# remark test_ipv6_acl
+# seq 1 permit ipv6 1000::/16 any dscp af22
+# seq 2 deny tcp any 2000::1000:0/112 range 100 1000
+# seq 3 permit tcp any any established
+# seq 4 deny udp any eq 3000 any
+# sonic#
+
+ - name: Delete specified Layer 3 ACLs, ACL remark and ACL rule entries
+ dellemc.enterprise_sonic.sonic_l3_acls:
+ config:
+ - address_family: 'ipv4'
+ acls:
+ - name: 'test'
+ rules:
+ - sequence_num: 2
+ - name: 'test2'
+ - address_family: 'ipv6'
+ acls:
+ - name: 'testv6-1'
+ remark: 'test_ipv6_acl'
+ rules:
+ - sequence_num: 3
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration ip access-list
+# !
+# ip access-list test
+# seq 1 permit ip host 192.168.1.2 any
+# seq 3 deny 2 any any
+# seq 4 deny ip any any vlan 10 remark Vlan10
+# !
+# ip access-list test1
+# remark test_ip_acl
+# seq 1 permit tcp 10.0.0.0/8 any
+# seq 2 deny udp any 20.1.0.0/16 gt 1024
+# seq 3 deny ip any any dscp 63
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# !
+# ipv6 access-list testv6
+# seq 1 permit tcp 3000::/16 any
+# seq 2 deny ipv6 any any
+# !
+# ipv6 access-list testv6-1
+# seq 1 permit ipv6 1000::/16 any dscp af22
+# seq 2 deny tcp any 2000::1000:0/112 range 100 1000
+# seq 4 deny udp any eq 3000 any
+# sonic#
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration ip access-list
+# !
+# ip access-list test
+# seq 1 permit ip host 192.168.1.2 any
+# seq 2 permit icmp any host 192.168.1.3 type 8
+# seq 3 deny 2 any any
+# seq 4 deny ip any any vlan 10 remark Vlan10
+# !
+# ip access-list test1
+# remark test_ip_acl
+# seq 1 permit tcp 10.0.0.0/8 any
+# seq 2 deny udp any 20.1.0.0/16 gt 1024
+# seq 3 deny ip any any dscp 63
+# !
+# ip access-list test2
+# seq 1 permit tcp 192.168.1.0/24 any
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# !
+# ipv6 access-list testv6
+# seq 1 permit tcp 3000::/16 any
+# seq 2 deny ipv6 any any
+# !
+# ipv6 access-list testv6-1
+# remark test_ipv6_acl
+# seq 1 permit ipv6 1000::/16 any dscp af22
+# seq 2 deny tcp any 2000::1000:0/112 range 100 1000
+# seq 3 permit tcp any any established
+# seq 4 deny udp any eq 3000 any
+# sonic#
+
+ - name: Delete all Layer 3 ACLs for an address-family
+ dellemc.enterprise_sonic.sonic_l3_acls:
+ config:
+ - address_family: 'ipv4'
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration ip access-list
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# !
+# ipv6 access-list testv6
+# seq 1 permit tcp 3000::/16 any
+# seq 2 deny ipv6 any any
+# !
+# ipv6 access-list testv6-1
+# remark test_ipv6_acl
+# seq 1 permit ipv6 1000::/16 any dscp af22
+# seq 2 deny tcp any 2000::1000:0/112 range 100 1000
+# seq 3 permit tcp any any established
+# seq 4 deny udp any eq 3000 any
+# sonic#
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration ip access-list
+# !
+# ip access-list test
+# seq 1 permit ip host 192.168.1.2 any
+# seq 2 permit icmp any host 192.168.1.3 type 8
+# seq 3 deny 2 any any
+# seq 4 deny ip any any vlan 10 remark Vlan10
+# !
+# ip access-list test1
+# remark test_ip_acl
+# seq 1 permit tcp 10.0.0.0/8 any
+# seq 2 deny udp any 20.1.0.0/16 gt 1024
+# seq 3 deny ip any any dscp 63
+# !
+# ip access-list test2
+# seq 1 permit tcp 192.168.1.0/24 any
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# !
+# ipv6 access-list testv6
+# seq 1 permit tcp 3000::/16 any
+# seq 2 deny ipv6 any any
+# !
+# ipv6 access-list testv6-1
+# remark test_ipv6_acl
+# seq 1 permit ipv6 1000::/16 any dscp af22
+# seq 2 deny tcp any 2000::1000:0/112 range 100 1000
+# seq 3 permit tcp any any established
+# seq 4 deny udp any eq 3000 any
+# sonic#
+
+ - name: Delete all Layer 3 ACL configurations
+ dellemc.enterprise_sonic.sonic_l3_acls:
+ config:
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration ip access-list
+# sonic#
+# sonic# show running-configuration ipv6 access-list
+# sonic#
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after(generated):
+ description: The generated configuration model invocation.
+ returned: when C(check_mode)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.l3_acls.l3_acls import L3_aclsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.l3_acls.l3_acls import L3_acls
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=L3_aclsArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = L3_acls(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l3_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l3_interfaces.py
index e796897a5..1ebc11994 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l3_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l3_interfaces.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-# Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -29,6 +29,13 @@ The module file for sonic_l3_interfaces
from __future__ import absolute_import, division, print_function
__metaclass__ = type
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community',
+ 'license': 'Apache 2.0'
+}
+
DOCUMENTATION = """
---
module: sonic_l3_interfaces
@@ -44,7 +51,8 @@ description:
author: Kumaraguru Narayanan (@nkumaraguru)
options:
config:
- description: A list of l3_interfaces configurations.
+ description:
+ - A list of l3_interfaces configurations.
type: list
elements: dict
suboptions:
@@ -101,11 +109,13 @@ options:
type: bool
state:
description:
- - The state that the configuration should be left in.
+ - The state of the configuration after module completion.
type: str
choices:
- - merged
- - deleted
+ - merged
+ - deleted
+ - replaced
+ - overridden
default: merged
"""
EXAMPLES = """
@@ -328,7 +338,181 @@ EXAMPLES = """
# ip anycast-address 11.12.13.14/12
#!
#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+#rno-dctor-1ar01c01sw02# show running-configuration interface
+#!
+#interface Ethernet20
+# mtu 9100
+# speed 100000
+# shutdown
+# ip address 83.1.1.1/16
+# ip address 84.1.1.1/16 secondary
+# ipv6 address 83::1/16
+# ipv6 address 84::1/16
+# ipv6 enable
+#!
+#interface Ethernet24
+# mtu 9100
+# speed 100000
+# shutdown
+# ip address 91.1.1.1/16
+# ipv6 address 90::1/16
+# ipv6 address 91::1/16
+# ipv6 address 92::1/16
+# ipv6 address 93::1/16
+#!
+#
+- name: Replace l3 interface
+ dellemc.enterprise_sonic.sonic_l3_interfaces:
+ config:
+ - name: Ethernet20
+ ipv4:
+ - address: 81.1.1.1/16
+ state: replaced
+
+# After state:
+# ------------
#
+#rno-dctor-1ar01c01sw02# show running-configuration interface
+#!
+#interface Ethernet20
+# mtu 9100
+# speed 100000
+# shutdown
+# ip address 81.1.1.1/16
+#!
+#interface Ethernet24
+# mtu 9100
+# speed 100000
+# shutdown
+# ip address 91.1.1.1/16
+# ipv6 address 90::1/16
+# ipv6 address 91::1/16
+# ipv6 address 92::1/16
+# ipv6 address 93::1/16
+#!
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+#rno-dctor-1ar01c01sw02# show running-configuration interface
+#!
+#interface Ethernet20
+# mtu 9100
+# speed 100000
+# shutdown
+# ip address 83.1.1.1/16
+# ip address 84.1.1.1/16 secondary
+# ipv6 address 83::1/16
+# ipv6 address 84::1/16
+# ipv6 enable
+#!
+#interface Ethernet24
+# mtu 9100
+# speed 100000
+# shutdown
+# ip address 91.1.1.1/16
+# ipv6 address 90::1/16
+# ipv6 address 91::1/16
+# ipv6 address 92::1/16
+# ipv6 address 93::1/16
+#!
+- name: Replace l3 interface
+ dellemc.enterprise_sonic.sonic_l3_interfaces:
+ config:
+ - name: Ethernet20
+ state: replaced
+
+# After state:
+# ------------
+#
+#rno-dctor-1ar01c01sw02# show running-configuration interface
+#!
+#interface Ethernet20
+# mtu 9100
+# speed 100000
+# shutdown
+#!
+#interface Ethernet24
+# mtu 9100
+# speed 100000
+# shutdown
+# ip address 91.1.1.1/16
+# ipv6 address 90::1/16
+# ipv6 address 91::1/16
+# ipv6 address 92::1/16
+# ipv6 address 93::1/16
+#!
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+#rno-dctor-1ar01c01sw02# show running-configuration interface
+#!
+#interface Ethernet20
+# mtu 9100
+# speed 100000
+# shutdown
+# ip address 83.1.1.1/16
+# ip address 84.1.1.1/16 secondary
+# ipv6 address 83::1/16
+# ipv6 address 84::1/16
+# ipv6 enable
+#!
+#interface Ethernet24
+# mtu 9100
+# speed 100000
+# shutdown
+# ip address 91.1.1.1/16
+# ipv6 address 90::1/16
+# ipv6 address 91::1/16
+# ipv6 address 92::1/16
+# ipv6 address 93::1/16
+#!
+#
+- name: Override l3 interface
+ dellemc.enterprise_sonic.sonic_l3_interfaces:
+ config:
+ - name: Ethernet24
+ ipv4:
+ - address: 81.1.1.1/16
+ - name: Vlan100
+ ipv4:
+ anycast_addresses:
+ - 83.1.1.1/24
+ - 85.1.1.12/24
+ state: overridden
+
+# After state:
+# ------------
+#
+#rno-dctor-1ar01c01sw02# show running-configuration interface
+#!
+#interface Ethernet20
+# mtu 9100
+# speed 100000
+# shutdown
+#!
+#interface Ethernet24
+# mtu 9100
+# speed 100000
+# shutdown
+# ip address 81.1.1.1/16
+#!
+#interface Vlan100
+# ip anycast-address 83.1.1.1/24
+# ip anycast-address 85.1.1.12/24
+#!
+
+
"""
RETURN = """
before:
@@ -336,14 +520,14 @@ before:
returned: always
type: list
sample: >
- The configuration returned is always in the same format
+ The configuration returned will always be in the same format
of the parameters above.
after:
description: The resulting configuration model invocation.
returned: when changed
type: list
sample: >
- The configuration returned is always in the same format
+ The configuration returned will always be in the same format
of the parameters above.
commands:
description: The set of commands pushed to the remote device.
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_lag_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_lag_interfaces.py
index 630db7985..0fd0d8b7e 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_lag_interfaces.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_lag_interfaces.py
@@ -80,6 +80,8 @@ options:
type: str
choices:
- merged
+ - replaced
+ - overridden
- deleted
default: merged
"""
@@ -124,6 +126,111 @@ EXAMPLES = """
# speed 100000
# no shutdown
#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+# interface Eth1/5
+# channel-group 10
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+# interface Eth1/6
+# channel-group 20
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+# interface Eth1/7
+# no channel-group
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+- name: Replace device configuration of specified LAG attributes
+ dellemc.enterprise_sonic.sonic_lag_interfaces:
+ config:
+ - name: PortChannel10
+ members:
+ interfaces:
+ - member: Eth1/7
+ state: replaced
+#
+# After state:
+# ------------
+#
+# interface Eth1/5
+# no channel-group
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+# interface Eth1/6
+# channel-group 20
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+# interface Eth1/7
+# channel-group 10
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+# interface Eth1/5
+# channel-group 10
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+# interface Eth1/6
+# no channel-group
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+# interface Eth1/7
+# channel-group 2
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+- name: Override device configuration of all LAG attributes
+ dellemc.enterprise_sonic.sonic_lag_interfaces:
+ config:
+ - name: PortChannel20
+ members:
+ interfaces:
+ - member: Eth1/6
+ state: overridden
+#
+# After state:
+# ------------
+# interface Eth1/5
+# no channel-group
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+# interface Eth1/6
+# channel-group 20
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+# interface Eth1/7
+# no channel-group
+# mtu 9100
+# speed 100000
+# no shutdown
+#
# Using deleted
#
# Before state:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_lldp_global.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_lldp_global.py
new file mode 100644
index 000000000..6577d21d0
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_lldp_global.py
@@ -0,0 +1,301 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_lldp_global
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: sonic_lldp_global
+version_added: '2.1.0'
+short_description: Manage Global LLDP configurations on SONiC
+description:
+ - This module provides configuration management of global LLDP parameters
+ for use on LLDP enabled Layer 2 interfaces of devices running SONiC.
+ - It is intended for use in conjunction with LLDP Layer 2 interface
+ configuration applied on participating interfaces.
+author: 'Divya Balasubramanian(@divya-balasubramania)'
+options:
+ config:
+ description: The set of link layer discovery protocol global attribute configurations
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - This argument is a boolean value to enable or disable LLDP.
+ type: bool
+ multiplier:
+ description:
+ - Multiplier value is used to determine the timeout interval (i.e. hello-time x multiplier value)
+ - The range is from 1 to 10
+ type: int
+ system_description:
+ description:
+ - Description of this system to be sent in LLDP advertisements.
+ - When configured, this value is used in the advertisements
+ instead of the default system description.
+ type: str
+ system_name:
+ description:
+ - Specifying a descriptive system name using this command, user may find it easier to distinguish the device with LLDP.
+ - By default, the host name is used.
+ type: str
+ mode:
+ description:
+ - By default both transmit and receive of LLDP frames is enabled.
+ - This command can be used to configure either in receive only or transmit only mode.
+ type: str
+ choices:
+ - receive
+ - transmit
+ hello_time:
+ description:
+ - Frequency at which LLDP advertisements are sent (in seconds).
+ - The range is from 5 to 254 sec
+ type: int
+ tlv_select:
+ description:
+ - By default, management address and system capabilities TLV are advertised in LLDP frames.
+ - This configuration option can be used to selectively suppress sending of these TLVs
+ to the Peer.
+ type: dict
+ suboptions:
+ management_address:
+ description:
+ - Enable or disable management address TLV.
+ type: bool
+ system_capabilities:
+ description:
+ - Enable or disable system capabilities TLV.
+ type: bool
+ state:
+ description:
+ - The state specifies the type of configuration update to be performed on the device.
+ - If the state is "merged", merge specified attributes with existing configured attributes.
+ - For "deleted", delete the specified attributes from existing configuration.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration
+# !
+# lldp receive
+# lldp timer 200
+# lldp multiplier 1
+# lldp system-name 8999_System
+# lldp system-description sonic_system
+# !
+
+ - name: Delete LLDP configurations
+ dellemc.enterprise_sonic.sonic_lldp_global:
+ config:
+ hello_time: 200
+ system_description : sonic_system
+ mode: receive
+ multiplier: 1
+ state: deleted
+
+# After State:
+# ------------
+# sonic# show running-configuration | grep lldp
+# !
+# lldp system-name 8999_System
+# !
+# sonic#
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration | grep lldp
+# sonic#
+
+ - name: Delete default LLDP configurations
+ dellemc.enterprise_sonic.sonic_lldp_global:
+ config:
+ tlv_select:
+ system_capabilities: true
+ state: deleted
+
+# After State:
+# ------------
+# sonic# show running-configuration
+# !
+# no lldp tlv-select system-capabilities
+# !
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration | grep lldp
+# !
+# lldp receive
+# lldp timer 200
+# lldp multiplier 1
+# lldp system-name 8999_System
+# lldp system-description sonic_system
+# !
+
+ - name: Delete all LLDP configuration
+ dellemc.enterprise_sonic.sonic_lldp_global:
+ config:
+ state: deleted
+
+# After State: (No LLDP global configuration present.)
+# ------------
+# sonic# show running-configuration | grep lldp
+# sonic#
+
+
+# Using Merged
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration | grep lldp
+# sonic#
+
+ - name: Modify LLDP configurations
+ dellemc.enterprise_sonic.sonic_lldp_global:
+ config:
+ enable: false
+ multiplier: 9
+ system_name : CR_sonic
+ hello_time: 18
+ mode: receive
+ system_description: Sonic_System
+ tlv_select:
+ management_address: true
+ system_capabilities: false
+ state: merged
+
+# After State:
+# ------------
+# sonic# show running-configuration | grep lldp
+# !
+# no lldp enable
+# no lldp tlv-select system_capabilities
+# lldp receive
+# lldp timer 18
+# lldp multiplier 9
+# lldp system-name CR_sonic
+# lldp system-description Sonic_System
+# !
+
+
+# Using Merged
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration | grep lldp
+# !
+# lldp receive
+# lldp timer 200
+# lldp multiplier 1
+# lldp system-name 8999_System
+# lldp system-description sonic_system
+# !
+
+ - name: Modify LLDP configurations
+ dellemc.enterprise_sonic.sonic_lldp_global:
+ config:
+ multiplier: 9
+ system_name : CR_sonic
+ state: merged
+
+# After State:
+# ------------
+# sonic# show running-configuration | grep lldp
+# !
+# lldp receive
+# lldp timer 200
+# lldp multiplier 9
+# lldp system-name CR_sonic
+# lldp system-description sonic_system
+# !
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+ type: list
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+ type: list
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.lldp_global.lldp_global import Lldp_globalArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.lldp_global.lldp_global import Lldp_global
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Lldp_globalArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Lldp_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_logging.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_logging.py
new file mode 100644
index 000000000..d8c592874
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_logging.py
@@ -0,0 +1,274 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_logging
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: sonic_logging
+version_added: 2.1.0
+notes:
+ - Supports C(check_mode).
+short_description: Manage logging configuration on SONiC.
+description:
+ - This module provides configuration management of logging for devices running SONiC.
+author: "M. Zhang (@mingjunzhang2019)"
+options:
+ config:
+ description:
+ - Specifies logging related configurations.
+ type: dict
+ suboptions:
+ remote_servers:
+ type: list
+ elements: dict
+ description:
+ - Remote logging sever configuration.
+ suboptions:
+ host:
+ type: str
+ description:
+ - IPv4/IPv6 address or host name of the remote logging server.
+ required: true
+ remote_port:
+ type: int
+ description:
+ - Destination port number for logging messages sent to the server.
+ - remote_port can not be deleted.
+ source_interface:
+ type: str
+ description:
+ - Source interface used as source ip for sending logging packets.
+ - source_interface can not be deleted.
+ message_type:
+ type: str
+ description:
+ - Type of messages that remote server receives.
+ - message_type can not be deleted.
+ choices:
+ - log
+ - event
+ vrf:
+ type: str
+ description:
+ - VRF name used by remote logging server.
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#sonic# show logging servers
+#--------------------------------------------------------------------------------
+#HOST PORT SOURCE-INTERFACE VRF MESSGE-TYPE
+#--------------------------------------------------------------------------------
+#10.11.0.2 5 Ethernet24 - event
+#10.11.1.1 616 Ethernet8 - log
+#log1.dell.com 6 Ethernet28 - log
+#
+- name: Delete logging server configuration
+ sonic_logging:
+ config:
+ remote_servers:
+ - host: 10.11.0.2
+ - host: log1.dell.com
+ state: deleted
+
+# After state:
+# ------------
+#
+#sonic# show logging servers
+#--------------------------------------------------------------------------------
+#HOST PORT SOURCE-INTERFACE VRF MESSGE-TYPE
+#--------------------------------------------------------------------------------
+#10.11.1.1 616 Ethernet8 - log
+#
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#sonic# show logging servers
+#--------------------------------------------------------------------------------
+#HOST PORT SOURCE-INTERFACE VRF MESSGE-TYPE
+#--------------------------------------------------------------------------------
+#10.11.1.1 616 Ethernet8 - log
+#
+- name: Merge logging server configuration
+ sonic_logging:
+ config:
+ remote_servers:
+ - host: 10.11.0.2
+ remote_port: 5
+ source_interface: Ethernet24
+ message_type: event
+ - host: log1.dell.com
+ remote_port: 6
+ source_interface: Ethernet28
+ state: merged
+
+# After state:
+# ------------
+#
+#sonic# show logging servers
+#--------------------------------------------------------------------------------
+#HOST PORT SOURCE-INTERFACE VRF MESSGE-TYPE
+#--------------------------------------------------------------------------------
+#10.11.0.2 5 Ethernet24 - event
+#10.11.1.1 616 Ethernet8 - log
+#log1.dell.com 6 Ethernet28 - log
+#
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+#sonic# show logging servers
+#--------------------------------------------------------------------------------
+#HOST PORT SOURCE-INTERFACE VRF MESSGE-TYPE
+#--------------------------------------------------------------------------------
+#10.11.1.1 616 Ethernet8 - log
+#10.11.1.2 626 Ethernet16 - event
+#
+- name: Replace logging server configuration
+ sonic_logging:
+ config:
+ remote_servers:
+ - host: 10.11.1.2
+ remote_port: 622
+ source_interface: Ethernet24
+ message_type: event
+ state: overridden
+#
+# After state:
+# ------------
+#
+#sonic# show logging servers
+#--------------------------------------------------------------------------------
+#HOST PORT SOURCE-INTERFACE VRF MESSGE-TYPE
+#--------------------------------------------------------------------------------
+#10.11.1.2 622 Ethernet24 - event
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+#sonic# show logging servers
+#--------------------------------------------------------------------------------
+#HOST PORT SOURCE-INTERFACE VRF MESSGE-TYPE
+#--------------------------------------------------------------------------------
+#10.11.1.1 616 Ethernet8 - log
+#10.11.1.2 626 Ethernet16 - event
+#
+- name: Replace logging server configuration
+ sonic_logging:
+ config:
+ remote_servers:
+ - host: 10.11.1.2
+ remote_port: 622
+ state: replaced
+#
+# After state:
+# ------------
+#
+# "MESSAGE-TYPE" has default value of "log"
+#
+#sonic# show logging servers
+#--------------------------------------------------------------------------------
+#HOST PORT SOURCE-INTERFACE VRF MESSGE-TYPE
+#--------------------------------------------------------------------------------
+#10.11.1.1 616 Ethernet8 - log
+#10.11.1.2 622 - - log
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ type: list
+ returned: always
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after(generated):
+ description: The generated configuration model invocation.
+ returned: when C(check_mode)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.logging.logging import LoggingArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.logging.logging import Logging
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=LoggingArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Logging(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_mac.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_mac.py
new file mode 100644
index 000000000..42acfd4fb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_mac.py
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_mac
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'
+}
+
+DOCUMENTATION = """
+---
+module: sonic_mac
+version_added: "2.1.0"
+short_description: Manage MAC configuration on SONiC
+description:
+ - This module provides configuration management of MAC for devices running SONiC
+author: "Shade Talabi (@stalabi1)"
+options:
+ config:
+ description:
+ - A list of MAC configurations.
+ type: list
+ elements: dict
+ suboptions:
+ vrf_name:
+ description:
+ - Specifies the VRF name.
+ type: str
+ default: 'default'
+ mac:
+ description:
+ - Configuration attributes for MAC.
+ type: dict
+ suboptions:
+ aging_time:
+ description:
+ - Time in seconds of inactivity before the MAC entry is timed out.
+ type: int
+ default: 600
+ dampening_interval:
+ description:
+ - Interval for which mac movements are observed before disabling MAC learning on a port.
+ type: int
+ default: 5
+ dampening_threshold:
+ description:
+ - Number of MAC movements allowed per second before disabling MAC learning on a port.
+ type: int
+ default: 5
+ mac_table_entries:
+ description:
+ - Configuration attributes for MAC table entries.
+ type: list
+ elements: dict
+ suboptions:
+ mac_address:
+ description:
+ - MAC address for the dynamic or static MAC table entry.
+ type: str
+ required: True
+ vlan_id:
+ description:
+ - ID number of VLAN on which the MAC address is present.
+ type: int
+ required: True
+ interface:
+ description:
+ - Specifies the interface for the MAC table entry.
+ type: str
+ state:
+ description:
+ - The state of the configuration after module completion
+ type: str
+ choices: ['merged', 'deleted', 'replaced', 'overridden']
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+#
+# Before state:
+# -------------
+#
+# sonic# show mac dampening
+# MAC Move Dampening Threshold : 5
+# MAC Move Dampening Interval : 5
+# sonic# show running-configuration | grep mac
+# (No mac configuration pressent)
+
+ - name: Merge MAC configurations
+ dellemc.enterprise_sonic.sonic_mac:
+ config:
+ - vrf_name: 'default'
+ mac:
+ aging_time: 50
+ dampening_interval: 20
+ dampening_threshold: 30
+ mac_table_entries:
+ - mac_address: '00:00:5e:00:53:af'
+ vlan_id: 1
+ interface: 'Ethernet20'
+ - mac_address: '00:33:33:33:33:33'
+ vlan_id: 2
+ interface: 'Ethernet24'
+ - mac_address: '00:00:4e:00:24:af'
+ vlan_id: 3
+ interface: 'Ethernet28'
+ state: merged
+
+# After state:
+# ------------
+#
+# sonic# show mac dampening
+# MAC Move Dampening Threshold : 30
+# MAC Move Dampening Interval : 20
+# sonic# show running-configuration | grep mac
+# mac address-table 00:00:5e:00:53:af Vlan1 Ethernet20
+# mac address-table 00:33:33:33:33:33 Vlan2 Ethernet24
+# mac address-table 00:00:4e:00:24:af Vlan3 Ethernet28
+# mac address-table aging-time 50
+#
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+# sonic# show mac dampening
+# MAC Move Dampening Threshold : 30
+# MAC Move Dampening Interval : 20
+# sonic# show running-configuration | grep mac
+# mac address-table 00:00:5e:00:53:af Vlan1 Ethernet20
+# mac address-table 00:33:33:33:33:33 Vlan2 Ethernet24
+# mac address-table 00:00:4e:00:24:af Vlan3 Ethernet28
+# mac address-table aging-time 50
+
+ - name: Replace MAC configurations
+ dellemc.enterprise_sonic.sonic_mac:
+ config:
+ - vrf_name: 'default'
+ mac:
+ aging_time: 45
+ dampening_interval: 30
+ dampening_threshold: 60
+ mac_table_entries:
+ - mac_address: '00:00:5e:00:53:af'
+ vlan_id: 3
+ interface: 'Ethernet24'
+ - mac_address: '00:44:44:44:44:44'
+ vlan_id: 2
+ interface: 'Ethernet20'
+ state: replaced
+
+# sonic# show mac dampening
+# MAC Move Dampening Threshold : 60
+# MAC Move Dampening Interval : 30
+# sonic# show running-configuration | grep mac
+# mac address-table 00:00:5e:00:53:af Vlan3 Ethernet24
+# mac address-table 00:33:33:33:33:33 Vlan2 Ethernet24
+# mac address-table 00:00:4e:00:24:af Vlan3 Ethernet28
+# mac address-table 00:44:44:44:44:44 Vlan2 Ethernet20
+# mac address-table aging-time 45
+#
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+# sonic# show mac dampening
+# MAC Move Dampening Threshold : 60
+# MAC Move Dampening Interval : 30
+# sonic# show running-configuration | grep mac
+# mac address-table 00:00:5e:00:53:af Vlan3 Ethernet24
+# mac address-table 00:33:33:33:33:33 Vlan2 Ethernet24
+# mac address-table 00:00:4e:00:24:af Vlan3 Ethernet28
+# mac address-table 00:44:44:44:44:44 Vlan2 Ethernet20
+# mac address-table aging-time 45
+
+ - name: Override MAC cofigurations
+ dellemc.enterprise_sonic.sonic_mac:
+ config:
+ - vrf_name: 'default'
+ mac:
+ aging_time: 10
+ dampening_interval: 20
+ dampening_threshold: 30
+ mac_table_entries:
+ - mac_address: '00:11:11:11:11:11'
+ vlan_id: 1
+ interface: 'Ethernet20'
+ - mac_address: '00:22:22:22:22:22'
+ vlan_id: 2
+ interface: 'Ethernet24'
+ state: overridden
+
+# After state:
+# ------------
+#
+# sonic# show mac dampening
+# MAC Move Dampening Threshold : 30
+# MAC Move Dampening Interval : 20
+# sonic# show running-configuration | grep mac
+# mac address-table 00:11:11:11:11:11 Vlan1 Ethernet20
+# mac address-table 00:22:22:22:22:22 Vlan2 Ethernet24
+# mac address-table aging-time 10
+#
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# sonic# show mac dampening
+# MAC Move Dampening Threshold : 30
+# MAC Move Dampening Interval : 20
+# sonic# show running-configuration | grep mac
+# mac address-table 00:11:11:11:11:11 Vlan1 Ethernet20
+# mac address-table 00:22:22:22:22:22 Vlan2 Ethernet24
+# mac address-table aging-time 10
+
+ - name: Delete MAC cofigurations
+ dellemc.enterprise_sonic.sonic_mac:
+ config:
+ - vrf_name: 'default'
+ mac:
+ aging_time: 10
+ dampening_interval: 20
+ dampening_threshold: 30
+ mac_table_entries:
+ - mac_address: '00:11:11:11:11:11'
+ vlan_id: 1
+ interface: 'Ethernet20'
+ - mac_address: '00:22:22:22:22:22'
+ vlan_id: 2
+ interface: 'Ethernet24'
+ state: deleted
+
+# After state:
+# ------------
+#
+# sonic# show mac dampening
+# MAC Move Dampening Threshold : 5
+# MAC Move Dampening Interval : 5
+# sonic# show running-configuration | grep mac
+# (No mac configuration pressent)
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.mac.mac import MacArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.mac.mac import Mac
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=MacArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Mac(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_mclag.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_mclag.py
index 28d3dbb5b..e17fe080c 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_mclag.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_mclag.py
@@ -34,11 +34,11 @@ DOCUMENTATION = """
module: sonic_mclag
version_added: 1.0.0
notes:
-- Tested against Enterprise SONiC Distribution by Dell Technologies.
-- Supports C(check_mode).
+ - Tested against Enterprise SONiC Distribution by Dell Technologies.
+ - Supports C(check_mode).
short_description: Manage multi chassis link aggregation groups domain (MCLAG) and its parameters
description:
- - Manage multi chassis link aggregation groups domain (MCLAG) and its parameters
+ - Manage multi chassis link aggregation groups domain (MCLAG) and its parameters.
author: Abirami N (@abirami-n)
options:
@@ -65,7 +65,7 @@ options:
type: str
system_mac:
description:
- - Mac address of MCLAG.
+ - MAC address of MCLAG.
type: str
keepalive:
description:
@@ -75,17 +75,45 @@ options:
description:
- MCLAG session timeout value in secs.
type: int
+ delay_restore:
+ description:
+ - MCLAG delay restore time in secs.
+ type: int
+ gateway_mac:
+ description:
+ - Gateway MAC address for router ports over MCLAG.
+ - Configured gateway MAC address can be modified only when I(state=replaced) or I(state=overridden).
+ type: str
unique_ip:
- description: Holds Vlan dictionary for mclag unique ip.
+ description: Holds Vlan dictionary for MCLAG unique IP.
+ suboptions:
+ vlans:
+ description:
+ - Holds a list of VLANs and VLAN ranges for which a separate IP address is enabled for Layer 3 protocol support over MCLAG.
+ type: list
+ elements: dict
+ suboptions:
+ vlan:
+ description:
+ - Holds a VLAN name or VLAN range.
+ - Specify a single VLAN eg. Vlan10.
+ - Specify a range of VLANs eg. Vlan10-20.
+ type: str
+ type: dict
+ peer_gateway:
+ description: Holds Vlan dictionary for MCLAG peer gateway.
suboptions:
vlans:
description:
- - Holds list of VLANs for which a separate IP addresses is enabled for Layer 3 protocol support over MCLAG.
+ - Holds a list of VLANs and VLAN ranges for which MCLAG peer gateway functionality is enabled.
type: list
elements: dict
suboptions:
vlan:
- description: Holds a VLAN ID.
+ description:
+ - Holds a VLAN name or VLAN range.
+ - Specify a single VLAN eg. Vlan10.
+ - Specify a range of VLANs eg. Vlan10-20.
type: str
type: dict
members:
@@ -106,8 +134,10 @@ options:
- The state that the configuration should be left in.
type: str
choices:
- - merged
- - deleted
+ - merged
+ - deleted
+ - replaced
+ - overridden
default: merged
"""
EXAMPLES = """
@@ -118,7 +148,7 @@ EXAMPLES = """
#
# sonic# show mclag brief
# MCLAG Not Configured
-#
+
- name: Merge provided configuration with device configuration
dellemc.enterprise_sonic.sonic_mclag:
config:
@@ -128,14 +158,22 @@ EXAMPLES = """
peer_link: 'Portchannel1'
keepalive: 1
session_timeout: 3
+ delay_restore: 240
+ system_mac: '00:00:00:11:11:11'
+ gateway_mac: '00:00:00:12:12:12'
unique_ip:
- vlans:
- - vlan: Vlan4
+ vlans:
+ - vlan: Vlan4
+ - vlan: Vlan21-25
+ peer_gateway:
+ vlans:
+ - vlan: Vlan4
+ - vlan: Vlan21-25
members:
- portchannles:
- - lag: PortChannel10
+ portchannels:
+ - lag: PortChannel10
state: merged
-#
+
# After state:
# ------------
#
@@ -150,7 +188,10 @@ EXAMPLES = """
# Peer Link : PortChannel1
# Keepalive Interval : 1 secs
# Session Timeout : 3 secs
+# Delay Restore : 240 secs
# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
#
#
# Number of MLAG Interfaces:1
@@ -159,18 +200,34 @@ EXAMPLES = """
#-----------------------------------------------------------
# PortChannel10 down/down
#
-# admin@sonic:~$ show runningconfiguration all
-# {
-# ...
-# "MCLAG_UNIQUE_IP": {
-# "Vlan4": {
-# "unique_ip": "enable"
-# }
-# },
-# ...
-# }
-#
-#
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# ==============
+# Total count : 6
+# ==============
+# sonic#
+# sonic# show mclag peer-gateway-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# ==============
+# Total count : 6
+# ==============
+# sonic#
+
+
# Using merged
#
# Before state:
@@ -187,7 +244,10 @@ EXAMPLES = """
# Peer Link : PortChannel1
# Keepalive Interval : 1 secs
# Session Timeout : 3 secs
+# Delay Restore : 240 secs
# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
#
#
# Number of MLAG Interfaces:1
@@ -196,18 +256,33 @@ EXAMPLES = """
#-----------------------------------------------------------
# PortChannel10 down/down
#
-# admin@sonic:~$ show runningconfiguration all
-# {
-# ...
-# "MCLAG_UNIQUE_IP": {
-# "Vlan4": {
-# "unique_ip": "enable"
-# }
-# },
-# ...
-# }
-#
-#
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# ==============
+# Total count : 6
+# ==============
+# sonic#
+# sonic# show mclag peer-gateway-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# ==============
+# Total count : 6
+# ==============
+# sonic#
+
- name: Merge device configuration with the provided configuration
dellemc.enterprise_sonic.sonic_mclag:
config:
@@ -215,14 +290,20 @@ EXAMPLES = """
source_address: 3.3.3.3
keepalive: 10
session_timeout: 30
+ delay_restore: 360
unique_ip:
vlans:
- vlan: Vlan5
+ - vlan: Vlan26-28
+ peer_gateway:
+ vlans:
+ - vlan: Vlan5
+ - vlan: Vlan26-28
members:
portchannels:
- lag: PortChannel12
state: merged
-#
+
# After state:
# ------------
#
@@ -237,7 +318,10 @@ EXAMPLES = """
# Peer Link : PortChannel1
# Keepalive Interval : 10 secs
# Session Timeout : 30 secs
+# Delay Restore : 360 secs
# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
#
#
# Number of MLAG Interfaces:2
@@ -247,21 +331,41 @@ EXAMPLES = """
# PortChannel10 down/down
# PortChannel12 down/down
#
-# admin@sonic:~$ show runningconfiguration all
-# {
-# ...
-# "MCLAG_UNIQUE_IP": {
-# "Vlan4": {
-# "unique_ip": "enable"
-# },
-# "Vlan5": {
-# "unique_ip": "enable"
-# }
-# },
-# ...
-# }
-#
-#
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan5
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# Vlan26
+# Vlan27
+# Vlan28
+# ==============
+# Total count : 10
+# ==============
+# sonic# show mclag peer-gateway-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan5
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# Vlan26
+# Vlan27
+# Vlan28
+# ==============
+# Total count : 10
+# ==============
+# sonic#
+
+
# Using deleted
#
# Before state:
@@ -278,7 +382,10 @@ EXAMPLES = """
# Peer Link : PortChannel1
# Keepalive Interval : 10 secs
# Session Timeout : 30 secs
+# Delay Restore : 360 secs
# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
#
#
# Number of MLAG Interfaces:1
@@ -287,28 +394,52 @@ EXAMPLES = """
#-----------------------------------------------------------
# PortChannel10 down/down
#
-# admin@sonic:~$ show runningconfiguration all
-# {
-# ...
-# "MCLAG_UNIQUE_IP": {
-# "Vlan4": {
-# "unique_ip": "enable"
-# }
-# },
-# ...
-# }
-#
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# ==============
+# Total count : 6
+# ==============
+# sonic#
+# sonic# show mclag peer-gateway-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# ==============
+# Total count : 6
+# ==============
+# sonic#
+
- name: Delete device configuration based on the provided configuration
dellemc.enterprise_sonic.sonic_mclag:
- config:
- domain_id: 1
- source_address: 3.3.3.3
- keepalive: 10
- members:
- portchannels:
- - lag: PortChannel10
- state: deleted
-#
+ config:
+ domain_id: 1
+ source_address: 3.3.3.3
+ keepalive: 10
+ unique_ip:
+ vlans:
+ - vlan: Vlan22
+ - vlan: Vlan24-25
+ peer_gateway:
+ vlans:
+ - vlan: Vlan22
+ - vlan: Vlan24-25
+ members:
+ portchannels:
+ - lag: PortChannel10
+ state: deleted
+
# After state:
# ------------
#
@@ -322,25 +453,37 @@ EXAMPLES = """
# Peer Address : 1.1.1.1
# Peer Link : PortChannel1
# Keepalive Interval : 1 secs
-# Session Timeout : 15 secs
+# Session Timeout : 30 secs
+# Delay Restore : 360 secs
# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
#
#
# Number of MLAG Interfaces:0
#
-# admin@sonic:~$ show runningconfiguration all
-# {
-# ...
-# "MCLAG_UNIQUE_IP": {
-# "Vlan4": {
-# "unique_ip": "enable"
-# }
-# },
-# ...
-# }
-#
-#
-#
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan23
+# ==============
+# Total count : 3
+# ==============
+# sonic#
+# sonic# show mclag peer-gateway-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan23
+# ==============
+# Total count : 3
+# ==============
+# sonic#
+
+
# Using deleted
#
# Before state:
@@ -357,7 +500,10 @@ EXAMPLES = """
# Peer Link : PortChannel1
# Keepalive Interval : 10 secs
# Session Timeout : 30 secs
+# Delay Restore : 360 secs
# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
#
#
# Number of MLAG Interfaces:1
@@ -366,32 +512,40 @@ EXAMPLES = """
#-----------------------------------------------------------
# PortChannel10 down/down
#
-# admin@sonic:~$ show runningconfiguration all
-# {
-# ...
-# "MCLAG_UNIQUE_IP": {
-# "Vlan4": {
-# "unique_ip": "enable"
-# }
-# },
-# ...
-# }
-#
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# ==============
+# Total count : 1
+# ==============
+# sonic#
+# sonic# show mclag peer-gateway-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# ==============
+# Total count : 1
+# ==============
+# sonic#
+
- name: Delete all device configuration
dellemc.enterprise_sonic.sonic_mclag:
config:
state: deleted
-#
+
# After state:
# ------------
#
# sonic# show mclag brief
# MCLAG Not Configured
-#
-# admin@sonic:~$ show runningconfiguration all | grep MCLAG_UNIQUE_IP
-# admin@sonic:~$
-#
-#
+# sonic# show mclag separate-ip-interfaces
+# MCLAG separate IP interface not configured
+# sonic# show mclag peer-gateway-interfaces
+# MCLAG Peer Gateway interface not configured
+# sonic#
+
+
# Using deleted
#
# Before state:
@@ -408,7 +562,10 @@ EXAMPLES = """
# Peer Link : PortChannel1
# Keepalive Interval : 10 secs
# Session Timeout : 30 secs
+# Delay Restore : 360 secs
# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
#
#
# Number of MLAG Interfaces:2
@@ -416,29 +573,37 @@ EXAMPLES = """
# MLAG Interface Local/Remote Status
#-----------------------------------------------------------
# PortChannel10 down/down
-# PortChannel12 down/sown
-#
-# admin@sonic:~$ show runningconfiguration all
-# {
-# ...
-# "MCLAG_UNIQUE_IP": {
-# "Vlan4": {
-# "unique_ip": "enable"
-# }
-# },
-# ...
-# }
+# PortChannel12 down/down
+#
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# ==============
+# Total count : 1
+# ==============
+# sonic#
+# sonic# show mclag peer-gateway-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# ==============
+# Total count : 1
+# ==============
+# sonic#
+
- name: Delete device configuration based on the provided configuration
dellemc.enterprise_sonic.sonic_mclag:
config:
domain_id: 1
source_address: 3.3.3.3
keepalive: 10
+ peer_gateway:
+ vlans:
members:
portchannels:
- - lag: PortChannel10
state: deleted
-#
+
# After state:
# ------------
#
@@ -452,24 +617,283 @@ EXAMPLES = """
# Peer Address : 1.1.1.1
# Peer Link : PortChannel1
# Keepalive Interval : 1 secs
-# Session Timeout : 15 secs
+# Session Timeout : 30 secs
+# Delay Restore : 360 secs
# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
#
#
# Number of MLAG Interfaces:0
#
-# admin@sonic:~$ show runningconfiguration all
-# {
-# ...
-# "MCLAG_UNIQUE_IP": {
-# "Vlan4": {
-# "unique_ip": "enable"
-# }
-# },
-# ...
-# }
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# ==============
+# Total count : 1
+# ==============
+# sonic#
+# sonic# show mclag peer-gateway-interfaces
+# MCLAG Peer Gateway interface not configured
+# sonic#
+
+
+# Using replaced
+#
+# Before state:
+# ------------
+#
+# sonic# show mclag brief
+#
+# Domain ID : 1
+# Role : standby
+# Session Status : down
+# Peer Link Status : down
+# Source Address : 2.2.2.2
+# Peer Address : 1.1.1.1
+# Peer Link : PortChannel1
+# Keepalive Interval : 1 secs
+# Session Timeout : 3 secs
+# Delay Restore : 240 secs
+# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
+#
+#
+# Number of MLAG Interfaces:2
+#-----------------------------------------------------------
+# MLAG Interface Local/Remote Status
+#-----------------------------------------------------------
+# PortChannel10 down/down
+# PortChannel11 down/down
+#
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# ==============
+# Total count : 6
+# ==============
+# sonic#
+# sonic# show mclag peer-gateway-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# ==============
+# Total count : 6
+# ==============
+# sonic#
+
+- name: Replace device configuration with the provided configuration
+ dellemc.enterprise_sonic.sonic_mclag:
+ config:
+ domain_id: 1
+ unique_ip:
+ vlans:
+ - vlan: Vlan5
+ - vlan: Vlan24-28
+ peer_gateway:
+ vlans:
+ - vlan: Vlan5
+ - vlan: Vlan24-28
+ members:
+ portchannels:
+ - lag: PortChannel10
+ - lag: PortChannel12
+ state: replaced
+
+# After state:
+# ------------
+#
+# sonic# show mclag brief
+#
+# Domain ID : 1
+# Role : standby
+# Session Status : down
+# Peer Link Status : down
+# Source Address : 2.2.2.2
+# Peer Address : 1.1.1.1
+# Peer Link : PortChannel1
+# Keepalive Interval : 1 secs
+# Session Timeout : 3 secs
+# Delay Restore : 240 secs
+# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
+#
+#
+# Number of MLAG Interfaces:2
+#-----------------------------------------------------------
+# MLAG Interface Local/Remote Status
+#-----------------------------------------------------------
+# PortChannel10 down/down
+# PortChannel12 down/down
+#
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan5
+# Vlan24
+# Vlan25
+# Vlan26
+# Vlan27
+# Vlan28
+# ==============
+# Total count : 6
+# ==============
+# sonic# show mclag peer-gateway-interfaces
+# Interface Name
+# ==============
+# Vlan5
+# Vlan24
+# Vlan25
+# Vlan26
+# Vlan27
+# Vlan28
+# ==============
+# Total count : 6
+# ==============
+# sonic#
+
+
+# Using overridden
+#
+# Before state:
+# ------------
+#
+# sonic# show mclag brief
+#
+# Domain ID : 1
+# Role : standby
+# Session Status : down
+# Peer Link Status : down
+# Source Address : 2.2.2.2
+# Peer Address : 1.1.1.1
+# Peer Link : PortChannel1
+# Keepalive Interval : 1 secs
+# Session Timeout : 3 secs
+# Delay Restore : 240 secs
+# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
+#
+#
+# Number of MLAG Interfaces:2
+#-----------------------------------------------------------
+# MLAG Interface Local/Remote Status
+#-----------------------------------------------------------
+# PortChannel10 down/down
+# PortChannel11 down/down
+#
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# ==============
+# Total count : 6
+# ==============
+# sonic#
+# sonic# show mclag peer-gateway-interfaces
+# Interface Name
+# ==============
+# Vlan4
+# Vlan21
+# Vlan22
+# Vlan23
+# Vlan24
+# Vlan25
+# ==============
+# Total count : 6
+# ==============
+# sonic#
+
+- name: Override device configuration with the provided configuration
+ dellemc.enterprise_sonic.sonic_mclag:
+ config:
+ domain_id: 1
+ peer_address: 1.1.1.1
+ source_address: 3.3.3.3
+ peer_link: 'Portchannel1'
+ system_mac: '00:00:00:11:11:11'
+ gateway_mac: '00:00:00:12:12:12'
+ unique_ip:
+ vlans:
+ - vlan: Vlan24-28
+ peer_gateway:
+ vlans:
+ - vlan: Vlan24-28
+ members:
+ portchannels:
+ - lag: PortChannel10
+ - lag: PortChannel12
+ state: overridden
+
+# After state:
+# ------------
+#
+# sonic# show mclag brief
#
+# Domain ID : 1
+# Role : standby
+# Session Status : down
+# Peer Link Status : down
+# Source Address : 3.3.3.3
+# Peer Address : 1.1.1.1
+# Peer Link : PortChannel1
+# Keepalive Interval : 1 secs
+# Session Timeout : 30 secs
+# Delay Restore : 300 secs
+# System Mac : 20:04:0f:37:bd:c9
+# Mclag System Mac : 00:00:00:11:11:11
+# Gateway Mac : 00:00:00:12:12:12
+#
+#
+# Number of MLAG Interfaces:2
+#-----------------------------------------------------------
+# MLAG Interface Local/Remote Status
+#-----------------------------------------------------------
+# PortChannel10 down/down
+# PortChannel12 down/down
#
+# sonic# show mclag separate-ip-interfaces
+# Interface Name
+# ==============
+# Vlan24
+# Vlan25
+# Vlan26
+# Vlan27
+# Vlan28
+# ==============
+# Total count : 5
+# ==============
+# sonic# show mclag peer-gateway-interfaces
+# Interface Name
+# ==============
+# Vlan24
+# Vlan25
+# Vlan26
+# Vlan27
+# Vlan28
+# ==============
+# Total count : 5
+# ==============
+# sonic#
"""
RETURN = """
before:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ntp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ntp.py
index 87db8bb06..c04e437c1 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ntp.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ntp.py
@@ -34,6 +34,8 @@ DOCUMENTATION = """
---
module: sonic_ntp
version_added: 2.0.0
+notes:
+ - Supports C(check_mode).
short_description: Manage NTP configuration on SONiC.
description:
- This module provides configuration management of NTP for devices running SONiC.
@@ -67,6 +69,7 @@ options:
elements: dict
description:
- List of NTP servers.
+ - minpoll and maxpoll are required to be configured together.
suboptions:
address:
type: str
@@ -88,6 +91,11 @@ options:
description:
- Maximum poll interval to poll NTP server.
- maxpoll can not be deleted.
+ prefer:
+ type: bool
+ description:
+ - Indicates whether this server should be preferred.
+ - prefer can not be deleted.
ntp_keys:
type: list
elements: dict
@@ -127,8 +135,10 @@ options:
- The state of the configuration after module completion.
type: str
choices:
- - merged
- - deleted
+ - merged
+ - replaced
+ - overridden
+ - deleted
default: merged
"""
EXAMPLES = """
@@ -138,16 +148,16 @@ EXAMPLES = """
# -------------
#
#sonic# show ntp server
-#----------------------------------------------------------------------
-#NTP Servers minpoll maxpoll Authentication key ID
-#----------------------------------------------------------------------
-#10.11.0.1 6 10
-#10.11.0.2 5 9
-#dell.com 6 9
-#dell.org 7 10
+#----------------------------------------------------------------------------
+#NTP Servers minpoll maxpoll Prefer Authentication key ID
+#----------------------------------------------------------------------------
+#10.11.0.1 6 10 False
+#10.11.0.2 5 9 False
+#dell.com 6 9 False
+#dell.org 7 10 True
#
- name: Delete NTP server configuration
- ntp:
+ sonic_ntp:
config:
servers:
- address: 10.11.0.2
@@ -158,11 +168,11 @@ EXAMPLES = """
# ------------
#
#sonic# show ntp server
-#----------------------------------------------------------------------
-#NTP Servers minpoll maxpoll Authentication key ID
-#----------------------------------------------------------------------
-#10.11.0.1 6 10
-#dell.com 6 9
+#----------------------------------------------------------------------------
+#NTP Servers minpoll maxpoll Prefer Authentication key ID
+#----------------------------------------------------------------------------
+#10.11.0.1 6 10 False
+#dell.com 6 9 False
#
#
# Using deleted
@@ -177,7 +187,7 @@ EXAMPLES = """
#NTP source-interfaces: Ethernet0, Ethernet4, Ethernet8, Ethernet16
#
- name: Delete NTP source-interface configuration
- ntp:
+ sonic_ntp:
config:
source_interfaces:
- Ethernet8
@@ -205,7 +215,7 @@ EXAMPLES = """
#ntp authentication-key 20 sha2-256 U2FsdGVkX1/eAzKj1teKhYWD7tnzOsYOijGeFAT0rKM= encrypted
#
- name: Delete NTP key configuration
- ntp:
+ sonic_ntp:
config:
ntp_keys:
- key_id: 10
@@ -225,14 +235,14 @@ EXAMPLES = """
# -------------
#
#sonic# show ntp server
-#----------------------------------------------------------------------
-#NTP Servers minpoll maxpoll Authentication key ID
-#----------------------------------------------------------------------
-#10.11.0.1 6 10
-#dell.com 6 9
+#----------------------------------------------------------------------------
+#NTP Servers minpoll maxpoll Prefer Authentication key ID
+#----------------------------------------------------------------------------
+#10.11.0.1 6 10 False
+#dell.com 6 9 False
#
- name: Merge NTP server configuration
- ntp:
+ sonic_ntp:
config:
servers:
- address: 10.11.0.2
@@ -240,19 +250,20 @@ EXAMPLES = """
- address: dell.org
minpoll: 7
maxpoll: 10
+ prefer: true
state: merged
# After state:
# ------------
#
#sonic# show ntp server
-#----------------------------------------------------------------------
-#NTP Servers minpoll maxpoll Authentication key ID
-#----------------------------------------------------------------------
-#10.11.0.1 6 10
-#10.11.0.2 5 9
-#dell.com 6 9
-#dell.org 7 10
+#----------------------------------------------------------------------------
+#NTP Servers minpoll maxpoll Prefer Authentication key ID
+#----------------------------------------------------------------------------
+#10.11.0.1 6 10 Flase
+#10.11.0.2 5 10 Flase
+#dell.com 6 9 Flase
+#dell.org 7 10 True
#
#
# Using merged
@@ -267,7 +278,7 @@ EXAMPLES = """
#NTP source-interfaces: Ethernet0, Ethernet4
#
- name: Merge NTP source-interface configuration
- ntp:
+ sonic_ntp:
config:
source_interfaces:
- Ethernet8
@@ -293,7 +304,7 @@ EXAMPLES = """
#ntp authentication-key 8 sha1 U2FsdGVkX1/NpJrdOeyMeUHEkSohY6azY9VwbAqXRTY= encrypted
#
- name: Merge NTP key configuration
- ntp:
+ sonic_ntp:
config:
ntp_keys:
- key_id: 10
@@ -314,6 +325,87 @@ EXAMPLES = """
#ntp authentication-key 10 md5 U2FsdGVkX1/Gxds/5pscCvIKbVngGaKka4SQineS51Y= encrypted
#ntp authentication-key 20 sha2-256 U2FsdGVkX1/eAzKj1teKhYWD7tnzOsYOijGeFAT0rKM= encrypted
#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+#sonic# show ntp server
+#----------------------------------------------------------------------------
+#NTP Servers minpoll maxpoll Prefer Authentication key ID
+#----------------------------------------------------------------------------
+#10.11.0.1 6 10 False
+#dell.com 6 9 False
+#
+- name: Replace NTP server configuration
+ sonic_ntp:
+ config:
+ servers:
+ - address: 10.11.0.2
+ minpoll: 5
+ maxpoll: 9
+ - address: dell.com
+ minpoll: 7
+ maxpoll: 10
+ prefer: true
+ state: replaced
+#
+# After state:
+# ------------
+#
+#sonic# show ntp server
+#----------------------------------------------------------------------------
+#NTP Servers minpoll maxpoll Prefer Authentication key ID
+#----------------------------------------------------------------------------
+#10.11.0.1 6 10 False
+#10.11.0.2 5 9 False
+#dell.com 7 10 True
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+#sonic# show ntp server
+#----------------------------------------------------------------------------
+#NTP Servers minpoll maxpoll Prefer Authentication key ID
+#----------------------------------------------------------------------------
+#10.11.0.1 6 10 False
+#dell.com 6 9 False
+#
+#sonic# show ntp global
+#----------------------------------------------
+#NTP Global Configuration
+#----------------------------------------------
+#NTP source-interfaces: Ethernet0, Ethernet4
+#
+- name: Overridden NTP configuration
+ sonic_ntp:
+ config:
+ servers:
+ - address: 10.11.0.2
+ minpoll: 5
+ - address: dell.com
+ minpoll: 7
+ maxpoll: 10
+ prefer: true
+ state: overridden
+#
+# After state:
+# ------------
+#
+# After state:
+# ------------
+#
+#sonic# show ntp server
+#----------------------------------------------------------------------------
+#NTP Servers minpoll maxpoll Prefer Authentication key ID
+#----------------------------------------------------------------------------
+#10.11.0.2 5 10 False
+#dell.com 7 10 True
+#
+#sonic# show ntp global
+#
"""
RETURN = """
before:
@@ -330,6 +422,13 @@ after:
sample: >
The configuration returned will always be in the same format
of the parameters above.
+after(generated):
+ description: The generated configuration model invocation.
+ returned: when C(check_mode)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
commands:
description: The set of commands pushed to the remote device.
returned: always
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_pki.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_pki.py
new file mode 100644
index 000000000..559935fef
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_pki.py
@@ -0,0 +1,301 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2022 Dell EMC
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_pki
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_pki
+version_added: 2.3.0
+short_description: 'Manages PKI attributes of Enterprise Sonic'
+description: 'Manages PKI attributes of Enterprise Sonic'
+author: Eric Seifert (@seiferteric)
+notes:
+ - 'Tested against Dell Enterprise SONiC 4.1.0'
+options:
+ config:
+ description: The provided configuration
+ type: dict
+ suboptions:
+ trust_stores:
+ description: Store of CA Certificates
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ type: str
+ required: True
+ description: The name of the Trust Store
+ ca_name:
+ type: list
+ elements: str
+ description: List of CA certificates in the trust store.
+ security_profiles:
+ description: Application Security Profiles
+ type: list
+ elements: dict
+ suboptions:
+ profile_name:
+ type: str
+ required: True
+ description: Profile Name
+ certificate_name:
+ type: str
+ description: Host Certificate Name
+ trust_store:
+ type: str
+ description: Name of associated trust_store
+ revocation_check:
+ description: Require certificate revocation check succeeds
+ type: bool
+ peer_name_check:
+ description: Require peer name is verified
+ type: bool
+ key_usage_check:
+ description: Require key usage is enforced
+ type: bool
+ cdp_list:
+ description: Global list of CDP's
+ type: list
+ elements: str
+ ocsp_responder_list:
+ description: Global list of OCSP responders
+ type: list
+ elements: str
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices: ['merged', 'deleted', 'replaced', 'overridden']
+ default: merged
+"""
+EXAMPLES = """
+# Using "merged" state for initial config
+#
+# Before state:
+# -------------
+#
+# sonic# show running-configuration | grep crypto
+# sonic#
+#
+- name: PKI Config Test
+ hosts: datacenter
+ gather_facts: false
+ connection: httpapi
+ collections:
+ - dellemc.enterprise_sonic
+ tasks:
+ - name: "Initial Config"
+ sonic_pki:
+ config:
+ security_profiles:
+ - profile_name: rest
+ ocsp_responder_list:
+ - http://example.com/ocspa
+ - http://example.com/ocspb
+ certificate_name: host
+ trust_store: default-ts
+ trust_stores:
+ - name: default-ts
+ ca_name:
+ - CA2
+ state: merged
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration | grep crypto
+# crypto trust_store default-ts ca-cert CA2
+# crypto security-profile rest
+# crypto security-profile trust_store rest default-ts
+# crypto security-profile certificate rest host
+# crypto security-profile ocsp-list rest http://example.com/ocspa,http://example.com/ocspb
+
+# Using "deleted" state to remove configuration
+#
+# Before state:
+# ------------
+#
+# sonic# show running-configuration | grep crypto
+# crypto trust_store default-ts ca-cert CA2
+# crypto security-profile rest
+# crypto security-profile trust_store rest default-ts
+# crypto security-profile certificate rest host
+# crypto security-profile ocsp-list rest http://example.com/ocsp
+#
+- name: PKI Delete Test
+ hosts: datacenter
+ gather_facts: true
+ connection: httpapi
+ collections:
+ - dellemc.enterprise_sonic
+ tasks:
+ - name: Remove trust_store from security-profile
+ sonic_pki:
+ config:
+ security_profiles:
+ - profile_name: rest
+ trust_store: default-ts
+ state: deleted
+# After state:
+# ------------
+#
+# sonic# show running-configuration | grep crypto
+# crypto trust_store default-ts ca-cert CA2
+# crypto security-profile rest
+# crypto security-profile certificate rest host
+# crypto security-profile ocsp-list rest http://example.com/ocsp
+
+# Using "overridden" state
+
+# Before state:
+# ------------
+#
+# sonic# show running-configuration | grep crypto
+# crypto trust_store default-ts ca-cert CA2
+# crypto security-profile rest
+# crypto security-profile trust_store rest default-ts
+# crypto security-profile certificate rest host
+# crypto security-profile ocsp-list rest http://example.com/ocspa,http://example.com/ocspb
+#
+- name: PKI Overridden Test
+ hosts: datacenter
+ gather_facts: false
+ connection: httpapi
+ collections:
+ - dellemc.enterprise_sonic
+ tasks:
+ - name: "Overridden Config"
+ sonic_pki:
+ config:
+ security_profiles:
+ - profile_name: telemetry
+ ocsp_responder_list:
+ - http://example.com/ocspb
+ revocation_check: true
+ trust_store: telemetry-ts
+ certificate_name: host
+ trust_stores:
+ - name: telemetry-ts
+ ca_name: CA
+ state: overridden
+# After state:
+# -----------
+#
+# sonic# show running-configuration | grep crypto
+# crypto trust_store telemetry-ts ca-cert CA
+# crypto security-profile telemetry revocation_check true
+# crypto security-profile trust_store telemetry telemetry-ts
+# crypto security-profile certificate telemetry host
+# crypto security-profile ocsp-list telemetry http://example.com/ocspb
+
+# Using "replaced" state to update config
+
+# Before state:
+# ------------
+#
+# sonic# show running-configuration | grep crypto
+# crypto trust_store default-ts ca-cert CA2
+# crypto security-profile rest
+# crypto security-profile trust_store rest default-ts
+# crypto security-profile certificate rest host
+# crypto security-profile ocsp-list rest http://example.com/ocspa,http://example.com/ocspb
+#
+- name: PKI Replace Test
+ hosts: datacenter
+ gather_facts: false
+ connection: httpapi
+ collections:
+ - dellemc.enterprise_sonic
+ tasks:
+ - name: "Replace Config"
+ sonic_pki:
+ config:
+ security_profiles:
+ - profile_name: rest
+ ocsp_responder_list:
+ - http://example.com/ocsp
+ revocation_check: false
+ trust_store: default-ts
+ certificate_name: host
+ state: replaced
+# After state:
+# -----------
+#
+# sonic# show running-configuration | grep crypto
+# crypto trust_store default-ts ca-cert CA2
+# crypto security-profile rest
+# crypto security-profile trust_store rest default-ts
+# crypto security-profile certificate rest host
+# crypto security-profile ocsp-list rest http://example.com/ocsp
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.pki.pki import PkiArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.pki.pki import Pki
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=PkiArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Pki(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_port_breakout.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_port_breakout.py
index 66ea00476..3de7dfb17 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_port_breakout.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_port_breakout.py
@@ -57,23 +57,35 @@ options:
- Specifies the mode of the port breakout.
type: str
choices:
+ - 1x10G
+ - 1x25G
+ - 1x40G
+ - 1x50G
- 1x100G
+ - 1x200G
- 1x400G
- - 1x40G
+ - 2x10G
+ - 2x25G
+ - 2x40G
+ - 2x50G
- 2x100G
- 2x200G
- - 2x50G
- - 4x100G
- 4x10G
- 4x25G
- 4x50G
+ - 4x100G
+ - 8x10G
+ - 8x25G
+ - 8x50G
state:
description:
- Specifies the operation to be performed on the port breakout configured on the device.
- In case of merged, the input mode configuration will be merged with the existing port breakout configuration on the device.
- - In case of deleted the existing port breakout mode configuration will be removed from the device.
+ - In case of deleted, the existing port breakout mode configuration will be removed from the device.
+ - In case of replaced, on-device port breakout configuration of the specified interfaces is replaced with provided configuration.
+ - In case of overridden, all on-device port breakout configurations are overridden with the provided configuration.
default: merged
- choices: ['merged', 'deleted']
+ choices: ['merged', 'deleted', 'replaced', 'overridden']
type: str
"""
EXAMPLES = """
@@ -82,18 +94,18 @@ EXAMPLES = """
# Before state:
# -------------
#
-#do show interface breakout
-#-----------------------------------------------
-#Port Breakout Mode Status Interfaces
-#-----------------------------------------------
-#1/1 4x10G Completed Eth1/1/1
-# Eth1/1/2
-# Eth1/1/3
-# Eth1/1/4
-#1/11 1x100G Completed Eth1/11
+# sonic# show interface breakout
+# -----------------------------------------------
+# Port Breakout Mode Status Interfaces
+# -----------------------------------------------
+# 1/1 4x10G Completed Eth1/1/1
+# Eth1/1/2
+# Eth1/1/3
+# Eth1/1/4
+# 1/11 1x100G Completed Eth1/11/1
#
-- name: Merge users configurations
+- name: Delete interface port breakout configuration
dellemc.enterprise_sonic.sonic_port_breakout:
config:
- name: 1/11
@@ -103,15 +115,16 @@ EXAMPLES = """
# After state:
# ------------
#
-#do show interface breakout
-#-----------------------------------------------
-#Port Breakout Mode Status Interfaces
-#-----------------------------------------------
-#1/1 4x10G Completed Eth1/1/1
-# Eth1/1/2
-# Eth1/1/3
-# Eth1/1/4
-#1/11 Default Completed Ethernet40
+# sonic# show interface breakout
+# -----------------------------------------------
+# Port Breakout Mode Status Interfaces
+# -----------------------------------------------
+# 1/1 4x10G Completed Eth1/1/1
+# Eth1/1/2
+# Eth1/1/3
+# Eth1/1/4
+# 1/11 Default Completed Eth1/11
+#
# Using deleted
@@ -119,31 +132,31 @@ EXAMPLES = """
# Before state:
# -------------
#
-#do show interface breakout
-#-----------------------------------------------
-#Port Breakout Mode Status Interfaces
-#-----------------------------------------------
-#1/1 4x10G Completed Eth1/1/1
-# Eth1/1/2
-# Eth1/1/3
-# Eth1/1/4
-#1/11 1x100G Completed Eth1/11
-#
-- name: Merge users configurations
+# sonic# show interface breakout
+# -----------------------------------------------
+# Port Breakout Mode Status Interfaces
+# -----------------------------------------------
+# 1/1 4x10G Completed Eth1/1/1
+# Eth1/1/2
+# Eth1/1/3
+# Eth1/1/4
+# 1/11 1x100G Completed Eth1/11/1
+#
+
+- name: Delete all port breakout configurations
dellemc.enterprise_sonic.sonic_port_breakout:
config:
state: deleted
-
# After state:
# ------------
#
-#do show interface breakout
-#-----------------------------------------------
-#Port Breakout Mode Status Interfaces
-#-----------------------------------------------
-#1/1 Default Completed Ethernet0
-#1/11 Default Completed Ethernet40
+# sonic# show interface breakout
+# -----------------------------------------------
+# Port Breakout Mode Status Interfaces
+# -----------------------------------------------
+# 1/1 Default Completed Eth1/1
+# 1/11 Default Completed Eth1/11
# Using merged
@@ -151,35 +164,111 @@ EXAMPLES = """
# Before state:
# -------------
#
-#do show interface breakout
-#-----------------------------------------------
-#Port Breakout Mode Status Interfaces
-#-----------------------------------------------
-#1/1 4x10G Completed Eth1/1/1
-# Eth1/1/2
-# Eth1/1/3
-# Eth1/1/4
+# sonic# show interface breakout
+# -----------------------------------------------
+# Port Breakout Mode Status Interfaces
+# -----------------------------------------------
+# 1/1 4x10G Completed Eth1/1/1
+# Eth1/1/2
+# Eth1/1/3
+# Eth1/1/4
#
-- name: Merge users configurations
+
+- name: Merge port breakout configurations
dellemc.enterprise_sonic.sonic_port_breakout:
config:
- name: 1/11
mode: 1x100G
state: merged
+# After state:
+# ------------
+#
+# sonic# show interface breakout
+# -----------------------------------------------
+# Port Breakout Mode Status Interfaces
+# -----------------------------------------------
+# 1/1 4x10G Completed Eth1/1/1
+# Eth1/1/2
+# Eth1/1/3
+# Eth1/1/4
+# 1/11 1x100G Completed Eth1/11/1
+
+
+# Using replaced
+#
+# Before state:
+# -------------
+#
+# sonic# show interface breakout
+# -----------------------------------------------
+# Port Breakout Mode Status Interfaces
+# -----------------------------------------------
+# 1/49 4x25G Completed Eth1/49/1
+# Eth1/49/2
+# Eth1/49/3
+# Eth1/49/4
+#
+
+- name: Replace port breakout configurations
+ dellemc.enterprise_sonic.sonic_port_breakout:
+ config:
+ - name: 1/49
+ mode: 4x10G
+ state: replaced
+
+# After state:
+# ------------
+#
+# sonic# show interface breakout
+# -----------------------------------------------
+# Port Breakout Mode Status Interfaces
+# -----------------------------------------------
+# 1/49 4x10G Completed Eth1/49/1
+# Eth1/49/2
+# Eth1/49/3
+# Eth1/49/4
+
+
+# Using overridden
+#
+# Before state:
+# -------------
+#
+# sonic# show interface breakout
+# ----------------------------------------------
+# Port Breakout Mode Status Interfaces
+# -----------------------------------------------
+# 1/49 4x10G Completed Eth1/49/1
+# Eth1/49/2
+# Eth1/49/3
+# Eth1/49/4
+# 1/50 2x50G Completed Eth1/50/1
+# Eth1/50/2
+# 1/51 1x100G Completed Eth1/51/1
+#
+
+- name: Override port breakout configurations
+ dellemc.enterprise_sonic.sonic_port_breakout:
+ config:
+ - name: 1/52
+ mode: 4x10G
+ state: overridden
# After state:
# ------------
#
-#do show interface breakout
-#-----------------------------------------------
-#Port Breakout Mode Status Interfaces
-#-----------------------------------------------
-#1/1 4x10G Completed Eth1/1/1
-# Eth1/1/2
-# Eth1/1/3
-# Eth1/1/4
-#1/11 1x100G Completed Eth1/11
+# sonic# show interface breakout
+# -----------------------------------------------
+# Port Breakout Mode Status Interfaces
+# -----------------------------------------------
+# 1/49 Default Completed Eth1/49
+# 1/50 Default Completed Eth1/50
+# 1/51 Default Completed Eth1/51
+# 1/52 4x10G Completed Eth1/52/1
+# Eth1/52/2
+# Eth1/52/3
+# Eth1/52/4
"""
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_port_group.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_port_group.py
new file mode 100644
index 000000000..d31c19cd3
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_port_group.py
@@ -0,0 +1,370 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# © Copyright 2022 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_port_group
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: sonic_port_group
+version_added: 2.1.0
+notes:
+ - Supports C(check_mode).
+short_description: Manages port group configuration on SONiC.
+description:
+ - This module provides configuration management of port group for devices running SONiC.
+author: 'M. Zhang (@mingjunzhang2019)'
+options:
+ config:
+ description:
+ - A list of port group configurations.
+ type: list
+ elements: dict
+ suboptions:
+ id:
+ type: str
+ description:
+ - The index of the port group.
+ required: true
+ speed:
+ description:
+ - Speed for the port group.
+ - This configures the speed for all the memebr ports of the prot group.
+ - Supported speeds are dependent on the type of switch.
+ type: str
+ choices:
+ - SPEED_10MB
+ - SPEED_100MB
+ - SPEED_1GB
+ - SPEED_2500MB
+ - SPEED_5GB
+ - SPEED_10GB
+ - SPEED_20GB
+ - SPEED_25GB
+ - SPEED_40GB
+ - SPEED_50GB
+ - SPEED_100GB
+ - SPEED_400GB
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#sonic# show port-group
+#-------------------------------------------------------------------------------------
+#Port-group Interface range Valid speeds Default Speed Current Speed
+#-------------------------------------------------------------------------------------
+#1 Ethernet0 - Ethernet3 10G, 25G 25G 10G
+#2 Ethernet4 - Ethernet7 10G, 25G 25G 25G
+#3 Ethernet8 - Ethernet11 10G, 25G 25G 25G
+#4 Ethernet12 - Ethernet15 10G, 25G 25G 25G
+#5 Ethernet16 - Ethernet19 10G, 25G 25G 25G
+#6 Ethernet20 - Ethernet23 10G, 25G 25G 25G
+#7 Ethernet24 - Ethernet27 10G, 25G 25G 25G
+#8 Ethernet28 - Ethernet31 10G, 25G 25G 25G
+#9 Ethernet32 - Ethernet35 10G, 25G 25G 10G
+#10 Ethernet36 - Ethernet39 10G, 25G 25G 25G
+#
+- name: Configure port group speed
+ sonic_port_group:
+ config:
+ - id: 1
+ - id: 10
+ state: deleted
+#
+#
+# After state:
+# ------------
+#
+#sonic# show port-group
+#-------------------------------------------------------------------------------------
+#Port-group Interface range Valid speeds Default Speed Current Speed
+#-------------------------------------------------------------------------------------
+#1 Ethernet0 - Ethernet3 10G, 25G 25G 25G
+#2 Ethernet4 - Ethernet7 10G, 25G 25G 25G
+#3 Ethernet8 - Ethernet11 10G, 25G 25G 25G
+#4 Ethernet12 - Ethernet15 10G, 25G 25G 25G
+#5 Ethernet16 - Ethernet19 10G, 25G 25G 25G
+#6 Ethernet20 - Ethernet23 10G, 25G 25G 25G
+#7 Ethernet24 - Ethernet27 10G, 25G 25G 25G
+#8 Ethernet28 - Ethernet31 10G, 25G 25G 25G
+#9 Ethernet32 - Ethernet35 10G, 25G 25G 10G
+#10 Ethernet36 - Ethernet39 10G, 25G 25G 25G
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#sonic# show port-group
+#-------------------------------------------------------------------------------------
+#Port-group Interface range Valid speeds Default Speed Current Speed
+#-------------------------------------------------------------------------------------
+#1 Ethernet0 - Ethernet3 10G, 25G 25G 10G
+#2 Ethernet4 - Ethernet7 10G, 25G 25G 25G
+#3 Ethernet8 - Ethernet11 10G, 25G 25G 25G
+#4 Ethernet12 - Ethernet15 10G, 25G 25G 25G
+#5 Ethernet16 - Ethernet19 10G, 25G 25G 25G
+#6 Ethernet20 - Ethernet23 10G, 25G 25G 25G
+#7 Ethernet24 - Ethernet27 10G, 25G 25G 25G
+#8 Ethernet28 - Ethernet31 10G, 25G 25G 25G
+#9 Ethernet32 - Ethernet35 10G, 25G 25G 10G
+#10 Ethernet36 - Ethernet39 10G, 25G 25G 25G
+#
+- name: Configure port group speed
+ sonic_port_group:
+ config:
+ - id:
+ state: deleted
+#
+#
+# After state:
+# ------------
+#
+#sonic# show port-group
+#-------------------------------------------------------------------------------------
+#Port-group Interface range Valid speeds Default Speed Current Speed
+#-------------------------------------------------------------------------------------
+#1 Ethernet0 - Ethernet3 10G, 25G 25G 25G
+#2 Ethernet4 - Ethernet7 10G, 25G 25G 25G
+#3 Ethernet8 - Ethernet11 10G, 25G 25G 25G
+#4 Ethernet12 - Ethernet15 10G, 25G 25G 25G
+#5 Ethernet16 - Ethernet19 10G, 25G 25G 25G
+#6 Ethernet20 - Ethernet23 10G, 25G 25G 25G
+#7 Ethernet24 - Ethernet27 10G, 25G 25G 25G
+#8 Ethernet28 - Ethernet31 10G, 25G 25G 25G
+#9 Ethernet32 - Ethernet35 10G, 25G 25G 25G
+#10 Ethernet36 - Ethernet39 10G, 25G 25G 25G
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#sonic# show port-group
+#-------------------------------------------------------------------------------------
+#Port-group Interface range Valid speeds Default Speed Current Speed
+#-------------------------------------------------------------------------------------
+#1 Ethernet0 - Ethernet3 10G, 25G 25G 25G
+#2 Ethernet4 - Ethernet7 10G, 25G 25G 25G
+#3 Ethernet8 - Ethernet11 10G, 25G 25G 25G
+#4 Ethernet12 - Ethernet15 10G, 25G 25G 25G
+#5 Ethernet16 - Ethernet19 10G, 25G 25G 25G
+#6 Ethernet20 - Ethernet23 10G, 25G 25G 25G
+#7 Ethernet24 - Ethernet27 10G, 25G 25G 25G
+#8 Ethernet28 - Ethernet31 10G, 25G 25G 25G
+#9 Ethernet32 - Ethernet35 10G, 25G 25G 25G
+#10 Ethernet36 - Ethernet39 10G, 25G 25G 25G
+#
+- name: Configure port group speed
+ sonic_port_group:
+ config:
+ - id: 1
+ speed: SPEED_10GB
+ - id: 9
+ speed: SPEED_10GB
+ state: merged
+#
+#
+# After state:
+# ------------
+#
+#sonic# show port-group
+#-------------------------------------------------------------------------------------
+#Port-group Interface range Valid speeds Default Speed Current Speed
+#-------------------------------------------------------------------------------------
+#1 Ethernet0 - Ethernet3 10G, 25G 25G 10G
+#2 Ethernet4 - Ethernet7 10G, 25G 25G 25G
+#3 Ethernet8 - Ethernet11 10G, 25G 25G 25G
+#4 Ethernet12 - Ethernet15 10G, 25G 25G 25G
+#5 Ethernet16 - Ethernet19 10G, 25G 25G 25G
+#6 Ethernet20 - Ethernet23 10G, 25G 25G 25G
+#7 Ethernet24 - Ethernet27 10G, 25G 25G 25G
+#8 Ethernet28 - Ethernet31 10G, 25G 25G 25G
+#9 Ethernet32 - Ethernet35 10G, 25G 25G 10G
+#10 Ethernet36 - Ethernet39 10G, 25G 25G 25G
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+#sonic# show port-group
+#-------------------------------------------------------------------------------------
+#Port-group Interface range Valid speeds Default Speed Current Speed
+#-------------------------------------------------------------------------------------
+#1 Ethernet0 - Ethernet3 10G, 25G 25G 25G
+#2 Ethernet4 - Ethernet7 10G, 25G 25G 25G
+#3 Ethernet8 - Ethernet11 10G, 25G 25G 25G
+#4 Ethernet12 - Ethernet15 10G, 25G 25G 10G
+#5 Ethernet16 - Ethernet19 10G, 25G 25G 25G
+#6 Ethernet20 - Ethernet23 10G, 25G 25G 25G
+#7 Ethernet24 - Ethernet27 10G, 25G 25G 25G
+#8 Ethernet28 - Ethernet31 10G, 25G 25G 25G
+#9 Ethernet32 - Ethernet35 10G, 25G 25G 25G
+#10 Ethernet36 - Ethernet39 10G, 25G 25G 25G
+#
+- name: Replace port group speed
+ sonic_port_group:
+ config:
+ - id: 1
+ speed: SPEED_10GB
+ - id: 9
+ speed: SPEED_10GB
+ state: replaced
+#
+# After state:
+# ------------
+#
+#sonic# show port-group
+#-------------------------------------------------------------------------------------
+#Port-group Interface range Valid speeds Default Speed Current Speed
+#-------------------------------------------------------------------------------------
+#1 Ethernet0 - Ethernet3 10G, 25G 25G 10G
+#2 Ethernet4 - Ethernet7 10G, 25G 25G 25G
+#3 Ethernet8 - Ethernet11 10G, 25G 25G 25G
+#4 Ethernet12 - Ethernet15 10G, 25G 25G 10G
+#5 Ethernet16 - Ethernet19 10G, 25G 25G 25G
+#6 Ethernet20 - Ethernet23 10G, 25G 25G 25G
+#7 Ethernet24 - Ethernet27 10G, 25G 25G 25G
+#8 Ethernet28 - Ethernet31 10G, 25G 25G 25G
+#9 Ethernet32 - Ethernet35 10G, 25G 25G 10G
+#10 Ethernet36 - Ethernet39 10G, 25G 25G 25G
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+#sonic# show port-group
+#-------------------------------------------------------------------------------------
+#Port-group Interface range Valid speeds Default Speed Current Speed
+#-------------------------------------------------------------------------------------
+#1 Ethernet0 - Ethernet3 10G, 25G 25G 25G
+#2 Ethernet4 - Ethernet7 10G, 25G 25G 10G
+#3 Ethernet8 - Ethernet11 10G, 25G 25G 10G
+#4 Ethernet12 - Ethernet15 10G, 25G 25G 25G
+#5 Ethernet16 - Ethernet19 10G, 25G 25G 10G
+#6 Ethernet20 - Ethernet23 10G, 25G 25G 25G
+#7 Ethernet24 - Ethernet27 10G, 25G 25G 10G
+#8 Ethernet28 - Ethernet31 10G, 25G 25G 10G
+#9 Ethernet32 - Ethernet35 10G, 25G 25G 10G
+#10 Ethernet36 - Ethernet39 10G, 25G 25G 10G
+#
+- name: Override port group speed
+ sonic_port_group:
+ config:
+ - id: 1
+ speed: SPEED_10GB
+ - id: 9
+ speed: SPEED_10GB
+ state: overridden
+#
+# After state:
+# ------------
+#
+#sonic# show port-group
+#-------------------------------------------------------------------------------------
+#Port-group Interface range Valid speeds Default Speed Current Speed
+#-------------------------------------------------------------------------------------
+#1 Ethernet0 - Ethernet3 10G, 25G 25G 10G
+#2 Ethernet4 - Ethernet7 10G, 25G 25G 25G
+#3 Ethernet8 - Ethernet11 10G, 25G 25G 25G
+#4 Ethernet12 - Ethernet15 10G, 25G 25G 25G
+#5 Ethernet16 - Ethernet19 10G, 25G 25G 25G
+#6 Ethernet20 - Ethernet23 10G, 25G 25G 25G
+#7 Ethernet24 - Ethernet27 10G, 25G 25G 25G
+#8 Ethernet28 - Ethernet31 10G, 25G 25G 25G
+#9 Ethernet32 - Ethernet35 10G, 25G 25G 10G
+#10 Ethernet36 - Ethernet39 10G, 25G 25G 25G
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after(generated):
+ description: The generated configuration model invocation.
+ returned: when C(check_mode)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.port_group.port_group import Port_groupArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.port_group.port_group import Port_group
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Port_groupArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Port_group(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_prefix_lists.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_prefix_lists.py
index 5a734e8b2..b3389b6ad 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_prefix_lists.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_prefix_lists.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-# Copyright 2019 Red Hat
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -87,11 +87,15 @@ options:
description:
- Specifies the type of configuration update to be performed on the device.
- For "merged", merge specified attributes with existing configured attributes.
- - For "deleted", delete the specified attributes from exiting configuration.
+ - For "deleted", delete the specified attributes from existing configuration.
+ - For "replaced", replace the specified existing configuration with the provided configuration.
+ - For "overridden", override the existing configuration with the provided configuration.
type: str
choices:
- merged
- deleted
+ - replaced
+ - overridden
default: merged
"""
EXAMPLES = """
@@ -227,6 +231,95 @@ EXAMPLES = """
# sonic#
# (no IPv6 prefix-list configuration present)
#
+# ***************************************************************
+# Using "overriden" state to override configuration
+#
+# Before state:
+# ------------
+#
+# sonic# show running-configuration ip prefix-list
+# !
+# ip prefix-list pfx1 seq 10 permit 1.2.3.4/24 ge 26 le 30
+# ip prefix-list pfx3 seq 20 deny 1.2.3.12/26
+# ip prefix-list pfx4 seq 30 permit 7.8.9.0/24
+#
+# sonic# show running-configuration ipv6 prefix-list
+# !
+# ipv6 prefix-list pfx6 seq 25 permit 40::300/124
+#
+# ------------
+#
+- name: Override prefix-list configuration
+ dellemc.enterprise_sonic.sonic_prefix_lists:
+ config:
+ - name: pfx2
+ afi: "ipv4"
+ prefixes:
+ - sequence: 10
+ prefix: "10.20.30.128/24"
+ action: "deny"
+ ge: 25
+ le: 30
+ state: overridden
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration ip prefix-list
+# !
+# ip prefix-list pfx2 seq 10 deny 10.20.30.128/24 ge 25 le 30
+#
+# sonic# show running-configuration ipv6 prefix-list
+# sonic#
+# (no IPv6 prefix-list configuration present)
+#
+# ***************************************************************
+# Using "replaced" state to replace configuration
+#
+# Before state:
+# ------------
+#
+# sonic# show running-configuration ip prefix-list
+# !
+# ip prefix-list pfx2 seq 10 deny 10.20.30.128/24 ge 25 le 30
+#
+# sonic# show running-configuration ipv6 prefix-list
+# sonic#
+# (no IPv6 prefix-list configuration present)
+#
+# ------------
+#
+- name: Replace prefix-list configuration
+ dellemc.enterprise_sonic.sonic_prefix_lists:
+ config:
+ - name: pfx2
+ afi: "ipv4"
+ prefixes:
+ - sequence: 10
+ prefix: "10.20.30.128/24"
+ action: "permit"
+ ge: 25
+ le: 30
+ - name: pfx3
+ afi: "ipv6"
+ prefixes:
+ - sequence: 20
+ action: "deny"
+ prefix: "60::70/124"
+ state: replaced
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration ip prefix-list
+# !
+# ip prefix-list pfx2 seq 10 permit 10.20.30.128/24 ge 25 le 30
+#
+# sonic# show running-configuration ipv6 prefix-list
+# sonic#
+# !
+# ipv6 prefix-list pfx3 seq 20 deny 60::70/124
+#
"""
RETURN = """
before:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_radius_server.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_radius_server.py
index 1df4aff61..bc1f81d39 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_radius_server.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_radius_server.py
@@ -71,6 +71,7 @@ options:
description:
- Specifies the timeout of the radius server.
type: int
+ default: 5
retransmit:
description:
- Specifies the re-transmit value of the radius server.
@@ -110,6 +111,7 @@ options:
description:
- Specifies the port of the radius server host.
type: int
+ default: 1812
timeout:
description:
- Specifies the timeout of the radius server host.
@@ -131,8 +133,10 @@ options:
- Specifies the operation to be performed on the radius server configured on the device.
- In case of merged, the input mode configuration will be merged with the existing radius server configuration on the device.
- In case of deleted the existing radius server mode configuration will be removed from the device.
+ - In case of replaced, the existing radius server configuration will be replaced with provided configuration.
+ - In case of overridden, the existing radius server configuration will be overridden with the provided configuration.
default: merged
- choices: ['merged', 'deleted']
+ choices: ['merged', 'replaced', 'overridden', 'deleted']
type: str
"""
EXAMPLES = """
@@ -280,8 +284,106 @@ EXAMPLES = """
#---------------------------------------------------------
#RADIUS Statistics
#---------------------------------------------------------
-
-
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+#sonic(config)# do show radius-server
+#---------------------------------------------------------
+#RADIUS Global Configuration
+#---------------------------------------------------------
+#timeout : 10
+#auth-type : pap
+#key configured : Yes
+#--------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY-CONFIG AUTH-PORT PRIORITY TIMEOUT RTSMT VRF SI
+#--------------------------------------------------------------------------------------
+#1.2.3.4 pap No 49 1 5 - - Ethernet0
+#
+- name: Replace radius configurations
+ sonic_radius_server:
+ config:
+ auth_type: mschapv2
+ timeout: 20
+ servers:
+ - host:
+ name: 1.2.3.4
+ auth_type: mschapv2
+ key: mschapv2
+ source_interface: Ethernet12
+ state: replaced
+#
+# After state:
+# ------------
+#
+#sonic(config)# do show radius-server
+#---------------------------------------------------------
+#RADIUS Global Configuration
+#---------------------------------------------------------
+#timeout : 20
+#auth-type : mschapv2
+#key configured : No
+#--------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY-CONFIG AUTH-PORT PRIORITY TIMEOUT RTSMT VRF SI
+#--------------------------------------------------------------------------------------
+#1.2.3.4 mschapv2 Yes 1812 - - - - Ethernet12
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+#sonic(config)# do show radius-server
+#---------------------------------------------------------
+#RADIUS Global Configuration
+#---------------------------------------------------------
+#timeout : 10
+#auth-type : pap
+#key configured : Yes
+#--------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY-CONFIG AUTH-PORT PRIORITY TIMEOUT RTSMT VRF SI
+#--------------------------------------------------------------------------------------
+#1.2.3.4 pap No 49 1 5 - - Ethernet0
+#11.12.13.14 chap Yes 49 10 5 3 - -
+#
+- name: Override radius configurations
+ sonic_radius_server:
+ config:
+ auth_type: mschapv2
+ key: mschapv2
+ timeout: 20
+ servers:
+ - host:
+ name: 1.2.3.4
+ auth_type: mschapv2
+ key: mschapv2
+ source_interface: Ethernet12
+ - host:
+ name: 10.10.11.12
+ auth_type: chap
+ timeout: 30
+ priority: 2
+ port: 49
+ state: overridden
+#
+# After state:
+# ------------
+#
+#sonic(config)# do show radius-server
+#---------------------------------------------------------
+#RADIUS Global Configuration
+#---------------------------------------------------------
+#timeout : 20
+#auth-type : mschapv2
+#key configured : Yes
+#--------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY-CONFIG AUTH-PORT PRIORITY TIMEOUT RTSMT VRF SI
+#--------------------------------------------------------------------------------------
+#1.2.3.4 mschapv2 Yes 1812 - - - - Ethernet12
+#10.10.11.12 chap No 49 2 30 - - -
+#
"""
RETURN = """
before:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_route_maps.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_route_maps.py
new file mode 100644
index 000000000..01327572e
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_route_maps.py
@@ -0,0 +1,1606 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_route_maps
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: sonic_route_maps
+version_added: "2.1.0"
+author: "Kerry Meyer (@kerry-meyer)"
+short_description: route map configuration handling for SONiC
+description:
+ - This module provides configuration management for route map parameters on devices running SONiC.
+options:
+ config:
+ description:
+ - Specifies a list of route map configuration dictionaries
+ type: list
+ elements: dict
+ suboptions:
+ map_name:
+ description:
+ - Name of a route map
+ type: str
+ required: true
+ action:
+ description:
+ - action type for the route map (permit or deny)
+ - This value is required for creation and modification of a route
+ - map or route map attributes as well as for deletion of route map
+ - attributes. It can be omitted only when requesting deletion of a
+ - route map statement or all route map statements for a given route
+ - map map_name.
+ type: str
+ choices:
+ - permit
+ - deny
+ sequence_num:
+ description:
+ - unique number in the range 1-66535 to specify priority of the map
+ - This value is required for creation and modification of a route
+ - map or route map attributes as well as for deletion of route map
+ - attributes. It can be omitted only when requesting deletion of all
+ - route map "statements" for a given route map "map_name".
+ type: int
+ match:
+ description: Criteria for matching the route map to a route
+ type: dict
+ suboptions:
+ as_path:
+ description:
+ - Name of a configured BGP AS path list to be checked for
+ - a match with the target route
+ type: str
+ community:
+ description:
+ - Name of a configured BGP "community" to be checked for
+ - a match with the target route
+ type: str
+ evpn:
+ description:
+ - BGP Ethernet Virtual Private Network to be checked for
+ - a match with the target route
+ type: dict
+ suboptions:
+ default_route:
+ description:
+ - Default EVPN type-5 route
+ type: bool
+ route_type:
+ description:
+ - "Non-default route type: One of the following:"
+ - mac-ip route, EVPN Type 3 Inclusive Multicast Ethernet
+ - Tag (IMET) route, or prefix route
+ type: str
+ choices:
+ - macip
+ - multicast
+ - prefix
+ vni:
+ description:
+ - VNI ID to be checked for a match; specified by a value in the
+ - range 1-16777215
+ type: int
+ ext_comm:
+ description:
+ - Name of a configured BGP 'extended community' to be checked for
+ - a match with the target route
+ type: str
+ interface:
+ description:
+ - Next hop interface name (type and number) to be checked for a
+ - match with the target route. The interface type can be any
+ - of the following; 'Ethernet/Eth' interface or sub-interface,
+ - "'Loopback' interface, 'PortChannel' interface or"
+ - "sub-interface, 'Vlan' interface."
+ type: str
+ ip:
+ description:
+ - IP addresses or IP next hops to be checked for a match with the
+ - target route
+ type: dict
+ suboptions:
+ address:
+ description:
+ - name of an IPv4 prefix list containing a list of address
+ - prefixes to be checked for a match with the target route
+ type: str
+ next_hop:
+ description:
+ - name of a prefix list containing a list of next-hop
+ - prefixes to be checked for a match with the target route
+ type: str
+ ipv6:
+ description:
+ - IPv6 addresses to be checked for a match with the
+ - target route
+ type: dict
+ suboptions:
+ address:
+ description:
+ - name of an IPv6 prefix list containing a list of address
+ - prefixes to be checked for a match with the target route
+ type: str
+ required: true
+ local_preference:
+ description:
+ - local-preference value to be checked for a match with the
+ - target route. This is a value in the range 0-4294967295.
+ type: int
+ metric:
+ description:
+ - metric value to be checked for a match with the target route.
+ - This is a value in the range 0-4294967295.
+ type: int
+ origin:
+ description:
+ - BGP origin to be checked for a match with the target route
+ type: str
+ choices:
+ - egp
+ - igp
+ - incomplete
+ peer:
+ description:
+ - BGP routing peer/neighbor required for a matching route.
+ - I(ip), I(ipv6), and I(interface) are mutually exclusive.
+ type: dict
+ suboptions:
+ ip:
+ description: IPv4 address of a BGP peer
+ type: str
+ ipv6:
+ description: IPv6 address of a BGP peer
+ type: str
+ interface:
+ description:
+ - Name (type and number) of a BGP peer interface.
+ - Allowed interface types are Ethernet or Eth (depending
+ - on the configured interface-naming mode),
+ - Vlan, and Portchannel
+ type: str
+ source_protocol:
+ description: Source protocol required for a matching route
+ type: str
+ choices:
+ - bgp
+ - connected
+ - ospf
+ - static
+ source_vrf:
+ description: Name of the source VRF required for a matching route
+ type: str
+ tag:
+ description:
+ - Tag value required for a matching route
+ - The value must be in the range 1-4294967295
+ type: int
+ set:
+ description: Information to set into a matching route for re-distribution
+ type: dict
+ suboptions:
+ as_path_prepend:
+ description:
+ - String specifying a comma-separated list of AS-path numbers
+ - "to prepend to the BGP AS-path attribute in a matched route."
+ - AS-path values in the list must be in the range
+ - "1-4294967295; for example, 2000,3000"
+ type: str
+ comm_list_delete:
+ description:
+ - String specifying the name of a BGP community list containing
+ - BGP Community values to be deleted from matching routes.
+ type: str
+ community:
+ description:
+ - BGP community attributes to add to or replace the BGP
+ - community attributes in a matching route. Specifying the
+ - "'additive' attribute is allowed only if one of"
+ - the other attributes (other than 'none') is specified.
+ - It causes the specified 'set community' attributes
+ - to be added to the already existing community
+ - "attributes in the matching route. If the 'additive' attribute"
+ - is not specified, the previously existing community attributes
+ - in the matching route are replaced by the configured
+ - "'set community' attributes. Specifying a 'set community' attribute"
+ - of 'none' is mutually exclusive with setting of other community
+ - attributes and causes any community attributes in the matching
+ - route to be removed.
+ type: dict
+ suboptions:
+ community_number:
+ description:
+ - A list of one or more BGP community numbers in the
+ - "form AA:NN where AA and NN are integers in the range"
+ - "0-65535."
+ - "Note: Each community number in the list must be enclosed"
+ - in double quotes to avoid YAML parsing errors due to the
+ - "list values containing an embedded ':' character."
+ type: list
+ elements: str
+ community_attributes:
+ description:
+ - A list of one or more BGP community attributes. The allowed
+ - "values are the following:"
+ - local_as
+ - Do not send outside local AS (well-known community)
+ - no_advertise
+ - Do not advertise to any peer (well-known community)
+ - no_export
+ - Do not export to next AS (well-known community)
+ - no_peer
+ - "The route does not need to be advertised to peers."
+ - (Advertisement of the route can be suppressed based
+ - on other criteria.)
+ - additive
+ - Add the configured 'set community' attributes to
+ - "the matching route (if set to 'true'); Previously existing"
+ - attributes in the matching route are, instead, replaced
+ - by the configured attributes if this attribute is
+ - not specified or if it is set to 'false'.
+ - none
+ - Do not send any community attribute. This attribute
+ - is mutually exclusive with all other 'set community'
+ - attributes. It causes all attributes to be removed
+ - from the matching route.
+ - "I(none) is mutually exclusive with all of the other attributes:"
+ - I(local_as), I(no_advertise), I(no_export), I(no_peer), I(additive),
+ - and I(additive).
+ type: list
+ elements: str
+ choices:
+ - local_as
+ - no_advertise
+ - no_export
+ - no_peer
+ - additive
+ - none
+ extcommunity:
+ description:
+ - BGP extended community attributes to set into a matching route.
+ type: dict
+ suboptions:
+ rt:
+ description:
+ - Route Target VPN extended communities in the format
+ - "ASN:NN or IP-ADDRESS:NN"
+ - "Note: Each rt value in the list must be enclosed"
+ - in double quotes to avoid YAML parsing errors due to the
+ - "list values containing an embedded ':' character."
+ type: list
+ elements: str
+ soo:
+ description:
+ - "Site-of-Origin VPN extended communities in the format"
+ - "ASN:NN or IP-ADDRESS:NN"
+ - "Note: Each rt value in the list must be enclosed"
+ - in double quotes to avoid YAML parsing errors due to the
+ - "list values containing an embedded ':' character."
+ type: list
+ elements: str
+ ip_next_hop:
+ description:
+ - IPv4 next hop address to set into a matching route in the
+ - dotted decimal format A.B.C.D
+ type: str
+ ipv6_next_hop:
+ description:
+ - IPv6 next hop address attributes to set into a matching route
+ type: dict
+ suboptions:
+ global_addr:
+ description:
+ - IPv6 global next hop address to set into a matching
+ - "route in the format A::B"
+ type: str
+ prefer_global:
+ description:
+ - Set the corresponding attribute into a matching route
+ - if the value of this Ansible attribute is 'true'.
+ - The attribute indicates that the routing algorithm must
+ - prefer the global next-hop address over the link-local
+ - address if both exist.
+ type: bool
+ local_preference:
+ description:
+ - "BGP local preference path attribute; integer value in"
+ - the range 0-4294967295
+ type: int
+ metric:
+ description:
+ - route metric value actions
+ - I(value) and I(rtt_action) are mutually exclusive.
+ type: dict
+ suboptions:
+ value:
+ description:
+ - "metric value to be set into a matching route;"
+ - value in the range 0-4294967295
+ type: int
+ rtt_action:
+ description:
+ - Action to take for modifying the metric for a matched
+ - "route using the Round Trip Time (rtt);"
+ - C(set) causes the route metric to be set to the
+ - rtt value.
+ - C(add) causes the rtt value to be added
+ - to the route metric.
+ - C(subtract) causes the rtt value to be
+ - subtracted from route metric.
+ type: str
+ choices:
+ - set
+ - add
+ - subtract
+ origin:
+ description:
+ - "BGP route origin; One of the following must be selected."
+ - "egp (External; remote EGP)"
+ - "igp (Internal; local IGP)"
+ - incomplete (Unknown origin)
+ type: str
+ choices:
+ - egp
+ - igp
+ - incomplete
+ weight:
+ description:
+ - BGP weight for the routing table. The weight must be an
+ - integer in the range 0-4294967295
+ type: int
+ call:
+ description:
+ - Name of a route map to jump to after executing 'match' and 'set'
+ - statements for the current route map.
+ type: str
+
+ state:
+ description:
+ - Specifies the type of configuration update to be performed on the device.
+ - For C(merged), merge specified attributes with existing configured attributes.
+ - For C(deleted), delete the specified attributes from existing configuration.
+ - For C(replaced), replace each modified list or dictionary with the
+ - specified items.
+ - For C(overridden), replace all current configuration for this resource
+ - module with the specified configuration.
+ type: str
+ choices:
+ - merged
+ - deleted
+ - replaced
+ - overridden
+ default: merged
+"""
+EXAMPLES = """
+# Using "merged" state to create initial configuration
+#
+# Before state:
+# -------------
+#
+# sonic# show running-configuration route-map
+# sonic#
+# (No configuration present)
+#
+# -------------
+#
+- name: Merge initial route_maps configuration
+ dellemc.enterprise_sonic.sonic_route_maps:
+ config:
+ - map_name: rm1
+ action: permit
+ sequence_num: 80
+ match:
+ as_path: bgp_as1
+ community: bgp_comm_list1
+ evpn:
+ default_route: true
+ vni: 735
+ ext_comm: bgp_ext_comm1
+ interface: Ethernet4
+ ip:
+ address: ip_pfx_list1
+ ipv6:
+ address: ipv6_pfx_list1
+ local_preference: 8000
+ metric: 400
+ origin: egp
+ peer:
+ ip: 10.20.30.40
+ source_protocol: bgp
+ source_vrf: Vrf1
+ tag: 7284
+ set:
+ as_path_prepend: 200,315,7135
+ comm_list_delete: bgp_comm_list2
+ community:
+ community_number:
+ - "35:58"
+ - "79:150"
+ - "308:650"
+ community_attributes:
+ - local_as
+ - no_advertise
+ - no_export
+ - no_peer
+ - additive
+ extcommunity:
+ rt:
+ - "30:40"
+ soo:
+ - "10.73.14.9:78"
+ ip_next_hop: 10.48.16.18
+ ipv6_next_hop:
+ global_addr: 30::30
+ prefer_global: true
+ local_preference: 635
+ metric:
+ metric_value: 870
+ origin: egp
+ weight: 93471
+ - map_name: rm1
+ action: deny
+ sequence_num: 3047
+ match:
+ evpn:
+ route_type: multicast
+ origin: incomplete
+ peer:
+ interface: Ethernet6
+ source_protocol: ospf
+ set:
+ metric:
+ rtt_action: add
+ origin: incomplete
+ - map_name: rm3
+ action: deny
+ sequence_num: 285
+ match:
+ evpn:
+ route_type: macip
+ origin: igp
+ peer:
+ ipv6: 87:95:15::53
+ source_protocol: connected
+ set:
+ community:
+ community_attributes:
+ - none
+ metric:
+ rtt_action: set
+ origin: igp
+ call: rm1
+ - map_name: rm4
+ action: permit
+ sequence_num: 480
+ match:
+ evpn:
+ route_type: prefix
+ source_protocol: static
+ set:
+ metric:
+ rtt_action: subtract
+ state: merged
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# !
+# route-map rm1 permit 80
+# match as-path bgp_as1
+# match evpn default-route
+# match evpn vni 735
+# match ip address prefix-list ip_pfx_list1
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Ethernet4
+# match community bgp_comm_list1
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match peer 10.20.30.40
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set as-path prepend 200,315,7135
+# set community 35:58 79:150 308:650 local-AS no-advertise no-export no-peer additive
+# set extcommunity rt 30:40
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric 870
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set ipv6 next-hop prefer-global
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# !
+# route-map rm1 deny 3047
+# match evpn route-type multicast
+# match peer Ethernet6
+# match source-protocol ospf
+# match origin incomplete
+# set metric +rtt
+# set origin incomplete
+# !
+# route-map rm3 deny 285
+# match evpn route-type macip
+# call rm1
+# match peer 87:95:15::53
+# match source-protocol connected
+# match origin igp
+# set community none
+# set metric rtt
+# set origin igp
+# !
+# route-map rm4 permit 480
+# match evpn route-type prefix
+# match source-protocol static
+# set metric -rtt
+# ------------
+
+
+# Using "merged" state to update and add configuration
+#
+# Before state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# !
+# route-map rm1 permit 80
+# match as-path bgp_as1
+# match evpn default-route
+# match evpn vni 735
+# match ip address prefix-list ip_pfx_list1
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Ethernet4
+# match community bgp_comm_list1
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match peer 10.20.30.40
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set as-path prepend 200,315,7135
+# set community 35:58 79:150 308:650 local-AS no-advertise no-export no-peer additive
+# set extcommunity rt 30:40
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric 870
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set ipv6 next-hop prefer-global
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# !
+# route-map rm1 deny 3047
+# match evpn route-type multicast
+# match peer Ethernet6
+# match source-protocol ospf
+# match origin incomplete
+# set metric +rtt
+# set origin incomplete
+# !
+# route-map rm3 deny 285
+# match evpn route-type macip
+# call rm1
+# match peer 87:95:15::53
+# match source-protocol connected
+# match origin igp
+# set community none
+# set metric rtt
+# set origin igp
+# !
+# route-map rm4 permit 480
+# match evpn route-type prefix
+# match source-protocol static
+# set metric -rtt
+# ------------
+#
+- name: Merge additional and modified route map configuration
+ dellemc.enterprise_sonic.sonic_route_maps:
+ config:
+ - map_name: rm1
+ action: permit
+ sequence_num: 80
+ match:
+ as_path: bgp_as2
+ community: bgp_comm_list3
+ evpn:
+ route_type: prefix
+ vni: 850
+ interface: Vlan7
+ ip:
+ address: ip_pfx_list2
+ next_hop: ip_pfx_list3
+ peer:
+ interface: Portchannel14
+ set:
+ as_path_prepend: 188,257
+ community:
+ community_number:
+ - "45:736"
+ ipv6_next_hop:
+ prefer_global: false
+ metric:
+ rtt_action: add
+ - map_name: rm1
+ action: deny
+ sequence_num: 3047
+ match:
+ as_path: bgp_as3
+ ext_comm: bgp_ext_comm2
+ origin: igp
+ set:
+ metric:
+ rtt_action: subtract
+ - map_name: rm2
+ action: permit
+ sequence_num: 100
+ match:
+ interface: Ethernet16
+ set:
+ as_path_prepend: 200,300,400
+ ipv6_next_hop:
+ global_addr: 37::58
+ prefer_global: true
+ metric: 8000
+ - map_name: rm3
+ action: deny
+ sequence_num: 285
+ match:
+ local_preference: 14783
+ source_protocol: bgp
+ set:
+ community:
+ community_attributes:
+ - no_advertise
+ state: merged
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# !
+# route-map rm1 permit 80
+# match as-path bgp_as2
+# match evpn default-route
+# match evpn route-type prefix
+# match evpn vni 850
+# match ip address prefix-list ip_pfx_list2
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match community bgp_comm_list3
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match ip next-hop prefix-list ip_pfx_list3
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set as-path prepend 188,257
+# set community 35:58 79:150 308:650 45:736 local-AS no-advertise no-export no-peer additive
+# set extcommunity rt 30:40
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# !
+# route-map rm1 deny 3047
+# match as-path bgp_as3
+# match evpn route-type multicast
+# match ext-community bgp_ext_comm2
+# match peer Ethernet6
+# match source-protocol ospf
+# match origin igp
+# set metric -rtt
+# set origin incomplete
+# !
+# route-map rm2 permit 100
+# match interface Ethernet16
+# set as-path prepend 200,300,400
+# set ipv6 next-hop global 37::58
+# set ipv6 next-hop prefer-global
+# set metric 8000
+# !
+# route-map rm3 deny 285
+# match evpn route-type macip
+# match local-preference 14783
+# call rm1
+# match peer 87:95:15::53
+# match source-protocol bgp
+# match origin igp
+# set community no-advertise
+# set metric rtt
+# set origin igp
+# !
+# route-map rm4 permit 480
+# match evpn route-type prefix
+# match source-protocol static
+# set metric -rtt
+
+
+# Using "replaced" state to replace the contents of a list
+#
+# Before state:
+# ------------
+#
+# sonic(config-route-map)# do show running-configuration route-map rm1 80
+# !
+# route-map rm1 permit 80
+# match as-path bgp_as2
+# match evpn default-route
+# match evpn route-type prefix
+# match evpn vni 850
+# match ip address prefix-list ip_pfx_list2
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match community bgp_comm_list3
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match ip next-hop prefix-list ip_pfx_list3
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set as-path prepend 188,257
+# set community 35:58 79:150 308:650 45:736 local-AS no-export no-peer additive
+# set extcommunity rt 30:40
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# ------------
+- name: Replace a list
+ dellemc.enterprise_sonic.sonic_route_maps:
+ config:
+ - map_name: rm1
+ action: permit
+ sequence_num: 80
+ set:
+ community:
+ community_number:
+ - "15:30"
+ - "26:54"
+ state: replaced
+
+# After state:
+# ------------
+#
+# sonic#show running-configuration route-map rm1 80
+# !
+# route-map rm1 permit 80
+# match as-path bgp_as2
+# match evpn default-route
+# match evpn route-type prefix
+# match evpn vni 850
+# match ip address prefix-list ip_pfx_list2
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match community bgp_comm_list3
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match ip next-hop prefix-list ip_pfx_list3
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set as-path prepend 188,257
+# set community 15:30 26:54 local-AS no-export no-peer additive
+# set extcommunity rt 30:40
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+
+
+# Using "replaced" state to replace the contents of dictionaries
+#
+# Before state:
+# ------------
+# sonic# show running-configuration route-map
+# !
+# route-map rm1 permit 80
+# match as-path bgp_as2
+# match evpn default-route
+# match evpn route-type prefix
+# match evpn vni 850
+# match ip address prefix-list ip_pfx_list2
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match community bgp_comm_list3
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match ip next-hop prefix-list ip_pfx_list3
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set as-path prepend 188,257
+# set community 15:30 26:54 local-AS no-export no-peer additive
+# set extcommunity rt 30:40
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# !
+# route-map rm1 deny 3047
+# match as-path bgp_as3
+# match evpn route-type multicast
+# match ext-community bgp_ext_comm2
+# match peer Ethernet6
+# match source-protocol ospf
+# match origin igp
+# set metric -rtt
+# set origin incomplete
+# !
+# route-map rm2 permit 100
+# match interface Ethernet16
+# set as-path prepend 200,300,400
+# set ipv6 next-hop global 37::58
+# set ipv6 next-hop prefer-global
+# set metric 8000
+# !
+# route-map rm3 deny 285
+# match evpn route-type macip
+# match local-preference 14783
+# call rm1
+# match peer 87:95:15::53
+# match source-protocol bgp
+# match origin igp
+# set community no-advertise
+# set metric rtt
+# set origin igp
+# !
+# route-map rm4 permit 480
+# match evpn route-type prefix
+# match source-protocol static
+# set metric -rtt
+# ------------
+- name: Replace dictionaries
+ dellemc.enterprise_sonic.sonic_route_maps:
+ config:
+ - map_name: rm1
+ action: permit
+ sequence_num: 80
+ match:
+ evpn:
+ route_type: multicast
+ ip:
+ address: ip_pfx_list1
+ set:
+ community:
+ community_attributes:
+ - no_advertise
+ extcommunity:
+ rt:
+ - "20:20"
+
+ - map_name: rm2
+ action: permit
+ sequence_num: 100
+ set:
+ ipv6_next_hop:
+ global_addr: 45::90
+ state: replaced
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# !
+# route-map rm1 permit 80
+# match as-path bgp_as2
+# match evpn route-type multicast
+# match ip address prefix-list ip_pfx_list1
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match community bgp_comm_list3
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set as-path prepend 188,257
+# set community no-advertise
+# set extcommunity rt 20:20
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# !
+# route-map rm1 deny 3047
+# match as-path bgp_as3
+# match evpn route-type multicast
+# match ext-community bgp_ext_comm2
+# match peer Ethernet6
+# match source-protocol ospf
+# match origin igp
+# set metric -rtt
+# set origin incomplete
+# !
+# route-map rm2 permit 100
+# match interface Ethernet16
+# set as-path prepend 200,300,400
+# set metric 8000
+# set ipv6 next-hop global 45::90
+# !
+# route-map rm3 deny 285
+# match evpn route-type macip
+# match local-preference 14783
+# call rm1
+# match peer 87:95:15::53
+# match source-protocol bgp
+# match origin igp
+# set community no-advertise
+# set metric rtt
+# set origin igp
+# !
+# route-map rm4 permit 480
+# match evpn route-type prefix
+# match source-protocol static
+# set metric -rtt
+
+
+# Using "overridden" state to override all existing configuration with new
+# configuration
+#
+# Before state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# !
+# route-map rm1 permit 80
+# match as-path bgp_as2
+# match evpn route-type multicast
+# match ip address prefix-list ip_pfx_list1
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match community bgp_comm_list3
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set as-path prepend 188,257
+# set community no-advertise
+# set extcommunity rt 30:40
+# set extcommunity rt 20:20
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# !
+# route-map rm1 deny 3047
+# match as-path bgp_as3
+# match evpn route-type multicast
+# match ext-community bgp_ext_comm2
+# match peer Ethernet6
+# match source-protocol ospf
+# match origin igp
+# set metric -rtt
+# set origin incomplete
+# !
+# route-map rm2 permit 100
+# match interface Ethernet16
+# set as-path prepend 200,300,400
+# set metric 8000
+# set ipv6 next-hop global 45::90
+# !
+# route-map rm3 deny 285
+# match evpn route-type macip
+# match local-preference 14783
+# call rm1
+# match peer 87:95:15::53
+# match source-protocol bgp
+# match origin igp
+# set community no-advertise
+# set metric rtt
+# set origin igp
+# !
+# route-map rm4 permit 480
+# match evpn route-type prefix
+# match source-protocol static
+# set metric -rtt
+# ------------
+- name: Override all route map configuration with new configuration
+ dellemc.enterprise_sonic.sonic_route_maps:
+ config:
+ - map_name: rm5
+ action: permit
+ sequence_num: 250
+ match:
+ interface: Ethernet28
+ set:
+ as_path_prepend: 150,275
+ metric: 7249
+ state: overridden
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# !
+# route-map rm5 permit 250
+# match interface Ethernet28
+# set as-path prepend 150,275
+# set metric 7249
+
+
+# Using "overridden" state to override all existing configuration with new
+# configuration. (Restore previous configuration.)
+#
+# Before state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# !
+# route-map rm5 permit 250
+# match interface Ethernet28
+# set as-path prepend 150,275
+# set metric 7249
+# ------------
+- name: Override (restore) all route map configuration with older configuration
+ dellemc.enterprise_sonic.sonic_route_maps:
+ config:
+ - map_name: rm1
+ action: permit
+ sequence_num: 80
+ match:
+ as_path: bgp_as2
+ community: bgp_comm_list3
+ evpn:
+ default_route: true
+ route_type: prefix
+ vni: 850
+ ext_comm: bgp_ext_comm1
+ interface: Vlan7
+ ip:
+ address: ip_pfx_list2
+ next_hop: ip_pfx_list3
+ ipv6:
+ address: ipv6_pfx_list1
+ local_preference: 8000
+ metric: 400
+ origin: egp
+ peer:
+ interface: Portchannel14
+ source_protocol: bgp
+ source_vrf: Vrf1
+ tag: 7284
+ set:
+ as_path_prepend: 188,257
+ comm_list_delete: bgp_comm_list2
+ community:
+ community_number:
+ - "35:58"
+ - "79:150"
+ - "308:650"
+ - "45:736"
+ community_attributes:
+ - local_as
+ - no_export
+ - no_peer
+ - additive
+ extcommunity:
+ rt:
+ - "30:40"
+ soo:
+ - "10.73.14.9:78"
+ ip_next_hop: 10.48.16.18
+ ipv6_next_hop:
+ global_addr: 30::30
+ local_preference: 635
+ metric:
+ rtt_action: add
+ origin: egp
+ weight: 93471
+ - map_name: rm1
+ action: deny
+ sequence_num: 3047
+ match:
+ as_path: bgp_as3
+ evpn:
+ route_type: multicast
+ ext_comm: bgp_ext_comm2
+ origin: igp
+ peer:
+ interface: Ethernet6
+ source_protocol: ospf
+ set:
+ metric:
+ rtt_action: subtract
+ origin: incomplete
+ - map_name: rm2
+ action: permit
+ sequence_num: 100
+ match:
+ interface: Ethernet16
+ set:
+ as_path_prepend: 200,300,400
+ ipv6_next_hop:
+ global_addr: 37::58
+ prefer_global: true
+ metric: 8000
+ - map_name: rm3
+ action: deny
+ sequence_num: 285
+ match:
+ evpn:
+ route_type: macip
+ origin: igp
+ peer:
+ ipv6: 87:95:15::53
+ local_preference: 14783
+ source_protocol: bgp
+ set:
+ community:
+ community_attributes:
+ - no_advertise
+ metric:
+ rtt_action: set
+ origin: igp
+ call: rm1
+ - map_name: rm4
+ action: permit
+ sequence_num: 480
+ match:
+ evpn:
+ route_type: prefix
+ source_protocol: static
+ set:
+ metric:
+ rtt_action: subtract
+ state: overridden
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# !
+# route-map rm1 permit 80
+# match as-path bgp_as2
+# match evpn default-route
+# match evpn route-type prefix
+# match evpn vni 850
+# match ip address prefix-list ip_pfx_list2
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match community bgp_comm_list3
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match ip next-hop prefix-list ip_pfx_list3
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set as-path prepend 188,257
+# set community 35:58 79:150 308:650 45:736 local-AS no-export no-peer additive
+# set extcommunity rt 30:40
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# !
+# route-map rm1 deny 3047
+# match as-path bgp_as3
+# match evpn route-type multicast
+# match ext-community bgp_ext_comm2
+# match peer Ethernet6
+# match source-protocol ospf
+# match origin igp
+# set metric -rtt
+# set origin incomplete
+# !
+# route-map rm2 permit 100
+# match interface Ethernet16
+# set as-path prepend 200,300,400
+# set ipv6 next-hop global 37::58
+# set ipv6 next-hop prefer-global
+# set metric 8000
+# !
+# route-map rm3 deny 285
+# match evpn route-type macip
+# match local-preference 14783
+# call rm1
+# match peer 87:95:15::53
+# match source-protocol bgp
+# match origin igp
+# set community no-advertise
+# set metric rtt
+# set origin igp
+# !
+# route-map rm4 permit 480
+# match evpn route-type prefix
+# match source-protocol static
+# set metric -rtt
+
+
+# Using "deleted" state to remove configuration
+#
+# Before state:
+# ------------
+#
+# sonic# show running-configuration route-map rm1 80
+# !
+# route-map rm1 permit 80
+# match as-path bgp_as2
+# match evpn default-route
+# match evpn route-type prefix
+# match evpn vni 850
+# match ip address prefix-list ip_pfx_list2
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match community bgp_comm_list3
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match ip next-hop prefix-list ip_pfx_list3
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set as-path prepend 188,257
+# set community 35:58 79:150 308:650 45:736 local-AS no-export no-peer additive
+# set extcommunity rt 30:40
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# ------------
+- name: Delete selected route map configuration
+ dellemc.enterprise_sonic.sonic_route_maps:
+ config:
+ - map_name: rm1
+ action: permit
+ sequence_num: 80
+ match:
+ as_path: bgp_as2
+ community: bgp_comm_list3
+ evpn:
+ vni: 850
+ ip:
+ address: ip_pfx_list2
+ set:
+ as_path_prepend: 188,257
+ community:
+ community_number:
+ - "35:58"
+ community_attributes:
+ - local_as
+ extcommunity:
+ rt:
+ - "30:40"
+ state: deleted
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration route-map rm1 80
+# !
+# route-map rm1 permit 80
+# match evpn default-route
+# match evpn route-type prefix
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match ip next-hop prefix-list ip_pfx_list3
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set community 79:150 308:650 45:736 no-export no-peer additive
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+
+
+# Using "deleted" state to remove a route map or route map subset
+#
+# Before state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# !
+# route-map rm1 permit 80
+# match evpn default-route
+# match evpn route-type prefix
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match ip next-hop prefix-list ip_pfx_list3
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set community 79:150 308:650 45:736 no-export no-peer additive
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# !
+# route-map rm1 deny 3047
+# match as-path bgp_as3
+# match evpn route-type multicast
+# match ext-community bgp_ext_comm2
+# match peer Ethernet6
+# match source-protocol ospf
+# match origin igp
+# set metric -rtt
+# set origin incomplete
+# !
+# route-map rm2 permit 100
+# match interface Ethernet16
+# set as-path prepend 200,300,400
+# set metric 8000
+# set ipv6 next-hop prefer-global
+# set ipv6 next-hop global 37::58
+# !
+# route-map rm3 deny 285
+# match evpn route-type macip
+# match local-preference 14783
+# call rm1
+# match peer 87:95:15::53
+# match source-protocol bgp
+# match origin igp
+# set community no-advertise
+# set metric rtt
+# set origin igp
+# !
+# route-map rm4 permit 480
+# match evpn route-type prefix
+# match source-protocol static
+# set metric -rtt
+# ------------
+- name: Delete a route map or route map subset
+ dellemc.enterprise_sonic.sonic_route_maps:
+ config:
+ - map_name: rm1
+ sequence_num: 3047
+ - map_name: rm2
+ sequence_num: 100
+ state: deleted
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# !
+# route-map rm1 permit 80
+# match evpn default-route
+# match evpn route-type prefix
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match ip next-hop prefix-list ip_pfx_list3
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set community 79:150 308:650 45:736 no-export no-peer additive
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# !
+# route-map rm3 deny 285
+# match evpn route-type macip
+# match local-preference 14783
+# call rm1
+# match peer 87:95:15::53
+# match source-protocol bgp
+# match origin igp
+# set community no-advertise
+# set metric rtt
+# set origin igp
+# !
+# route-map rm4 permit 480
+# match evpn route-type prefix
+# match source-protocol static
+# set metric -rtt
+
+
+# Using "deleted" state to remove all route map configuration
+#
+# Before state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# !
+# route-map rm1 permit 80
+# match evpn default-route
+# match evpn route-type prefix
+# match ipv6 address prefix-list ipv6_pfx_list1
+# match interface Vlan7
+# match ext-community bgp_ext_comm1
+# match tag 7284
+# match local-preference 8000
+# match source-vrf Vrf1
+# match ip next-hop prefix-list ip_pfx_list3
+# match peer PortChannel 14
+# match source-protocol bgp
+# match metric 400
+# match origin egp
+# set community 79:150 308:650 45:736 no-export no-peer additive
+# set extcommunity soo 10.73.14.9:78
+# set comm-list bgp_comm_list2 delete
+# set metric +rtt
+# set ip next-hop 10.48.16.18
+# set ipv6 next-hop global 30::30
+# set local-preference 635
+# set origin egp
+# set weight 93471
+# !
+# route-map rm3 deny 285
+# match evpn route-type macip
+# match local-preference 14783
+# call rm1
+# match peer 87:95:15::53
+# match source-protocol bgp
+# match origin igp
+# set community no-advertise
+# set metric rtt
+# set origin igp
+# !
+# route-map rm4 permit 480
+# match evpn route-type prefix
+# match source-protocol static
+# set metric -rtt
+# ------------
+- name: Delete all route map configuration
+ dellemc.enterprise_sonic.sonic_route_maps:
+ config: []
+ state: deleted
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration route-map
+# sonic#
+# (no route map configuration present)
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ as the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ as the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.route_maps.route_maps import Route_mapsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.route_maps.route_maps import Route_maps
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Route_mapsArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Route_maps(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_static_routes.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_static_routes.py
index 7a528cdf0..b6f8be3b7 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_static_routes.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_static_routes.py
@@ -33,6 +33,8 @@ DOCUMENTATION = """
---
module: sonic_static_routes
version_added: 2.0.0
+notes:
+ - Supports C(check_mode).
short_description: Manage static routes configuration on SONiC
description:
- This module provides configuration management of static routes for devices running SONiC
@@ -108,6 +110,8 @@ options:
choices:
- merged
- deleted
+ - overridden
+ - replaced
default: merged
"""
EXAMPLES = """
@@ -137,13 +141,13 @@ EXAMPLES = """
metric: 2
tag: 4
track: 8
- - vrf_name: '{{vrf_1}}'
+ - vrf_name: 'VrfReg1'
static_list:
- prefix: '3.0.0.0/8'
next_hops:
- index:
interface: 'eth0'
- nexthop_vrf: '{{vrf_2}}'
+ nexthop_vrf: 'VrfReg2'
next_hop: '4.0.0.0'
metric: 4
tag: 5
@@ -162,7 +166,7 @@ EXAMPLES = """
# ip route 2.0.0.0/8 3.0.0.0 tag 4 track 8 2
# ip route 2.0.0.0/8 interface Ethernet4 tag 2 track 3 1
# ip route vrf VrfReg1 3.0.0.0/8 4.0.0.0 interface Management 0 nexthop-vrf VrfReg2 tag 5 track 6 4
-# ip route vrf VrfREg1 3.0.0.0/8 blackhole tag 20 track 30 10
+# ip route vrf VrfReg1 3.0.0.0/8 blackhole tag 20 track 30 10
#
#
# Modifying previous merge
@@ -170,7 +174,7 @@ EXAMPLES = """
- name: Modify static routes configurations
dellemc.enterprise_sonic.sonic_static_routes:
config:
- - vrf_name: '{{vrf_1}}'
+ - vrf_name: 'VrfReg1'
static_list:
- prefix: '3.0.0.0/8'
next_hops:
@@ -188,7 +192,65 @@ EXAMPLES = """
# ip route 2.0.0.0/8 3.0.0.0 tag 4 track 8 2
# ip route 2.0.0.0/8 interface Ethernet4 tag 2 track 3 1
# ip route vrf VrfReg1 3.0.0.0/8 4.0.0.0 interface Management 0 nexthop-vrf VrfReg2 tag 5 track 6 4
-# ip route vrf VrfREg1 3.0.0.0/8 blackhole tag 22 track 33 11
+# ip route vrf VrfReg1 3.0.0.0/8 blackhole tag 22 track 33 11
+
+
+# Using overridden
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration | grep "ip route"
+# ip route 4.0.0.0/8 2.0.0.0 tag 4 track 8 2
+
+ - name: Override static routes configurations
+ dellemc.enterprise_sonic.sonic_static_routes:
+ config:
+ - vrf_name: 'VrfReg2'
+ static_list:
+ - prefix: '3.0.0.0/8'
+ next_hops:
+ - index:
+ blackhole: True
+ metric: 10
+ tag: 20
+ track: 30
+ state: overridden
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration | grep "ip route"
+# ip route vrf VrfReg2 3.0.0.0/8 blackhole tag 20 track 30 10
+
+
+# Using Replaced
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration | grep "ip route"
+# ip route 4.0.0.0/8 2.0.0.0 tag 4 track 8 2
+
+ - name: Replace static routes configurations
+ dellemc.enterprise_sonic.sonic_static_routes:
+ config:
+ - vrf_name: 'default'
+ static_list:
+ - prefix: '4.0.0.0/8'
+ next_hops:
+ - index:
+ blackhole: True
+ metric: 5
+ tag: 10
+ track: 15
+ state: replaced
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration | grep "ip route"
+# ip route 4.0.0.0/8 blackhole tag 10 track 15 5
# Using deleted
@@ -200,7 +262,7 @@ EXAMPLES = """
# ip route 2.0.0.0/8 3.0.0.0 tag 4 track 8 2
# ip route 2.0.0.0/8 interface Ethernet4 tag 2 track 3 1
# ip route vrf VrfReg1 3.0.0.0/8 4.0.0.0 interface Management 0 nexthop-vrf VrfReg2 tag 5 track 6 4
-# ip route vrf VrfREg1 3.0.0.0/8 blackhole tag 22 track 33 11
+# ip route vrf VrfReg1 3.0.0.0/8 blackhole tag 22 track 33 11
- name: Delete static routes configurations
dellemc.enterprise_sonic.sonic_static_routes:
@@ -211,7 +273,7 @@ EXAMPLES = """
next_hops:
- index:
interface: 'Ethernet4'
- - vrf_name: '{{vrf_1}}'
+ - vrf_name: 'VrfReg1'
state: deleted
# After State:
@@ -237,6 +299,13 @@ after:
sample: >
The configuration returned will always be in the same format
of the parameters above.
+after(generated):
+ description: The generated configuration model invocation.
+ returned: when C(check_mode)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
commands:
description: The set of commands pushed to the remote device.
returned: always
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_stp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_stp.py
new file mode 100644
index 000000000..a25252547
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_stp.py
@@ -0,0 +1,677 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_stp
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: sonic_stp
+version_added: "2.3.0"
+short_description: Manage STP configuration on SONiC
+description:
+ - This module provides configuration management of STP for devices running SONiC
+author: "Shade Talabi (@stalabi1)"
+options:
+ config:
+ description:
+ - Specifies STP configurations
+ - I(mstp), I(pvst) and I(rapid_pvst) are mutually exclusive.
+ type: dict
+ suboptions:
+ global:
+ description:
+ - Global STP configuration
+ type: dict
+ suboptions:
+ enabled_protocol:
+ description:
+ - Specifies the type of STP enabled on the device
+ type: str
+ choices: ['mst', 'pvst', 'rapid_pvst']
+ loop_guard:
+ description:
+ - The loop guard default setting for the bridge
+ type: bool
+ default: False
+ bpdu_filter:
+ description:
+ - Enables edge port BPDU filter
+ type: bool
+ default: False
+ disabled_vlans:
+ description:
+ - List of disabled STP VLANs. The value of a list item can be a single VLAN ID or a range of VLAN IDs
+ - separated by '-' or '..'; for example 70-100 or 70..100.
+ type: list
+ elements: str
+ root_guard_timeout:
+ description:
+ - Specifies root guard recovery timeout in seconds before the port is moved back to forwarding state
+ - Range 5-600
+ type: int
+ portfast:
+ description:
+ - Enables PortFast globally on all access ports
+ - Configurable for pvst protocol
+ type: bool
+ default: False
+ hello_time:
+ description:
+ - Interval in seconds between periodic transmissions of configuration messages by designated ports
+ - Range 1-10
+ type: int
+ default: 2
+ max_age:
+ description:
+ - Maximum age in seconds of the information transmitted by the bridge when it is the root bridge
+ - Range 6-40
+ type: int
+ default: 20
+ fwd_delay:
+ description:
+ - Delay in seconds used by STP bridges to transition root and designated ports to forwarding
+ - Range 4-30
+ type: int
+ default: 15
+ bridge_priority:
+ description:
+ - The manageable component of the bridge identifier
+ - Value must be a multiple of 4096 in the range of 0-61440
+ type: int
+ default: 32768
+ interfaces:
+ description:
+ - Interfaces STP configuration
+ type: list
+ elements: dict
+ suboptions:
+ intf_name:
+ description:
+ - Name of interface
+ type: str
+ required: True
+ edge_port:
+ description:
+ - Configure interface as an STP edge port
+ type: bool
+ default: False
+ link_type:
+ description:
+ - Specifies the interface's link type
+ type: str
+ choices: ['point-to-point', 'shared']
+ guard:
+ description:
+ - Enables root guard or loop guard
+ type: str
+ choices: ['loop', 'root', 'none']
+ bpdu_guard:
+ description:
+ - Enable edge port BPDU guard
+ type: bool
+ default: False
+ bpdu_filter:
+ description:
+ - Enables edge port BPDU filter
+ type: bool
+ default: False
+ portfast:
+ description:
+ - Enable/Disable portfast on specified interface
+ - Configurable for pvst protocol
+ type: bool
+ default: False
+ uplink_fast:
+ description:
+ - Enables uplink fast
+ type: bool
+ default: False
+ shutdown:
+ description:
+ - Port to be shutdown when it receives a BPDU
+ type: bool
+ default: False
+ cost:
+ description:
+ - The port's contribution, when it is the root port, to the root path cost for the bridge
+ type: int
+ port_priority:
+ description:
+ - The manageable component of the port identifier
+ - Range 0-240
+ type: int
+ stp_enable:
+ description:
+ - Enables STP on the interface
+ type: bool
+ default: True
+ mstp:
+ description:
+ - Multi STP configuration
+ type: dict
+ suboptions:
+ mst_name:
+ description:
+ - Name of the MST configuration identifier
+ type: str
+ revision:
+ description:
+ - Revision level of the MST configuration identifier
+ type: int
+ max_hop:
+ description:
+ - Number of bridges in an MST region that a BPDU can traverse before it is discarded
+ type: int
+ hello_time:
+ description:
+ - Interval in seconds between periodic transmissions of configuration messages by designated ports
+ - Range 1-10
+ type: int
+ max_age:
+ description:
+ - Maximum age in seconds of the information transmitted by the bridge when it is the root bridge
+ - Range 6-40
+ type: int
+ fwd_delay:
+ description:
+ - Delay in seconds used by STP bridges to transition root and designated ports to forwarding
+ - Range 4-30
+ type: int
+ mst_instances:
+ description:
+ - Configuration for MST instances
+ type: list
+ elements: dict
+ suboptions:
+ mst_id:
+ description:
+ - Value used to identify MST instance
+ type: int
+ required: True
+ bridge_priority:
+ description:
+ - The manageable component of the bridge identifier
+ - Value must be a multiple of 4096
+ type: int
+ vlans:
+ description:
+ - List of VLANs mapped to the MST instance. The value of a list item can be a single VLAN ID or a range of VLAN IDs
+ - separated by '-' or '..'; for example 70-100 or 70..100.
+ type: list
+ elements: str
+ interfaces:
+ description:
+ - List of STP enabled interfaces
+ type: list
+ elements: dict
+ suboptions:
+ intf_name:
+ description:
+ - Reference to the STP interface
+ type: str
+ required: True
+ cost:
+ description:
+ - The port's contribution, when it is the root port, to the root path cost for the bridge
+ type: int
+ port_priority:
+ description:
+ - The manageable component of the port identifier
+ type: int
+ pvst:
+ description:
+ - Per VLAN STP configuration
+ type: list
+ elements: dict
+ suboptions:
+ vlan_id:
+ description:
+ - VLAN identifier
+ type: int
+ required: True
+ hello_time:
+ description:
+ - Interval in seconds between periodic transmissions of configuration messages by designated ports
+ - Range 1-10
+ type: int
+ max_age:
+ description:
+ - Maximum age in seconds of the information transmitted by the bridge when it is the root bridge
+ - Range 6-40
+ type: int
+ fwd_delay:
+ description:
+ - Delay in seconds used by STP bridges to transition root and designated ports to forwarding
+ - Range 4-30
+ type: int
+ bridge_priority:
+ description:
+ - The manageable component of the bridge identifier
+ - Value must be a multiple of 4096
+ type: int
+ interfaces:
+ description:
+ - List of STP enabled interfaces
+ type: list
+ elements: dict
+ suboptions:
+ intf_name:
+ description:
+ - Reference to the STP interface
+ type: str
+ required: True
+ cost:
+ description:
+ - The port's contribution, when it is the root port, to the root path cost for the bridge
+ type: int
+ port_priority:
+ description:
+ - The manageable component of the port identifier
+ type: int
+ rapid_pvst:
+ description:
+ - Rapid per VLAN STP configuration
+ type: list
+ elements: dict
+ suboptions:
+ vlan_id:
+ description:
+ - VLAN identifier
+ type: int
+ required: True
+ hello_time:
+ description:
+ - Interval in seconds between periodic transmissions of configuration messages by designated ports
+ - Range 1-10
+ type: int
+ max_age:
+ description:
+ - Maximum age in seconds of the information transmitted by the bridge when it is the root bridge
+ - Range 6-40
+ type: int
+ fwd_delay:
+ description:
+ - Delay in seconds used by STP bridges to transition root and designated ports to forwarding
+ - Range 4-30
+ type: int
+ bridge_priority:
+ description:
+ - The manageable component of the bridge identifier
+ - Value must be a multiple of 4096
+ type: int
+ interfaces:
+ description:
+ - List of STP enabled interfaces
+ type: list
+ elements: dict
+ suboptions:
+ intf_name:
+ description:
+ - Reference to the STP interface
+ type: str
+ required: True
+ cost:
+ description:
+ - The port's contribution, when it is the root port, to the root path cost for the bridge
+ type: int
+ port_priority:
+ description:
+ - The manageable component of the port identifier
+ type: int
+ state:
+ description:
+ - The state of the configuration after module completion
+ type: str
+ choices: ['merged', 'deleted', 'replaced', 'overridden']
+ default: merged
+"""
+EXAMPLES = """
+
+# Using merged
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration spanning-tree
+# (No spanning-tree configuration present)
+
+- name: Merge STP configurations
+ dellemc.enterprise_sonic.sonic_stp:
+ config:
+ global:
+ enabled_protocol: mst
+ loop_guard: true
+ bpdu_filter: true
+ disabled_vlans:
+ - 4-6
+ hello_time: 5
+ max_age: 10
+ fwd_delay: 20
+ bridge_priority: 4096
+ interfaces:
+ - intf_name: Ethernet20
+ edge_port: true
+ link_type: shared
+ guard: loop
+ bpdu_guard: true
+ bpdu_filter: true
+ uplink_fast: true
+ shutdown: true
+ cost: 20
+ port_priority: 30
+ stp_enable: true
+ mstp:
+ mst_name: mst1
+ revision: 1
+ max_hop: 3
+ hello_time: 6
+ max_age: 9
+ fwd_delay: 12
+ mst_instances:
+ - mst_id: 1
+ bridge_priority: 2048
+ vlans:
+ - 1
+ interfaces:
+ - intf_name: Ethernet20
+ cost: 60
+ port_priority: 65
+ state: merged
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration spanning-tree
+# no spanning-tree vlan 4-6
+# spanning-tree mode mst
+# spanning-tree edge-port bpdufilter default
+# spanning-tree forward-time 20
+# spanning-tree hello-time 5
+# spanning-tree max-age 10
+# spanning-tree loopguard default
+# spanning-tree mst hello-time 6
+# spanning-tree mst forward-time 12
+# spanning-tree mst max-age 9
+# spanning-tree mst max-hops 3
+# spanning-tree mst 1 priority 2048
+# !
+# spanning-tree mst configuration
+# name mst1
+# revision 1
+# instance 1 vlan 1
+# activate
+# !
+# interface Ethernet20
+# spanning-tree bpdufilter enable
+# spanning-tree guard loop
+# spanning-tree bpduguard port-shutdown
+# spanning-tree cost 20
+# spanning-tree link-type shared
+# spanning-tree port-priority 30
+# spanning-tree port type edge
+# spanning-tree uplinkfast
+# spanning-tree mst 1 cost 60
+# spanning-tree mst 1 port-priority 65
+
+
+# Using replaced
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration spanning-tree
+# no spanning-tree vlan 4-6
+# spanning-tree mode mst
+# spanning-tree edge-port bpdufilter default
+# spanning-tree loopguard default
+# spanning-tree mst hello-time 6
+# spanning-tree mst forward-time 12
+# spanning-tree mst max-age 9
+# spanning-tree mst max-hops 3
+# spanning-tree mst 1 priority 2048
+# !
+# spanning-tree mst configuration
+# name mst1
+# revision 1
+# instance 1 vlan 1
+# activate
+# !
+# interface Ethernet20
+# spanning-tree bpdufilter enable
+# spanning-tree guard loop
+# spanning-tree bpduguard port-shutdown
+# spanning-tree cost 20
+# spanning-tree link-type shared
+# spanning-tree port-priority 30
+# spanning-tree port type edge
+# spanning-tree uplinkfast
+# spanning-tree mst 1 cost 60
+# spanning-tree mst 1 port-priority 65
+
+- name: Replace STP configurations
+ dellemc.enterprise_sonic.sonic_stp:
+ config:
+ interfaces:
+ - intf_name: Ethernet20
+ cost: 25
+ port_priority: 35
+ mstp:
+ mst_name: mst2
+ revision: 2
+ max_hop: 4
+ hello_time: 7
+ max_age: 10
+ fwd_delay: 13
+ state: replaced
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration spanning-tree
+# no spanning-tree vlan 4-6
+# spanning-tree mode mst
+# spanning-tree edge-port bpdufilter default
+# spanning-tree loopguard default
+# spanning-tree mst hello-time 7
+# spanning-tree mst forward-time 13
+# spanning-tree mst max-age 10
+# spanning-tree mst max-hops 4
+# !
+# spanning-tree mst configuration
+# name mst2
+# revision 2
+# activate
+# !
+# interface Ethernet20
+# spanning-tree cost 25
+# spanning-tree port-priority 35
+
+
+# Using overridden
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration spanning-tree
+# no spanning-tree vlan 4-6
+# spanning-tree mode mst
+# spanning-tree edge-port bpdufilter default
+# spanning-tree loopguard default
+# spanning-tree mst hello-time 7
+# spanning-tree mst forward-time 13
+# spanning-tree mst max-age 10
+# spanning-tree mst max-hops 4
+# !
+# spanning-tree mst configuration
+# name mst2
+# revision 2
+# activate
+# !
+# interface Ethernet20
+# spanning-tree cost 25
+# spanning-tree port-priority 35
+
+- name: Override STP configurations
+ dellemc.enterprise_sonic.sonic_stp:
+ config:
+ global:
+ enabled_protocol: pvst
+ bpdu_filter: true
+ root_guard_timeout: 25
+ portfast: true
+ hello_time: 5
+ max_age: 10
+ fwd_delay: 20
+ bridge_priority: 4096
+ pvst:
+ - vlan_id: 1
+ hello_time: 4
+ max_age: 6
+ fwd_delay: 8
+ bridge_priority: 4096
+ interfaces:
+ - intf_name: Ethernet20
+ cost: 10
+ port_priority: 50
+ state: overridden
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration spanning-tree
+# spanning-tree mode pvst
+# spanning-tree edge-port bpdufilter default
+# spanning-tree forward-time 20
+# spanning-tree guard root timeout 25
+# spanning-tree hello-time 5
+# spanning-tree max-age 10
+# spanning-tree priority 4096
+# spanning-tree portfast default
+# spanning-tree vlan 1 hello-time 4
+# spanning-tree vlan 1 forward-time 8
+# spanning-tree vlan 1 max-age 6
+# sonic# show running-configuration interface Ethernet 20 | grep spanning-tree
+# spanning-tree vlan 1 cost 10
+# spanning-tree vlan 1 port-priority 50
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration spanning-tree
+# spanning-tree mode pvst
+# spanning-tree edge-port bpdufilter default
+# spanning-tree forward-time 20
+# spanning-tree guard root timeout 25
+# spanning-tree hello-time 5
+# spanning-tree max-age 10
+# spanning-tree priority 4096
+# spanning-tree portfast default
+# spanning-tree vlan 1 hello-time 4
+# spanning-tree vlan 1 forward-time 8
+# spanning-tree vlan 1 max-age 6
+# sonic# show running-configuration interface Ethernet 20 | grep spanning-tree
+# spanning-tree vlan 1 cost 10
+# spanning-tree vlan 1 port-priority 50
+
+- name: Delete STP configurations
+ dellemc.enterprise_sonic.sonic_stp:
+ config:
+ global:
+ bpdu_filter: true
+ root_guard_timeout: 25
+ pvst:
+ - vlan_id: 1
+ interfaces:
+ - intf_name: Ethernet20
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration spanning-tree
+# spanning-tree mode pvst
+# spanning-tree forward-time 20
+# spanning-tree hello-time 5
+# spanning-tree max-age 10
+# spanning-tree priority 4096
+# spanning-tree portfast default
+# spanning-tree vlan 1 hello-time 4
+# spanning-tree vlan 1 forward-time 8
+# spanning-tree vlan 1 max-age 6
+# sonic# show running-configuration interface Ethernet 20 | grep spanning-tree
+# (No spanning-tree configuration present)
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.stp.stp import StpArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.stp.stp import Stp
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=StpArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Stp(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_system.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_system.py
index efb285a11..8b4d29ae1 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_system.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_system.py
@@ -80,7 +80,7 @@ options:
- In case of merged, the input configuration will be merged with the existing system configuration on the device.
- In case of deleted the existing system configuration will be removed from the device.
default: merged
- choices: ['merged', 'deleted']
+ choices: ['merged', 'replaced', 'overridden', 'deleted']
type: str
"""
EXAMPLES = """
@@ -167,6 +167,89 @@ EXAMPLES = """
#ipv6 anycast-address enable
#interface-naming standard
+# Using replaced
+#
+# Before state:
+# -------------
+#!
+#sonic(config)#do show running-configuration
+#!
+#ip anycast-mac-address aa:bb:cc:dd:ee:ff
+#ip anycast-address enable
+#ipv6 anycast-address enable
+
+- name: Replace system configuration.
+ sonic_system:
+ config:
+ hostname: sonic
+ interface_naming: standard
+ state: replaced
+
+# After state:
+# ------------
+#!
+#SONIC(config)#do show running-configuration
+#!
+#interface-naming standard
+
+# Using replaced
+#
+# Before state:
+# -------------
+#!
+#sonic(config)#do show running-configuration
+#!
+#ip anycast-mac-address aa:bb:cc:dd:ee:ff
+#interface-naming standard
+
+- name: Replace system device configuration.
+ sonic_system:
+ config:
+ hostname: sonic
+ interface_naming: standard
+ anycast_address:
+ ipv6: true
+ ipv4: true
+ state: replaced
+
+# After state:
+# ------------
+#!
+#SONIC(config)#do show running-configuration
+#!
+#ip anycast-address enable
+#ipv6 anycast-address enable
+#interface-naming standard
+
+# Using overridden
+#
+# Before state:
+# -------------
+#!
+#sonic(config)#do show running-configuration
+#!
+#ip anycast-mac-address aa:bb:cc:dd:ee:ff
+#ip anycast-address enable
+#ipv6 anycast-address enable
+
+- name: Override system configuration.
+ sonic_system:
+ config:
+ hostname: sonic
+ interface_naming: standard
+ anycast_address:
+ ipv4: true
+ mac_address: bb:aa:cc:dd:ee:ff
+ state: overridden
+
+# After state:
+# ------------
+#!
+#SONIC(config)#do show running-configuration
+#!
+#ip anycast-mac-address bb:aa:cc:dd:ee:ff
+#ip anycast-address enable
+#interface-naming standard
"""
RETURN = """
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_tacacs_server.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_tacacs_server.py
index 3295e11ba..3361345f5 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_tacacs_server.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_tacacs_server.py
@@ -64,6 +64,7 @@ options:
description:
- Specifies the timeout of the tacacs server.
type: int
+ default: 5
source_interface:
description:
- Specifies the source interface of the tacacs server.
@@ -122,8 +123,10 @@ options:
- Specifies the operation to be performed on the tacacs server configured on the device.
- In case of merged, the input mode configuration will be merged with the existing tacacs server configuration on the device.
- In case of deleted the existing tacacs server mode configuration will be removed from the device.
+ - In case of replaced, the existing tacacs server configuration will be replaced with provided configuration.
+ - In case of overridden, the existing tacacs server configuration will be overridden with the provided configuration.
default: merged
- choices: ['merged', 'deleted']
+ choices: ['merged', 'replaced', 'overridden', 'deleted']
type: str
"""
EXAMPLES = """
@@ -249,8 +252,110 @@ EXAMPLES = """
#HOST AUTH-TYPE KEY PORT PRIORITY TIMEOUT VRF
#------------------------------------------------------------------------------------------------
#1.2.3.4 pap 1234 49 1 5 default
-
-
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+#sonic(config)# do show tacacs-server
+#---------------------------------------------------------
+#TACACS Global Configuration
+#---------------------------------------------------------
+#source-interface : Ethernet12
+#timeout : 10
+#auth-type : pap
+#key configured : Yes
+#--------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY-CONFIG PORT PRIORITY TIMEOUT VRF
+#--------------------------------------------------------------------------------------
+#1.2.3.4 pap No 49 1 5 default
+#
+- name: Replace tacacs configurations
+ sonic_tacacs_server:
+ config:
+ auth_type: pap
+ key: pap
+ source_interface: Ethernet12
+ timeout: 10
+ servers:
+ - host:
+ name: 1.2.3.4
+ auth_type: mschap
+ key: 1234
+ state: replaced
+#
+# After state:
+# ------------
+#
+#sonic(config)# do show tacacs-server
+#---------------------------------------------------------
+#TACACS Global Configuration
+#---------------------------------------------------------
+#source-interface : Ethernet12
+#timeout : 10
+#auth-type : pap
+#key configured : Yes
+#--------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY-CONFIG PORT PRIORITY TIMEOUT VRF
+#--------------------------------------------------------------------------------------
+#1.2.3.4 mschap Yes 49 1 5 default
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+#sonic(config)# do show tacacs-server
+#---------------------------------------------------------
+#TACACS Global Configuration
+#---------------------------------------------------------
+#source-interface : Ethernet12
+#timeout : 10
+#auth-type : pap
+#key configured : Yes
+#--------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY-CONFIG PORT PRIORITY TIMEOUT VRF
+#--------------------------------------------------------------------------------------
+#1.2.3.4 pap No 49 1 5 default
+#11.12.13.14 chap Yes 49 10 5 default
+#
+- name: Override tacacs configurations
+ sonic_tacacs_server:
+ config:
+ auth_type: mschap
+ key: mschap
+ source_interface: Ethernet12
+ timeout: 20
+ servers:
+ - host:
+ name: 1.2.3.4
+ auth_type: mschap
+ key: mschap
+ - host:
+ name: 10.10.11.12
+ auth_type: chap
+ timeout: 30
+ priority: 2
+ state: overridden
+#
+# After state:
+# ------------
+#
+#sonic(config)# do show tacacs-server
+#---------------------------------------------------------
+#TACACS Global Configuration
+#---------------------------------------------------------
+#source-interface : Ethernet12
+#timeout : 20
+#auth-type : mschap
+#key configured : Yes
+#--------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY-CONFIG PORT PRIORITY TIMEOUT VRF
+#--------------------------------------------------------------------------------------
+#1.2.3.4 mschap Yes 49 1 5 default
+#10.10.11.12 chap No 49 2 30 default
+#
"""
RETURN = """
before:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_users.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_users.py
index 7f0855a94..ac528e88d 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_users.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_users.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-# Copyright 2019 Red Hat
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -59,6 +59,8 @@ options:
choices:
- admin
- operator
+ - netadmin
+ - secadmin
password:
description:
- Specifies the password of the user.
@@ -78,8 +80,10 @@ options:
- Specifies the operation to be performed on the users configured on the device.
- In case of merged, the input configuration will be merged with the existing users configuration on the device.
- In case of deleted the existing users configuration will be removed from the device.
+ - In case of replaced, the existing specified user configuration will be replaced with provided configuration.
+ - In case of overridden, the existing users configuration will be overridden with the provided configuration.
default: merged
- choices: ['merged', 'deleted']
+ choices: ['merged', 'deleted', 'overridden', 'replaced']
type: str
"""
EXAMPLES = """
@@ -88,38 +92,44 @@ EXAMPLES = """
# Before state:
# -------------
#
-#do show running-configuration
-#!
-#username admin password $6$sdZt2C7F$3oPSRkkJyLZtsKlFNGWdwssblQWBj5dXM6qAJAQl7dgOfqLSpZJ/n6xf8zPRcqPUFCu5ZKpEtynJ9sZ/S8Mgj. role admin
-#username sysadmin password $6$3QNqJzpFAPL9JqHA$417xFKw6SRn.CiqMFJkDfQJXKJGjeYwi2A8BIyfuWjGimvunOOjTRunVluudey/W9l8jhzN1oewBW5iLxmq2Q1 role admin
-#username sysoperator password $6$s1eTVjcX4Udi69gY$zlYgqwoKRGC6hGL5iKDImN/4BL7LXKNsx9e5PoSsBLs6C80ShYj2LoJAUZ58ia2WNjcHXhTD1p8eU9wyRTCiE0 role operator
-#
-- name: Merge users configurations
+# sonic# show users configured
+# ----------------------------------------------------------------------
+# User Role(s)
+# ----------------------------------------------------------------------
+# admin admin
+# sysadmin admin
+# sysoperator operator
+
+- name: Delete user
dellemc.enterprise_sonic.sonic_users:
config:
- name: sysoperator
state: deleted
+
# After state:
# ------------
#
-#do show running-configuration
-#!
-#username admin password $6$sdZt2C7F$3oPSRkkJyLZtsKlFNGWdwssblQWBj5dXM6qAJAQl7dgOfqLSpZJ/n6xf8zPRcqPUFCu5ZKpEtynJ9sZ/S8Mgj. role admin
-#username sysadmin password $6$3QNqJzpFAPL9JqHA$417xFKw6SRn.CiqMFJkDfQJXKJGjeYwi2A8BIyfuWjGimvunOOjTRunVluudey/W9l8jhzN1oewBW5iLxmq2Q1 role admin
-
+# sonic# show users configured
+# ----------------------------------------------------------------------
+# User Role(s)
+# ----------------------------------------------------------------------
+# admin admin
+# sysadmin admin
# Using deleted
#
# Before state:
# -------------
#
-#do show running-configuration
-#!
-#username admin password $6$sdZt2C7F$3oPSRkkJyLZtsKlFNGWdwssblQWBj5dXM6qAJAQl7dgOfqLSpZJ/n6xf8zPRcqPUFCu5ZKpEtynJ9sZ/S8Mgj. role admin
-#username sysadmin password $6$3QNqJzpFAPL9JqHA$417xFKw6SRn.CiqMFJkDfQJXKJGjeYwi2A8BIyfuWjGimvunOOjTRunVluudey/W9l8jhzN1oewBW5iLxmq2Q1 role admin
-#username sysoperator password $6$s1eTVjcX4Udi69gY$zlYgqwoKRGC6hGL5iKDImN/4BL7LXKNsx9e5PoSsBLs6C80ShYj2LoJAUZ58ia2WNjcHXhTD1p8eU9wyRTCiE0 role operator
-#
-- name: Merge users configurations
+# sonic# show users configured
+# ----------------------------------------------------------------------
+# User Role(s)
+# ----------------------------------------------------------------------
+# admin admin
+# sysadmin admin
+# sysoperator operator
+
+- name: Delete all users configurations except admin
dellemc.enterprise_sonic.sonic_users:
config:
state: deleted
@@ -127,20 +137,23 @@ EXAMPLES = """
# After state:
# ------------
#
-#do show running-configuration
-#!
-#username admin password $6$sdZt2C7F$3oPSRkkJyLZtsKlFNGWdwssblQWBj5dXM6qAJAQl7dgOfqLSpZJ/n6xf8zPRcqPUFCu5ZKpEtynJ9sZ/S8Mgj. role admin
-
+# sonic# show users configured
+# ----------------------------------------------------------------------
+# User Role(s)
+# ----------------------------------------------------------------------
+# admin admin
# Using merged
#
# Before state:
# -------------
#
-#do show running-configuration
-#!
-#username admin password $6$sdZt2C7F$3oPSRkkJyLZtsKlFNGWdwssblQWBj5dXM6qAJAQl7dgOfqLSpZJ/n6xf8zPRcqPUFCu5ZKpEtynJ9sZ/S8Mgj. role admin
-#
+# sonic# show users configured
+# ----------------------------------------------------------------------
+# User Role(s)
+# ----------------------------------------------------------------------
+# admin admin
+
- name: Merge users configurations
dellemc.enterprise_sonic.sonic_users:
config:
@@ -156,14 +169,83 @@ EXAMPLES = """
# After state:
# ------------
-#!
-#do show running-configuration
-#!
-#username admin password $6$sdZt2C7F$3oPSRkkJyLZtsKlFNGWdwssblQWBj5dXM6qAJAQl7dgOfqLSpZJ/n6xf8zPRcqPUFCu5ZKpEtynJ9sZ/S8Mgj. role admin
-#username sysadmin password $6$3QNqJzpFAPL9JqHA$417xFKw6SRn.CiqMFJkDfQJXKJGjeYwi2A8BIyfuWjGimvunOOjTRunVluudey/W9l8jhzN1oewBW5iLxmq2Q1 role admin
-#username sysoperator password $6$s1eTVjcX4Udi69gY$zlYgqwoKRGC6hGL5iKDImN/4BL7LXKNsx9e5PoSsBLs6C80ShYj2LoJAUZ58ia2WNjcHXhTD1p8eU9wyRTCiE0 role operator
+#
+# sonic# show users configured
+# ----------------------------------------------------------------------
+# User Role(s)
+# ----------------------------------------------------------------------
+# admin admin
+# sysadmin admin
+# sysoperator operator
+
+# Using Overridden
+#
+# Before state:
+# -------------
+#
+# sonic# show users configured
+# ----------------------------------------------------------------------
+# User Role(s)
+# ----------------------------------------------------------------------
+# admin admin
+# sysadmin admin
+# sysoperator operator
+
+- name: Override users configurations
+ dellemc.enterprise_sonic.sonic_users:
+ config:
+ - name: user1
+ role: secadmin
+ password: 123abc
+ update_password: always
+ state: overridden
+
+# After state:
+# ------------
+#
+# sonic# show users configured
+# ----------------------------------------------------------------------
+# User Role(s)
+# ----------------------------------------------------------------------
+# admin admin
+# user1 secadmin
+# Using Replaced
+#
+# Before state:
+# -------------
+#
+# sonic# show users configured
+# ----------------------------------------------------------------------
+# User Role(s)
+# ----------------------------------------------------------------------
+# admin admin
+# user1 secadmin
+# user2 operator
+- name: Replace users configurations
+ dellemc.enterprise_sonic.sonic_users:
+ config:
+ - name: user1
+ role: operator
+ password: 123abc
+ update_password: always
+ - name: user2
+ role: netadmin
+ password: 123abc
+ update_password: always
+ state: replaced
+
+# After state:
+# ------------
+#
+# sonic# show users configured
+# ----------------------------------------------------------------------
+# User Role(s)
+# ----------------------------------------------------------------------
+# admin admin
+# user1 operator
+# user2 netadmin
"""
RETURN = """
before:
@@ -180,6 +262,13 @@ after:
sample: >
The configuration returned will always be in the same format
of the parameters above.
+after(generated):
+ description: The generated configuration model invocation.
+ returned: when C(check_mode)
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
commands:
description: The set of commands pushed to the remote device.
returned: always
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vlan_mapping.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vlan_mapping.py
new file mode 100644
index 000000000..985e0523b
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vlan_mapping.py
@@ -0,0 +1,543 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for sonic_vlan_mapping
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community',
+ 'license': 'Apache 2.0'
+}
+
+DOCUMENTATION = """
+---
+module: sonic_vlan_mapping
+author: "Cypher Miller (@Cypher-Miller)"
+version_added: "2.1.0"
+short_description: Configure vlan mappings on SONiC.
+description:
+ - This module provides configuration management for vlan mappings on devices running SONiC.
+ - Vlan mappings only available on TD3 and TD4 devices.
+ - For TD4 devices must enable vlan mapping first (can enable in config-switch-resource).
+options:
+ config:
+ description:
+ - Specifies the vlan mapping related configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of the interface, i.e. Ethernet8, PortChannel2, Eth1/2.
+ required: true
+ type: str
+ mapping:
+ description:
+ - Defining a single vlan mapping.
+ type: list
+ elements: dict
+ suboptions:
+ service_vlan:
+ description:
+ - Configure service provider VLAN ID.
+ - VLAN ID range is 1-4094.
+ required: true
+ type: int
+ vlan_ids:
+ description:
+ - Configure customer VLAN IDs.
+ - If mode is double tagged translation then this VLAN ID represents the outer VLAN ID.
+ - If mode is set to stacking can pass ranges and/or multiple list entries.
+ - Individual VLAN ID or (-) separated range of VLAN IDs.
+ type: list
+ elements: str
+ dot1q_tunnel:
+ description:
+ - Specify whether it is a vlan stacking or translation (false means translation; true means stacking).
+ type: bool
+ default: false
+ inner_vlan:
+ description:
+ - Configure inner customer VLAN ID.
+ - VLAN IDs range is 1-4094.
+ - Only available for double tagged translations.
+ type: int
+ priority:
+ description:
+ - Set priority level of the vlan mapping.
+ - Priority range is 0-7.
+ type: int
+ state:
+ description:
+ - Specifies the operation to be performed on the vlan mappings configured on the device.
+ - In case of merged, the input configuration will be merged with the existing vlan mappings on the device.
+ - In case of deleted, the existing vlan mapping configuration will be removed from the device.
+ - In case of overridden, all existing vlan mappings will be deleted and the specified input configuration will be add.
+ - In case of replaced, the existing vlan mappings on the device will be replaced by the configuration for each vlan mapping.
+ type: str
+ default: merged
+ choices:
+ - merged
+ - deleted
+ - replaced
+ - overridden
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before State:
+# -------------
+#
+#sonic# show running-configuration interface
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 623 2411
+# switchport vlan-mapping 392 inner 590 2755
+#!
+#interface Ethernet16
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-432 dot1q-tunnel 2436 priority 3
+# switchport vlan-mapping 300 dot1q-tunnel 2567 priority 3
+#!
+
+
+ - name: Delete vlan mapping configurations
+ sonic_vlan_mapping:
+ config:
+ - name: Ethernet8
+ mapping:
+ - service_vlan: 2755
+ - name: Ethernet16
+ mapping:
+ - service_vlan: 2567
+ priority: 3
+ - service_vlan: 2436
+ vlan_ids:
+ - 404
+ - 401
+ - 412
+ - 430-431
+ priority: 3
+ state: deleted
+
+# After State:
+# ------------
+#
+#sonic# show running-configuration interface
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 623 2411
+#!
+#interface Ethernet16
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 400,402,406,408,410,420,422,432 dot1q-tunnel 2436
+# switchport vlan-mapping 300 dot1q-tunnel 2567
+#!
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+#sonic# show running-configuration interface
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 623 2411
+# switchport vlan-mapping 392 inner 590 2755
+#!
+#interface Ethernet16
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-431 dot1q-tunnel 2436
+# switchport vlan-mapping 300 dot1q-tunnel 2567 priority 3
+#!
+
+
+ - name: Delete vlan mapping configurations
+ sonic_vlan_mapping:
+ config:
+ - name: Ethernet8
+ - name: Ethernet16
+ mapping:
+ - service_vlan: 2567
+ state: deleted
+
+# After State:
+# ------------
+#
+#sonic# show running-configuration interface
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdo#!
+#interface Ethernet16
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 400-402,406,408,410,420,422,431 dot1q-tunnel 2436
+#!
+
+
+# Using merged
+#
+# Before State:
+# -------------
+#
+#sonic# show running-configuration interface
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 623 2411
+#!
+#interface Ethernet16
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+#!
+#interface PortChannel 2
+# switchport vlan-mapping 345 2999 priority 0
+# switchport vlan-mapping 500,540 dot1q-tunnel 3000
+# no shutdown
+#!
+
+ - name: Add vlan mapping configurations
+ sonic_vlan_mapping:
+ config:
+ - name: Ethernet8
+ mapping:
+ - service_vlan: 2755
+ vlan_ids:
+ - 392
+ dot1q_tunnel: false
+ inner_vlan: 590
+ - name: Ethernet16
+ mapping:
+ - service_vlan: 2567
+ vlan_ids:
+ - 300
+ dot1q_tunnel: true
+ priority: 3
+ - service_vlan: 2436
+ vlan_ids:
+ - 400-402
+ - 404
+ - 406
+ - 408
+ - 410
+ - 412
+ - 420
+ - 422
+ - 430-431
+ dot1q_tunnel: true
+ - name: Portchannel 2
+ mapping:
+ - service_vlan: 2999
+ priority: 4
+ - service_vlan: 3000
+ vlan_ids:
+ - 506-512
+ - 561
+ priority: 5
+ state: merged
+
+# After State:
+# ------------
+#
+#sonic# show running-configuration interface
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 623 2411
+# switchport vlan-mapping 392 inner 590 2755
+#!
+#interface Ethernet16
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-431 dot1q-tunnel 2436
+# switchport vlan-mapping 300 dot1q-tunnel 2567 priority 3
+#!
+#interface PortChannel 2
+# switchport vlan-mapping 345 2999 priority 4
+# switchport vlan-mapping 500,506-512,540,561 dot1q-tunnel 3000 priority 5
+# no shutdown
+#!
+
+
+# Using replaced
+#
+# Before State:
+# -------------
+#
+#sonic# show running-configuration interface
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 623 2411
+# switchport vlan-mapping 392 inner 590 2755
+#!
+#interface Ethernet16
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-431 dot1q-tunnel 2436
+# switchport vlan-mapping 300 dot1q-tunnel 2567 priority 3
+#!
+#interface PortChannel 2
+# switchport vlan-mapping 345 2999 priority 0
+# no shutdown
+#!
+
+ - name: Replace vlan mapping configurations
+ sonic_vlan_mapping:
+ config:
+ - name: Ethernet8
+ mapping:
+ - service_vlan: 2755
+ vlan_ids:
+ - 390
+ dot1q_tunnel: false
+ inner_vlan: 593
+ - name: Ethernet16
+ mapping:
+ - service_vlan: 2567
+ vlan_ids:
+ - 310
+ - 330-340
+ priority: 5
+ - name: Portchannel 2
+ mapping:
+ - service_vlan: 2999
+ vlan_ids:
+ - 345
+ dot1q_tunnel: true
+ priority: 1
+ state: replaced
+
+
+# After State:
+# ------------
+#
+#sonic# show running-configuration interface
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 623 2411
+# switchport vlan-mapping 390 inner 593 2755
+#!
+#interface Ethernet16
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-431 dot1q-tunnel 2436
+# switchport vlan-mapping 310,330-340 dot1q-tunnel 2567 priority 5
+#!
+#interface PortChannel 2
+# switchport vlan-mapping 345 dot1q_tunnel 2999 priority 1
+# no shutdown
+#!
+
+
+# Using overridden
+#
+# Before State:
+# -------------
+#
+#sonic# show running-configuration interface
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 623 2411
+#!
+#interface Ethernet16
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 400-402,404,406,408,410,412,420,422,430-431 dot1q-tunnel 2436
+#!
+
+ - name: Override the vlan mapping configurations
+ sonic_vlan_mapping:
+ config:
+ - name: Ethernet8
+ mapping:
+ - service_vlan: 2755
+ vlan_ids:
+ - 392
+ dot1q_tunnel: false
+ inner_vlan: 590
+ - name: Ethernet16
+ mapping:
+ - service_vlan: 2567
+ vlan_ids:
+ - 300
+ dot1q_tunnel: true
+ priority: 3
+ - name: Portchannel 2
+ mapping:
+ - service_vlan: 2999
+ vlan_ids:
+ - 345
+ priority: 0
+ state: overridden
+
+# After State:
+# ------------
+#
+#sonic# show running-configuration interface
+#!
+#interface Ethernet8
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 392 inner 590 2755
+#!
+#interface Ethernet16
+# mtu 9100
+# speed 400000
+# fec RS
+# unreliable-los auto
+# shutdown
+# switchport vlan-mapping 300 dot1q-tunnel 2567 priority 3
+#!
+#interface PortChannel 2
+# switchport vlan-mapping 345 2999 priority 0
+# no shutdown
+#!
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.vlan_mapping.vlan_mapping import Vlan_mappingArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.vlan_mapping.vlan_mapping import Vlan_mapping
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Vlan_mappingArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Vlan_mapping(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vlans.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vlans.py
index cfd536c79..cd3d7729d 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vlans.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vlans.py
@@ -63,6 +63,8 @@ options:
type: str
choices:
- merged
+ - replaced
+ - overridden
- deleted
default: merged
"""
@@ -109,6 +111,64 @@ EXAMPLES = """
#sonic#
#
+# Using replaced
+
+# Before state:
+# -------------
+#
+#sonic# show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#30 Inactive
+#
+#sonic#
+
+- name: Replace all attributes of specified VLANs with provided configuration
+ dellemc.enterprise_sonic.sonic_vlans:
+ config:
+ - vlan_id: 10
+ state: replaced
+
+# After state:
+# ------------
+#
+#sonic# show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#30 Inactive
+#
+#sonic#
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+#sonic# show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#30 Inactive
+#
+#sonic#
+
+- name: Override device configuration of all VLANs with provided configuration
+ dellemc.enterprise_sonic.sonic_vlans:
+ config:
+ - vlan_id: 10
+ state: overridden
+
+# After state:
+# ------------
+#
+#sonic# show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#
+#sonic#
# Using deleted
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vrfs.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vrfs.py
index 4c881aee6..84233145a 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vrfs.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vrfs.py
@@ -66,6 +66,8 @@ options:
type: str
choices:
- merged
+ - replaced
+ - overridden
- deleted
default: merged
"""
@@ -158,6 +160,88 @@ EXAMPLES = """
#Vrfcheck4 Eth1/5
# Eth1/6
#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+#show ip vrf
+#VRF-NAME INTERFACES
+#----------------------------------------------------------------
+#Vrfcheck1
+#Vrfcheck2
+#Vrfcheck3 Eth1/7
+# Eth1/8
+#
+- name: Overridden VRF configuration
+ dellemc.enterprise_sonic.sonic_vrfs:
+ sonic_vrfs:
+ config:
+ - name: Vrfcheck1
+ members:
+ interfaces:
+ - name: Eth1/3
+ - name: Eth1/14
+ - name: Vrfcheck3
+ members:
+ interfaces:
+ - name: Eth1/5
+ - name: Eth1/6
+ state: overridden
+#
+# After state:
+# ------------
+#
+#show ip vrf
+#VRF-NAME INTERFACES
+#----------------------------------------------------------------
+#Vrfcheck1 Eth1/3
+# Eth1/14
+#Vrfcheck2
+#Vrfcheck3 Eth1/5
+# Eth1/6
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+#show ip vrf
+#VRF-NAME INTERFACES
+#----------------------------------------------------------------
+#Vrfcheck1 Eth1/3
+#Vrfcheck2
+#Vrfcheck3 Eth1/5
+# Eth1/6
+#
+- name: Replace VRF configuration
+ dellemc.enterprise_sonic.sonic_vrfs:
+ sonic_vrfs:
+ config:
+ - name: Vrfcheck1
+ members:
+ interfaces:
+ - name: Eth1/3
+ - name: Eth1/14
+ - name: Vrfcheck3
+ members:
+ interfaces:
+ - name: Eth1/5
+ - name: Eth1/6
+ state: replaced
+#
+# After state:
+# ------------
+#
+#show ip vrf
+#VRF-NAME INTERFACES
+#----------------------------------------------------------------
+#Vrfcheck1 Eth1/3
+# Eth1/14
+#Vrfcheck2
+#Vrfcheck3 Eth1/5
+# Eth1/6
+#
"""
RETURN = """
before:
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vxlans.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vxlans.py
index e6613ba24..0500db79e 100644
--- a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vxlans.py
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vxlans.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-# © Copyright 2020 Dell Inc. or its subsidiaries. All Rights Reserved
+# © Copyright 2023 Dell Inc. or its subsidiaries. All Rights Reserved
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -43,7 +43,6 @@ options:
config:
description:
- A list of VxLAN configurations.
- - source_ip and evpn_nvo are required together.
type: list
elements: dict
suboptions:
@@ -90,6 +89,8 @@ options:
choices:
- merged
- deleted
+ - replaced
+ - overridden
default: merged
"""
EXAMPLES = """
@@ -173,7 +174,7 @@ EXAMPLES = """
- name: vteptest1
source_ip: 1.1.1.1
primary_ip: 2.2.2.2
- evpn_nvo_name: nvo1
+ evpn_nvo: nvo1
vlan_map:
- vni: 101
vlan: 11
@@ -199,7 +200,87 @@ EXAMPLES = """
# map vni 101 vrf Vrfcheck1
# map vni 102 vrf Vrfcheck2
#!
+#
+# Using overridden
+#
+# Before state:
+# -------------
+#
+# do show running-configuration
+#
+#interface vxlan vteptest1
+# source-ip 1.1.1.1
+# primary-ip 2.2.2.2
+# map vni 101 vlan 11
+# map vni 102 vlan 12
+# map vni 101 vrf Vrfcheck1
+# map vni 102 vrf Vrfcheck2
+#!
+#
+- name: "Test vxlans overridden state 01"
+ dellemc.enterprise_sonic.sonic_vxlans:
+ config:
+ - name: vteptest2
+ source_ip: 3.3.3.3
+ primary_ip: 4.4.4.4
+ evpn_nvo: nvo2
+ vlan_map:
+ - vni: 101
+ vlan: 11
+ vrf_map:
+ - vni: 101
+ vrf: Vrfcheck1
+ state: overridden
+#
+# After state:
+# ------------
+#
+# do show running-configuration
+#
+#interface vxlan vteptest2
+# source-ip 3.3.3.3
+# primary-ip 4.4.4.4
+# map vni 101 vlan 11
+# map vni 101 vrf Vrfcheck1
+#!
+#
+# Using replaced
+#
+# Before state:
+# -------------
+#
+# do show running-configuration
+#
+#interface vxlan vteptest2
+# source-ip 3.3.3.3
+# primary-ip 4.4.4.4
+# map vni 101 vlan 11
+# map vni 101 vrf Vrfcheck
+#!
+#
+- name: "Test vxlans replaced state 01"
+ dellemc.enterprise_sonic.sonic_vxlans:
+ config:
+ - name: vteptest2
+ source_ip: 5.5.5.5
+ vlan_map:
+ - vni: 101
+ vlan: 12
+ state: replaced
+#
+# After state:
+# ------------
+#
+# do show running-configuration
+#
+#interface vxlan vteptest2
+# source-ip 5.5.5.5
+# primary-ip 4.4.4.4
+# map vni 101 vlan 12
+# map vni 101 vrf Vrfcheck1
+#!
# """
+
RETURN = """
before:
description: The configuration prior to the model invocation.