summaryrefslogtreecommitdiffstats
path: root/ansible_collections/dellemc/enterprise_sonic/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/dellemc/enterprise_sonic/plugins
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/dellemc/enterprise_sonic/plugins')
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/action/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/action/sonic.py51
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/sonic.py118
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/httpapi/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/httpapi/sonic.py113
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/aaa/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/aaa/aaa.py66
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp/bgp.py97
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_af/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_af/bgp_af.py117
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_as_paths/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_as_paths/bgp_as_paths.py48
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_communities/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_communities/bgp_communities.py59
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_ext_communities/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_ext_communities/bgp_ext_communities.py75
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors/bgp_neighbors.py249
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors_af/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors_af/bgp_neighbors_af.py114
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/facts/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/facts/facts.py53
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/interfaces/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/interfaces/interfaces.py56
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_interfaces/l2_interfaces.py71
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_interfaces/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_interfaces/l3_interfaces.py81
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lag_interfaces/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py67
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mclag/mclag.py82
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ntp/ntp.py89
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_breakout/port_breakout.py57
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/prefix_lists/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/prefix_lists/prefix_lists.py71
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/radius_server/radius_server.py83
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/static_routes/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/static_routes/static_routes.py79
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/system/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/system/system.py64
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/tacacs_server/tacacs_server.py80
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/users/users.py62
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlans/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlans/vlans.py54
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vrfs/vrfs.py66
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vxlans/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vxlans/vxlans.py73
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/aaa/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/aaa/aaa.py236
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp/bgp.py598
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_af/bgp_af.py848
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_as_paths/bgp_as_paths.py304
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_communities/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_communities/bgp_communities.py368
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_ext_communities/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_ext_communities/bgp_ext_communities.py371
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors/bgp_neighbors.py1100
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors_af/bgp_neighbors_af.py584
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/interfaces/interfaces.py354
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_interfaces/l2_interfaces.py414
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_interfaces/l3_interfaces.py515
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py421
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mclag/mclag.py323
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/ntp/ntp.py548
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/port_breakout/port_breakout.py260
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/prefix_lists/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/prefix_lists/prefix_lists.py458
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/radius_server/radius_server.py362
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/static_routes/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/static_routes/static_routes.py344
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/system/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/system/system.py294
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/tacacs_server/tacacs_server.py318
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/users/users.py299
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlans/vlans.py265
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vrfs/vrfs.py303
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vxlans/vxlans.py606
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/aaa/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/aaa/aaa.py111
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp/bgp.py156
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_af/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_af/bgp_af.py258
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_as_paths/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_as_paths/bgp_as_paths.py129
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_communities/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_communities/bgp_communities.py145
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_ext_communities/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_ext_communities/bgp_ext_communities.py158
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors/bgp_neighbors.py229
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors_af/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors_af/bgp_neighbors_af.py222
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/facts.py101
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/interfaces/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/interfaces/interfaces.py147
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py160
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_interfaces/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_interfaces/l3_interfaces.py185
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lag_interfaces/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py135
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mclag/mclag.py139
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ntp/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ntp/ntp.py153
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/port_breakout/port_breakout.py125
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/prefix_lists/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/prefix_lists/prefix_lists.py158
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/radius_server/radius_server.py168
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/static_routes/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/static_routes/static_routes.py173
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/system/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/system/system.py143
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/tacacs_server/tacacs_server.py150
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/users/users.py122
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlans/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlans/vlans.py126
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vrfs/vrfs.py120
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vxlans/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vxlans/vxlans.py207
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/sonic.py155
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/bgp_utils.py611
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/interfaces_util.py55
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/utils.py511
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_aaa.py215
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_api.py158
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp.py390
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_af.py414
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_as_paths.py224
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_communities.py301
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_ext_communities.py288
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors.py1112
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors_af.py451
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_command.py235
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_config.py329
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_facts.py136
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_interfaces.py230
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l2_interfaces.py296
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l3_interfaces.py375
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_lag_interfaces.py238
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_mclag.py516
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ntp.py360
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_port_breakout.py228
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_prefix_lists.py423
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_radius_server.py328
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_static_routes.py267
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_system.py214
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_tacacs_server.py297
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_users.py210
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vlans.py241
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vrfs.py204
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vxlans.py245
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/terminal/__init__.py0
-rw-r--r--ansible_collections/dellemc/enterprise_sonic/plugins/terminal/sonic.py73
162 files changed, 27038 insertions, 0 deletions
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/action/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/action/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/action/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/action/sonic.py b/ansible_collections/dellemc/enterprise_sonic/plugins/action/sonic.py
new file mode 100644
index 000000000..5f7ac3a82
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/action/sonic.py
@@ -0,0 +1,51 @@
+#
+# (c) 2020 Red Hat Inc.
+#
+# (c) 2020 Dell Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+from ansible_collections.ansible.netcommon.plugins.action.network import ActionModule as ActionNetworkModule
+
+from ansible.utils.display import Display
+
+display = Display()
+
+DOCUMENTATION = """
+short_description: Action plugin module for sonic CLI modules
+version_added: 1.0.0
+"""
+
+
+class ActionModule(ActionNetworkModule):
+
+ def run(self, task_vars=None):
+
+ module_name = self._task.action.split('.')[-1]
+ self._config_module = True if module_name == 'sonic_config' else False
+
+ if self._play_context.connection in ('network_cli', 'httpapi'):
+ provider = self._task.args.get('provider', {})
+ if any(provider.values()):
+ display.warning('provider is unnecessary when using network_cli and will be ignored')
+ del self._task.args['provider']
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ return result
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/sonic.py b/ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/sonic.py
new file mode 100644
index 000000000..37f1d872a
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/cliconf/sonic.py
@@ -0,0 +1,118 @@
+#
+# (c) 2020 Red Hat Inc.
+#
+# (c) 2020 Dell Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+name: sonic
+short_description: Use sonic cliconf to run command on Dell OS10 platform
+description:
+ - This sonic plugin provides low level abstraction apis for
+ sending and receiving CLI commands from Dell OS10 network devices.
+"""
+
+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
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
+from ansible.plugins.cliconf import CliconfBase, enable_mode
+
+
+class Cliconf(CliconfBase):
+
+ def get_device_info(self):
+ device_info = {}
+ device_info['network_os'] = 'sonic'
+ return device_info
+
+ @enable_mode
+ def edit_config(self, command):
+ response = []
+ self.send_command("configure terminal")
+ for cmd in to_list(command):
+ if isinstance(cmd, dict):
+ resp = self.get(command=cmd["command"], prompt=cmd["prompt"], answer=cmd["answer"])
+ response.append(resp)
+ else:
+ response.append(self.send_command(to_bytes(cmd)))
+ self.send_command("end")
+ return response
+
+ @enable_mode
+ def get_config(self, source="running", flags=None, format=None):
+ if source not in ("running", "startup"):
+ raise ValueError(
+ "fetching configuration from %s is not supported" % source
+ )
+ if not flags:
+ flags = []
+ if source == "running":
+ cmd = "show running-config "
+ else:
+ cmd = "show startup-config "
+
+ cmd += " ".join(to_list(flags))
+ cmd = cmd.strip()
+ return self.send_command(cmd)
+
+ def get(self, command, prompt=None, answer=None, sendonly=False, newline=True, check_all=False):
+ return self.send_command(command=command, prompt=prompt, answer=answer, sendonly=sendonly, newline=newline, check_all=check_all)
+
+ def get_capabilities(self):
+ result = super(Cliconf, self).get_capabilities()
+ return json.dumps(result)
+
+ def run_commands(self, commands=None, check_rc=True):
+ if commands is None:
+ raise ValueError("'commands' value is required")
+
+ responses = list()
+ for cmd in to_list(commands):
+ if not isinstance(cmd, Mapping):
+ cmd = {'command': cmd}
+
+ output = cmd.pop('output', None)
+ if output:
+ raise ValueError("'output' value %s is not supported for run_commands" % output)
+
+ try:
+ out = self.send_command(**cmd)
+ except AnsibleConnectionFailure as e:
+ if check_rc:
+ raise
+ out = getattr(e, 'err', to_text(e))
+
+ responses.append(out)
+
+ return responses
+
+ def set_cli_prompt_context(self):
+ """
+ Make sure we are in the operational cli mode
+ :return: None
+ """
+ if self._connection.connected:
+ self._update_cli_prompt_context(config_context=')#')
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/httpapi/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/httpapi/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/httpapi/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/httpapi/sonic.py b/ansible_collections/dellemc/enterprise_sonic/plugins/httpapi/sonic.py
new file mode 100644
index 000000000..4745e2e9f
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/httpapi/sonic.py
@@ -0,0 +1,113 @@
+# (c) 2019 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from __future__ import (absolute_import, division, print_function)
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+name: sonic
+short_description: HttpApi Plugin for devices supporting Restconf SONIC API
+description:
+ - This HttpApi plugin provides methods to connect to Restconf SONIC API endpoints.
+version_added: 1.0.0
+options:
+ root_path:
+ type: str
+ description:
+ - Specifies the location of the Restconf root.
+ default: '/restconf'
+ vars:
+ - name: ansible_httpapi_restconf_root
+"""
+
+import json
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.connection import ConnectionError
+from ansible.module_utils.six.moves.urllib.error import HTTPError
+from ansible.plugins.httpapi import HttpApiBase
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
+
+CONTENT_TYPE = 'application/yang-data+json'
+
+
+class HttpApi(HttpApiBase):
+ def send_request(self, data, **message_kwargs):
+ if data:
+ data = json.dumps(data)
+
+ path = '/'.join([self.get_option('root_path').rstrip('/'), message_kwargs.get('path', '').lstrip('/')])
+
+ headers = {
+ 'Content-Type': message_kwargs.get('content_type') or CONTENT_TYPE,
+ 'Accept': message_kwargs.get('accept') or CONTENT_TYPE,
+ }
+ response, response_data = self.connection.send(path, data, headers=headers, method=message_kwargs.get('method'))
+
+ return handle_response(response, response_data, message_kwargs)
+
+ def get(self, command):
+ return self.send_request(path=command, data=None, method='get')
+
+ def edit_config(self, requests):
+ """Send a list of http requests to remote device and return results
+ """
+ if requests is None:
+ raise ValueError("'requests' value is required")
+
+ responses = list()
+ for req in to_list(requests):
+ try:
+ response = self.send_request(**req)
+ except ConnectionError as exc:
+ raise ConnectionError(to_text(exc, errors='surrogate_then_replace'))
+ responses.append(response)
+ return responses
+
+ def get_capabilities(self):
+ result = {}
+ result['rpc'] = []
+ result['network_api'] = 'sonic_rest'
+
+ return json.dumps(result)
+
+
+def handle_response(response, response_data, request_data):
+ response_data = response_data.read()
+ try:
+ if not response_data:
+ response_data = ""
+ else:
+ response_data = json.loads(response_data.decode('utf-8'))
+ except ValueError:
+ pass
+
+ if isinstance(response, HTTPError):
+ if response_data:
+ if 'errors' in response_data:
+ errors = response_data['errors']['error']
+ error_text = '\n'.join((error['error-message'] for error in errors))
+ else:
+ error_text = response_data
+ error_text.update({u'code': response.code})
+ error_text.update({u'request_data': request_data})
+ raise ConnectionError(error_text, code=response.code)
+ raise ConnectionError(to_text(response), code=response.code)
+ return response.getcode(), response_data
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/aaa/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/aaa/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/aaa/__init__.py
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
new file mode 100644
index 000000000..86040892a
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/aaa/aaa.py
@@ -0,0 +1,66 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 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_aaa module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class AaaArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_aaa module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'authentication': {
+ 'options': {
+ 'data': {
+ 'options': {
+ 'fail_through': {'type': 'bool'},
+ 'group': {
+ 'choices': ['ldap', 'radius', 'tacacs+'],
+ 'type': 'str'
+ },
+ 'local': {'type': 'bool'}
+ },
+ 'type': 'dict'
+ }
+ },
+ '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/bgp/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp/__init__.py
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
new file mode 100644
index 000000000..fb7618133
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp/bgp.py
@@ -0,0 +1,97 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# 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_bgp module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class BgpArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_bgp module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'bestpath': {
+ 'options': {
+ 'as_path': {
+ 'options': {
+ 'confed': {'type': 'bool'},
+ 'ignore': {'type': 'bool'},
+ 'multipath_relax': {'type': 'bool'},
+ 'multipath_relax_as_set': {'type': 'bool'}
+ },
+ 'type': 'dict'
+ },
+ 'compare_routerid': {'type': 'bool'},
+ 'med': {
+ 'options': {
+ 'confed': {'type': 'bool'},
+ 'missing_as_worst': {'type': 'bool'},
+ 'always_compare_med': {'type': 'bool'}
+ },
+ 'type': 'dict'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'bgp_as': {'required': True, 'type': 'str'},
+ 'log_neighbor_changes': {'type': 'bool'},
+ 'router_id': {'type': 'str'},
+ "max_med": {
+ "options": {
+ "on_startup": {
+ "options": {
+ "timer": {"type": "int"},
+ "med_val": {"type": "int"}
+ },
+ "type": "dict"
+ }
+ },
+ "type": "dict"
+ },
+ 'timers': {
+ 'options': {
+ 'holdtime': {'type': 'int'},
+ 'keepalive_interval': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'vrf_name': {'default': 'default', 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted'],
+ 'default': 'merged'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_af/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_af/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_af/__init__.py
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
new file mode 100644
index 000000000..ac22210ee
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_af/bgp_af.py
@@ -0,0 +1,117 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# 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_bgp_af module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Bgp_afArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_bgp_af module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'address_family': {
+ 'options': {
+ 'afis': {
+ 'elements': 'dict',
+ 'options': {
+ 'advertise_pip': {'type': 'bool'},
+ 'advertise_pip_ip': {'type': 'str'},
+ 'advertise_pip_peer_ip': {'type': 'str'},
+ 'advertise_svi_ip': {'type': 'bool'},
+ 'route_advertise_list': {
+ 'elements': 'dict',
+ 'options': {
+ 'advertise_afi': {
+ 'choices': ['ipv4', 'ipv6'],
+ 'required': True,
+ 'type': 'str'
+ },
+ 'route_map': {
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'advertise_all_vni': {'type': 'bool'},
+ 'advertise_default_gw': {'type': 'bool'},
+ 'afi': {
+ 'choices': ['ipv4', 'ipv6', 'l2vpn'],
+ 'required': True,
+ 'type': 'str'
+ },
+ 'max_path': {
+ 'options': {
+ 'ebgp': {'type': 'int'},
+ 'ibgp': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'network': {'type': 'list', 'elements': 'str'},
+ 'dampening': {'type': 'bool'},
+ 'redistribute': {
+ 'elements': 'dict',
+ 'options': {
+ 'metric': {'type': 'str'},
+ 'protocol': {
+ 'choices': ['ospf', 'static', 'connected'],
+ 'required': True,
+ 'type': 'str'
+ },
+ 'route_map': {'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'safi': {
+ 'choices': ['unicast', 'evpn'],
+ 'default': 'unicast',
+ 'type': 'str'
+ }
+ },
+ 'required_together': [['afi', 'safi']],
+ 'type': 'list'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'bgp_as': {'required': True, 'type': 'str'},
+ 'vrf_name': {'default': 'default', 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted'],
+ 'default': 'merged'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_as_paths/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_as_paths/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_as_paths/__init__.py
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
new file mode 100644
index 000000000..dec9b930e
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_as_paths/bgp_as_paths.py
@@ -0,0 +1,48 @@
+#
+# -*- 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)
+
+#############################################
+# 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_bgp_as_paths module
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Bgp_as_pathsArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_bgp_as_paths module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {'config': {'elements': 'dict',
+ 'options': {'permit': {'required': False, 'type': 'bool'},
+ 'members': {'elements': 'str',
+ 'required': False,
+ 'type': 'list'},
+ 'name': {'required': True, 'type': 'str'}},
+ 'type': 'list'},
+ '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/bgp_communities/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_communities/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_communities/__init__.py
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
new file mode 100644
index 000000000..867e55204
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_communities/bgp_communities.py
@@ -0,0 +1,59 @@
+#
+# -*- 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)
+
+#############################################
+# 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_bgp_communities module
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Bgp_communitiesArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_bgp_communities module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {'config': {'elements': 'dict',
+ 'options': {'aann': {'type': 'str'},
+ 'local_as': {'type': 'bool'},
+ 'match': {'choices': ['ALL', 'ANY'],
+ 'default': 'ANY',
+ 'type': 'str'},
+ 'members': {'options': {'regex': {'elements': 'str',
+ 'type': 'list'}},
+ 'type': 'dict'},
+ 'name': {'required': True, 'type': 'str'},
+ 'no_advertise': {'type': 'bool'},
+ 'no_export': {'type': 'bool'},
+ 'no_peer': {'type': 'bool'},
+ 'permit': {'type': 'bool'},
+ 'type': {'choices': ['standard', 'expanded'],
+ 'default': 'standard',
+ 'type': 'str'}},
+ 'type': 'list'},
+ '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/bgp_ext_communities/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_ext_communities/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_ext_communities/__init__.py
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
new file mode 100644
index 000000000..aec0f364a
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_ext_communities/bgp_ext_communities.py
@@ -0,0 +1,75 @@
+#
+# -*- 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)
+
+#############################################
+# 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_bgp_ext_communities module
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Bgp_ext_communitiesArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_bgp_ext_communities module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'match': {
+ 'choices': ['all', 'any'],
+ 'default': 'any',
+ 'type': 'str'
+ },
+ 'members': {
+ 'mutually_exclusive': [
+ ['regex', 'route_origin'],
+ ['regex', 'route_target']
+ ],
+ 'options': {
+ 'regex': {'elements': 'str', 'type': 'list'},
+ 'route_origin': {'elements': 'str', 'type': 'list'},
+ 'route_target': {'elements': 'str', 'type': 'list'}
+ },
+ 'type': 'dict'
+ },
+ 'name': {'required': True, 'type': 'str'},
+ 'permit': {'type': 'bool'},
+ 'type': {
+ 'choices': ['standard', 'expanded'],
+ 'default': 'standard',
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ '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/bgp_neighbors/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors/bgp_neighbors.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors/bgp_neighbors.py
new file mode 100644
index 000000000..02e695fb4
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors/bgp_neighbors.py
@@ -0,0 +1,249 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# 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_bgp_neighbors module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Bgp_neighborsArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_bgp_neighbors module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'bgp_as': {'required': True, 'type': 'str'},
+ 'neighbors': {
+ 'elements': 'dict',
+ 'options': {
+ 'neighbor': {'required': True, 'type': 'str'},
+ 'remote_as': {
+ 'mutually_exclusive': [['peer_type', 'peer_as']],
+ 'options': {
+ 'peer_type': {'type': 'str', 'choices': ['internal', 'external']},
+ 'peer_as': {'type': 'int'},
+ },
+ 'type': 'dict'
+ },
+ 'peer_group': {'type': 'str'},
+ 'bfd': {
+ 'options': {
+ 'enabled': {'type': 'bool'},
+ 'check_failure': {'type': 'bool'},
+ 'profile': {'type': 'str'}
+ },
+ 'type': 'dict'
+ },
+ 'advertisement_interval': {'type': 'int'},
+ 'timers': {
+ 'options': {
+ 'holdtime': {'type': 'int'},
+ 'keepalive': {'type': 'int'},
+ 'connect_retry': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'capability': {
+ 'options': {
+ 'dynamic': {'type': 'bool'},
+ 'extended_nexthop': {'type': 'bool'},
+ },
+ 'type': 'dict'
+ },
+ 'auth_pwd': {
+ 'options': {
+ 'pwd': {'required': True, 'type': 'str'},
+ 'encrypted': {'default': 'False', 'type': 'bool'},
+ },
+ 'type': 'dict'
+ },
+ 'nbr_description': {'type': 'str'},
+ 'disable_connected_check': {'type': 'bool'},
+ 'dont_negotiate_capability': {'type': 'bool'},
+ 'ebgp_multihop': {
+ 'options': {
+ 'enabled': {'default': 'False', 'type': 'bool'},
+ 'multihop_ttl': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'enforce_first_as': {'type': 'bool'},
+ 'enforce_multihop': {'type': 'bool'},
+ 'local_address': {'type': 'str'},
+ 'local_as': {
+ 'options': {
+ 'as': {'required': True, 'type': 'int'},
+ 'no_prepend': {'type': 'bool'},
+ 'replace_as': {'type': 'bool'},
+ },
+ 'type': 'dict'
+ },
+ 'override_capability': {'type': 'bool'},
+ 'passive': {'default': 'False', 'type': 'bool'},
+ 'port': {'type': 'int'},
+ 'shutdown_msg': {'type': 'str'},
+ 'solo': {'type': 'bool'},
+ 'strict_capability_match': {'type': 'bool'},
+ 'ttl_security': {'type': 'int'},
+ 'v6only': {'type': 'bool'}
+ },
+ 'type': 'list'
+ },
+ 'peer_group': {
+ 'elements': 'dict',
+ 'options': {
+ 'name': {'required': True, 'type': 'str'},
+ 'remote_as': {
+ 'mutually_exclusive': [['peer_type', 'peer_as']],
+ 'options': {
+ 'peer_type': {'type': 'str', 'choices': ['internal', 'external']},
+ 'peer_as': {'type': 'int'},
+ },
+ 'type': 'dict'
+ },
+ 'address_family': {
+ 'options': {
+ 'afis': {
+ 'elements': 'dict',
+ 'options': {
+ 'activate': {'type': 'bool'},
+ 'afi': {
+ 'choices': ['ipv4', 'ipv6', 'l2vpn'],
+ 'type': 'str'
+ },
+ 'allowas_in': {
+ 'mutually_exclusive': [['origin', 'value']],
+ 'options': {
+ 'origin': {'type': 'bool'},
+ 'value': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'ip_afi': {
+ 'options': {
+ 'default_policy_name': {'type': 'str'},
+ 'send_default_route': {'default': False, 'type': 'bool'}
+ },
+ 'type': 'dict'
+ },
+ 'prefix_limit': {
+ 'options': {
+ 'max_prefixes': {'type': 'int'},
+ 'prevent_teardown': {'default': False, 'type': 'bool'},
+ 'warning_threshold': {'type': 'int'},
+ 'restart_timer': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'prefix_list_in': {'type': 'str'},
+ 'prefix_list_out': {'type': 'str'},
+ 'safi': {
+ 'choices': ['unicast', 'evpn'],
+ 'type': 'str'
+ },
+ },
+ 'required_together': [['afi', 'safi']],
+ 'type': 'list'
+ },
+ },
+ 'type': 'dict'
+ },
+ 'bfd': {
+ 'options': {
+ 'enabled': {'type': 'bool'},
+ 'check_failure': {'type': 'bool'},
+ 'profile': {'type': 'str'}
+ },
+ 'type': 'dict'
+ },
+ 'advertisement_interval': {'type': 'int'},
+ 'timers': {
+ 'options': {
+ 'holdtime': {'type': 'int'},
+ 'keepalive': {'type': 'int'},
+ 'connect_retry': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'capability': {
+ 'options': {
+ 'dynamic': {'type': 'bool'},
+ 'extended_nexthop': {'type': 'bool'},
+ },
+ 'type': 'dict'
+ },
+ 'auth_pwd': {
+ 'options': {
+ 'pwd': {'required': True, 'type': 'str'},
+ 'encrypted': {'default': 'False', 'type': 'bool'},
+ },
+ 'type': 'dict'
+ },
+ 'pg_description': {'type': 'str'},
+ 'disable_connected_check': {'type': 'bool'},
+ 'dont_negotiate_capability': {'type': 'bool'},
+ 'ebgp_multihop': {
+ 'options': {
+ 'enabled': {'default': 'False', 'type': 'bool'},
+ 'multihop_ttl': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'enforce_first_as': {'type': 'bool'},
+ 'enforce_multihop': {'type': 'bool'},
+ 'local_address': {'type': 'str'},
+ 'local_as': {
+ 'options': {
+ 'as': {'required': True, 'type': 'int'},
+ 'no_prepend': {'type': 'bool'},
+ 'replace_as': {'type': 'bool'},
+ },
+ 'type': 'dict'
+ },
+ 'override_capability': {'type': 'bool'},
+ 'passive': {'default': 'False', 'type': 'bool'},
+ 'shutdown_msg': {'type': 'str'},
+ 'solo': {'type': 'bool'},
+ 'strict_capability_match': {'type': 'bool'},
+ 'ttl_security': {'type': 'int'}
+ },
+ 'type': 'list'
+ },
+ 'vrf_name': {'default': 'default', 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted'],
+ 'default': 'merged'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors_af/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors_af/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors_af/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors_af/bgp_neighbors_af.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors_af/bgp_neighbors_af.py
new file mode 100644
index 000000000..6cafc9227
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/bgp_neighbors_af/bgp_neighbors_af.py
@@ -0,0 +1,114 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# 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_bgp_neighbors_af module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Bgp_neighbors_afArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_bgp_neighbors_af module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'bgp_as': {'required': True, 'type': 'str'},
+ 'neighbors': {
+ 'elements': 'dict',
+ 'options': {
+ 'address_family': {
+ 'elements': 'dict',
+ 'options': {
+ 'activate': {'type': 'bool'},
+ 'afi': {
+ 'choices': ['ipv4', 'ipv6', 'l2vpn'],
+ 'required': True,
+ 'type': 'str'
+ },
+ 'allowas_in': {
+ 'mutually_exclusive': [['origin', 'value']],
+ 'options': {
+ 'origin': {'type': 'bool'},
+ 'value': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'ip_afi': {
+ 'options': {
+ 'default_policy_name': {'type': 'str'},
+ 'send_default_route': {'default': False, 'type': 'bool'}
+ },
+ 'type': 'dict'
+ },
+ 'prefix_limit': {
+ 'options': {
+ 'max_prefixes': {'type': 'int'},
+ 'prevent_teardown': {'default': False, 'type': 'bool'},
+ 'warning_threshold': {'type': 'int'},
+ 'restart_timer': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'prefix_list_in': {'type': 'str'},
+ 'prefix_list_out': {'type': 'str'},
+ 'route_map': {
+ 'elements': 'dict',
+ 'options': {
+ 'direction': {'type': 'str'},
+ 'name': {'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'route_reflector_client': {'type': 'bool'},
+ 'route_server_client': {'type': 'bool'},
+ 'safi': {
+ 'choices': ['unicast', 'evpn'],
+ 'default': 'unicast',
+ 'type': 'str'
+ }
+ },
+ 'required_together': [['afi', 'safi']],
+ 'type': 'list'
+ },
+ 'neighbor': {'required': True, 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'vrf_name': {'default': 'default', 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted'],
+ 'default': 'merged'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/facts/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/facts/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/facts/__init__.py
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
new file mode 100644
index 000000000..3a4d02989
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/facts/facts.py
@@ -0,0 +1,53 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 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 arg spec for the sonic facts module.
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class FactsArgs(object): # pylint: disable=R0903
+
+ """ The arg spec for the sonic facts module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ choices = [
+ 'all',
+ 'vlans',
+ 'interfaces',
+ 'l2_interfaces',
+ 'l3_interfaces',
+ 'lag_interfaces',
+ 'bgp',
+ 'bgp_af',
+ 'bgp_neighbors',
+ 'bgp_neighbors_af',
+ 'bgp_as_paths',
+ 'bgp_communities',
+ 'bgp_ext_communities',
+ 'mclag',
+ 'prefix_lists',
+ 'vrfs',
+ 'vxlans',
+ 'users',
+ 'system',
+ 'port_breakout',
+ 'aaa',
+ 'tacacs_server',
+ 'radius_server',
+ 'static_routes',
+ 'ntp'
+ ]
+
+ argument_spec = {
+ 'gather_subset': dict(default=['!config'], type='list', elements='str'),
+ 'gather_network_resources': dict(choices=choices, type='list', elements='str'),
+ }
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/interfaces/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/interfaces/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/interfaces/__init__.py
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
new file mode 100644
index 000000000..76c36a90b
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/interfaces/interfaces.py
@@ -0,0 +1,56 @@
+#
+# -*- 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)
+
+#############################################
+# 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_interfaces module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class InterfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_interfaces module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ "config": {
+ "elements": "dict",
+ "options": {
+ "description": {"type": "str"},
+ "enabled": {"type": "bool"},
+ "mtu": {"type": "int"},
+ "name": {"required": True, "type": "str"}
+ },
+ "type": "list"
+ },
+ "state": {
+ "choices": ["merged", "deleted"],
+ "default": "merged",
+ "type": "str"
+ }
+ }
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
new file mode 100644
index 000000000..bbebe2d54
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l2_interfaces/l2_interfaces.py
@@ -0,0 +1,71 @@
+#
+# -*- 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)
+
+#############################################
+# 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_interfaces module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class L2_interfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_l2_interfaces module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'access': {
+ 'options': {
+ 'vlan': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'name': {'required': True, 'type': 'str'},
+ 'trunk': {
+ 'options': {
+ 'allowed_vlans': {
+ 'elements': 'dict',
+ 'options': {
+ 'vlan': {'type': 'int'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'dict'
+ }
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ }
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_interfaces/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_interfaces/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_interfaces/__init__.py
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
new file mode 100644
index 000000000..6e83289cc
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/l3_interfaces/l3_interfaces.py
@@ -0,0 +1,81 @@
+#
+# -*- 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)
+
+#############################################
+# 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_interfaces module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class L3_interfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_l3_interfaces module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'ipv4': {
+ 'mutually_exclusive': [['addresses', 'anycast_addresses']],
+ 'options': {
+ 'addresses': {
+ 'elements': 'dict',
+ 'options': {
+ 'address': {'type': 'str'},
+ 'secondary': {'default': 'False', 'type': 'bool'}
+ },
+ 'type': 'list'
+ },
+ 'anycast_addresses': {'elements': 'str', 'type': 'list'},
+ },
+ 'type': 'dict'
+ },
+ 'ipv6': {
+ 'options': {
+ 'addresses': {
+ 'elements': 'dict',
+ 'options': {
+ 'address': {'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'enabled': {'type': 'bool'}
+ },
+ 'type': 'dict'
+ },
+ 'name': {'required': True, 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ '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/lag_interfaces/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lag_interfaces/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lag_interfaces/__init__.py
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
new file mode 100644
index 000000000..867d61a27
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/lag_interfaces/lag_interfaces.py
@@ -0,0 +1,67 @@
+#
+# -*- 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)
+
+#############################################
+# 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_lag_interfaces module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Lag_interfacesArgs(object): # pylint: disable=R0903
+
+ """The arg spec for the sonic_lag_interfaces module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ "config": {
+ "elements": "dict",
+ "options": {
+ "members": {
+ "options": {
+ "interfaces": {
+ "elements": "dict",
+ "options": {
+ "member": {"type": "str"}
+ },
+ "type": "list"
+ }
+ },
+ "type": "dict"
+ },
+ "name": {"required": True, "type": "str"},
+ "mode": {"type": "str", "choices": ["static", "lacp"]}
+ },
+ "type": "list"
+ },
+ "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/mclag/mclag.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mclag/mclag.py
new file mode 100644
index 000000000..be3c38ca2
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/mclag/mclag.py
@@ -0,0 +1,82 @@
+#
+# -*- 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)
+
+#############################################
+# 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_mclag module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class MclagArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_mclag module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'domain_id': {'required': True, 'type': 'int'},
+ 'keepalive': {'type': 'int'},
+ 'peer_address': {'type': 'str'},
+ 'peer_link': {'type': 'str'},
+ 'members': {
+ 'options': {
+ 'portchannels': {
+ 'elements': 'dict',
+ 'options': {
+ 'lag': {'type': 'str'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'session_timeout': {'type': 'int'},
+ 'source_address': {'type': 'str'},
+ 'system_mac': {'type': 'str'},
+ 'unique_ip': {
+ 'options': {
+ 'vlans': {
+ 'elements': 'dict',
+ 'options': {
+ 'vlan': {'type': 'str'}
+ },
+ 'type': 'list'
+ }
+ },
+ '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/ntp/ntp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ntp/ntp.py
new file mode 100644
index 000000000..062520af9
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/ntp/ntp.py
@@ -0,0 +1,89 @@
+#
+# -*- 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_ntp module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class NtpArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_ntp module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'enable_ntp_auth': {'type': 'bool'},
+ 'ntp_keys': {
+ 'elements': 'dict',
+ 'options': {
+ 'encrypted': {'type': 'bool'},
+ 'key_id': {'required': True,
+ 'type': 'int',
+ 'no_log': True},
+ 'key_type': {'type': 'str',
+ 'choices': ['NTP_AUTH_SHA1',
+ 'NTP_AUTH_MD5',
+ 'NTP_AUTH_SHA2_256']},
+ 'key_value': {'type': 'str', 'no_log': True}
+ },
+ 'type': 'list',
+ 'no_log': True
+ },
+ 'servers': {
+ 'elements': 'dict',
+ 'options': {
+ 'address': {'required': True,
+ 'type': 'str'},
+ 'key_id': {'type': 'int', 'no_log': True},
+ 'maxpoll': {'type': 'int'},
+ 'minpoll': {'type': 'int'}
+ },
+ 'type': 'list'
+ },
+ 'source_interfaces': {
+ 'elements': 'str',
+ 'type': 'list'
+ },
+ 'trusted_keys': {
+ 'elements': 'int',
+ 'type': 'list',
+ 'no_log': True
+ },
+ 'vrf': {'type': 'str'}
+ },
+ '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/port_breakout/port_breakout.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_breakout/port_breakout.py
new file mode 100644
index 000000000..3b8f4a5a3
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/port_breakout/port_breakout.py
@@ -0,0 +1,57 @@
+#
+# -*- 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)
+
+#############################################
+# 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_breakout module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Port_breakoutArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_port_breakout module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'mode': {
+ 'choices': ['1x100G', '1x400G', '1x40G', '2x100G', '2x200G',
+ '2x50G', '4x100G', '4x10G', '4x25G', '4x50G'],
+ 'type': 'str'
+ },
+ 'name': {'required': True, 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted'],
+ 'default': 'merged'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/prefix_lists/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/prefix_lists/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/prefix_lists/__init__.py
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
new file mode 100644
index 000000000..d043ae6f8
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/prefix_lists/prefix_lists.py
@@ -0,0 +1,71 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# 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_prefix_lists module
+"""
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+class Prefix_listsArgs: # pylint: disable=R0903
+ """The arg spec for the sonic_prefix_lists module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'afi': {
+ 'choices': ['ipv4', 'ipv6'],
+ 'default': 'ipv4',
+ 'type': 'str'
+ },
+ 'name': {'required': True, 'type': 'str'},
+ 'prefixes': {
+ 'elements': 'dict',
+ 'options': {
+ 'action': {
+ 'choices': ['permit', 'deny'],
+ 'required': True,
+ 'type': 'str'
+ },
+ 'ge': {'type': 'int'},
+ 'le': {'type': 'int'},
+ 'prefix': {'required': True, 'type': 'str'},
+ 'sequence': {'required': True, 'type': 'int'}},
+ 'type': 'list'
+ }
+ },
+ 'type': 'list'
+ },
+ '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/radius_server/radius_server.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/radius_server/radius_server.py
new file mode 100644
index 000000000..a56147a5b
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/radius_server/radius_server.py
@@ -0,0 +1,83 @@
+#
+# -*- coding: utf-8 -*-
+# © Copyright 2021 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_radius_server module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Radius_serverArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_radius_server module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'auth_type': {
+ 'choices': ['pap', 'chap', 'mschapv2'],
+ 'default': 'pap',
+ 'type': 'str'
+ },
+ 'key': {'type': 'str', 'no_log': True},
+ 'nas_ip': {'type': 'str'},
+ 'retransmit': {'type': 'int'},
+ 'servers': {
+ 'options': {
+ 'host': {
+ 'elements': 'dict',
+ 'options': {
+ 'auth_type': {
+ 'choices': ['pap', 'chap', 'mschapv2'],
+ 'type': 'str'
+ },
+ 'key': {'type': 'str', 'no_log': True},
+ 'name': {'type': 'str'},
+ 'port': {'type': 'int'},
+ 'priority': {'type': 'int'},
+ 'retransmit': {'type': 'int'},
+ 'source_interface': {'type': 'str'},
+ 'timeout': {'type': 'int'},
+ 'vrf': {'type': 'str'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'statistics': {'type': 'bool'},
+ 'timeout': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted'],
+ 'default': 'merged'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/static_routes/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/static_routes/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/static_routes/__init__.py
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
new file mode 100644
index 000000000..a146f1ecd
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/static_routes/static_routes.py
@@ -0,0 +1,79 @@
+#
+# -*- 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_static_routes module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Static_routesArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_static_routes module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'static_list': {
+ 'elements': 'dict',
+ 'options': {
+ 'next_hops': {
+ 'elements': 'dict',
+ 'options': {
+ 'index': {
+ 'required': True,
+ 'options': {
+ 'blackhole': {'type': 'bool', 'default': False},
+ 'interface': {'type': 'str'},
+ 'nexthop_vrf': {'type': 'str'},
+ 'next_hop': {'type': 'str'}
+ },
+ 'type': 'dict'
+ },
+ 'metric': {'type': 'int'},
+ 'tag': {'type': 'int'},
+ 'track': {'type': 'int'}
+ },
+ 'type': 'list'
+ },
+ 'prefix': {'required': True, 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'vrf_name': {'required': True, 'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ '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/system/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/system/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/system/__init__.py
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
new file mode 100644
index 000000000..b08c5f4bc
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/system/system.py
@@ -0,0 +1,64 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 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_system module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class SystemArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_system module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'anycast_address': {
+ 'options': {
+ 'ipv4': {'type': 'bool'},
+ 'ipv6': {'type': 'bool'},
+ 'mac_address': {'type': 'str'}
+ },
+ 'type': 'dict'
+ },
+ 'hostname': {'type': 'str'},
+ 'interface_naming': {
+ 'choices': ['standard', 'native'],
+ 'type': 'str'
+ }
+ },
+ '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/tacacs_server/tacacs_server.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/tacacs_server/tacacs_server.py
new file mode 100644
index 000000000..aad1746d4
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/tacacs_server/tacacs_server.py
@@ -0,0 +1,80 @@
+#
+# -*- coding: utf-8 -*-
+# © Copyright 2021 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_tacacs_server module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Tacacs_serverArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_tacacs_server module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'options': {
+ 'auth_type': {
+ 'choices': ['pap', 'chap', 'mschap', 'login'],
+ 'default': 'pap',
+ 'type': 'str'
+ },
+ 'key': {'type': 'str', 'no_log': True},
+ 'servers': {
+ 'options': {
+ 'host': {
+ 'elements': 'dict',
+ 'options': {
+ 'auth_type': {
+ 'choices': ['pap', 'chap', 'mschap', 'login'],
+ 'default': 'pap',
+ 'type': 'str'
+ },
+ 'key': {'type': 'str', 'no_log': True},
+ 'name': {'type': 'str'},
+ 'port': {'default': 49, 'type': 'int'},
+ 'priority': {'default': 1, 'type': 'int'},
+ 'timeout': {'default': 5, 'type': 'int'},
+ 'vrf': {'default': 'default', 'type': 'str'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'source_interface': {'type': 'str'},
+ 'timeout': {'type': 'int'}
+ },
+ 'type': 'dict'
+ },
+ 'state': {
+ 'choices': ['merged', '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
new file mode 100644
index 000000000..db23d78e0
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/users/users.py
@@ -0,0 +1,62 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# 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_users module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class UsersArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_users module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'name': {'required': True, 'type': 'str'},
+ 'password': {'type': 'str', 'no_log': True},
+ 'role': {
+ 'choices': ['admin', 'operator'],
+ 'type': 'str'
+ },
+ 'update_password': {
+ 'choices': ['always', 'on_create'],
+ 'default': 'always',
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted'],
+ 'default': 'merged'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlans/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlans/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlans/__init__.py
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
new file mode 100644
index 000000000..971fc8571
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vlans/vlans.py
@@ -0,0 +1,54 @@
+#
+# -*- 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)
+
+#############################################
+# 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_vlans module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class VlansArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_vlans module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'vlan_id': {'required': True, 'type': 'int'},
+ 'description': {'type': 'str'}
+ },
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', '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
new file mode 100644
index 000000000..e074936a7
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vrfs/vrfs.py
@@ -0,0 +1,66 @@
+#
+# -*- 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)
+
+#############################################
+# 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_vrfs module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class VrfsArgs(object): # pylint: disable=R0903
+
+ """The arg spec for the sonic_vrfs module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ "config": {
+ "elements": "dict",
+ "options": {
+ "members": {
+ "options": {
+ "interfaces": {
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str"}
+ },
+ "type": "list"
+ }
+ },
+ "type": "dict"
+ },
+ "name": {"required": True, "type": "str"}
+ },
+ "type": "list"
+ },
+ "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/vxlans/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vxlans/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vxlans/__init__.py
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
new file mode 100644
index 000000000..dd475b78a
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/argspec/vxlans/vxlans.py
@@ -0,0 +1,73 @@
+#
+# -*- 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)
+
+#############################################
+# 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_vxlans module
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class VxlansArgs(object): # pylint: disable=R0903
+ """The arg spec for the sonic_vxlans module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'evpn_nvo': {'type': 'str'},
+ 'name': {'required': True, 'type': 'str'},
+ 'source_ip': {'type': 'str'},
+ 'primary_ip': {'type': 'str'},
+ 'vlan_map': {
+ 'elements': 'dict',
+ 'options': {
+ 'vlan': {'type': 'int'},
+ 'vni': {'required': True, 'type': 'int'}
+ },
+ 'type': 'list'
+ },
+ 'vrf_map': {
+ 'elements': 'dict',
+ 'options': {
+ 'vni': {'required': True, 'type': 'int'},
+ 'vrf': {'type': 'str'}
+ },
+ 'type': 'list'
+ }
+ },
+ 'required_together': [['source_ip', 'evpn_nvo']],
+ 'type': 'list'
+ },
+ 'state': {
+ 'choices': ['merged', 'deleted'],
+ 'default': 'merged',
+ 'type': 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/aaa/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/aaa/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/aaa/__init__.py
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
new file mode 100644
index 000000000..85f93bc73
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/aaa/aaa.py
@@ -0,0 +1,236 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 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_aaa 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 (
+ update_states,
+ get_diff,
+)
+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
+)
+
+PATCH = 'patch'
+DELETE = 'delete'
+
+
+class Aaa(ConfigBase):
+ """
+ The sonic_aaa class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'aaa',
+ ]
+
+ def __init__(self, module):
+ super(Aaa, self).__init__(module)
+
+ def get_aaa_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)
+ aaa_facts = facts['ansible_network_resources'].get('aaa')
+ if not aaa_facts:
+ return []
+ return aaa_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_aaa_facts = self.get_aaa_facts()
+ commands, requests = self.set_config(existing_aaa_facts)
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ self.edit_config(requests)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_aaa_facts = self.get_aaa_facts()
+
+ result['before'] = existing_aaa_facts
+ if result['changed']:
+ result['after'] = changed_aaa_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def edit_config(self, requests):
+ try:
+ response = edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ def set_config(self, existing_aaa_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_aaa_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 not want:
+ want = {}
+
+ if state == 'deleted':
+ commands = self._state_deleted(want, have)
+ elif state == 'merged':
+ diff = get_diff(want, have)
+ commands = self._state_merged(want, have, diff)
+ return commands
+
+ 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 = []
+ requests = []
+ if diff:
+ requests = self.get_create_aaa_request(diff)
+ if len(requests) > 0:
+ 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:
+ if have:
+ requests = self.get_delete_all_aaa_request(have)
+ if len(requests) > 0:
+ commands = update_states(have, "deleted")
+ else:
+ want = utils.remove_empties(want)
+ new_have = self.remove_default_entries(have)
+ d_diff = get_diff(want, new_have, is_skeleton=True)
+ diff_want = get_diff(want, d_diff, is_skeleton=True)
+ if diff_want:
+ requests = self.get_delete_all_aaa_request(diff_want)
+ if len(requests) > 0:
+ commands = update_states(diff_want, "deleted")
+ return commands, requests
+
+ def get_create_aaa_request(self, commands):
+ requests = []
+ aaa_path = 'data/openconfig-system:system/aaa'
+ method = PATCH
+ aaa_payload = self.build_create_aaa_payload(commands)
+ if aaa_payload:
+ request = {'path': aaa_path, 'method': method, 'data': aaa_payload}
+ requests.append(request)
+ return requests
+
+ def build_create_aaa_payload(self, commands):
+ payload = {}
+ if "authentication" in commands and commands["authentication"]:
+ payload = {"openconfig-system:aaa": {"authentication": {"config": {"authentication-method": []}}}}
+ if "local" in commands["authentication"]["data"] and commands["authentication"]["data"]["local"]:
+ payload['openconfig-system:aaa']['authentication']['config']['authentication-method'].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)
+ if "fail_through" in commands["authentication"]["data"]:
+ cfg = {'failthrough': str(commands["authentication"]["data"]["fail_through"])}
+ payload['openconfig-system:aaa']['authentication']['config'].update(cfg)
+ return payload
+
+ def remove_default_entries(self, data):
+ new_data = {}
+ if not data:
+ return new_data
+ else:
+ new_data = {'authentication': {'data': {}}}
+ local = data['authentication']['data'].get('local', None)
+ if local is not None:
+ new_data["authentication"]["data"]["local"] = local
+ group = data['authentication']['data'].get('group', None)
+ if group is not None:
+ new_data["authentication"]["data"]["group"] = group
+ fail_through = data['authentication']['data'].get('fail_through', None)
+ if fail_through is not None:
+ new_data["authentication"]["data"]["fail_through"] = fail_through
+ return new_data
+
+ def get_delete_all_aaa_request(self, have):
+ requests = []
+ if "authentication" in have and have["authentication"]:
+ if "local" in have["authentication"]["data"] or "group" in have["authentication"]["data"]:
+ request = self.get_authentication_method_delete_request()
+ requests.append(request)
+ if "fail_through" in have["authentication"]["data"]:
+ request = self.get_failthrough_delete_request()
+ requests.append(request)
+ return requests
+
+ def get_authentication_method_delete_request(self):
+ path = 'data/openconfig-system:system/aaa/authentication/config/authentication-method'
+ method = DELETE
+ request = {'path': path, 'method': method}
+ return request
+
+ def get_failthrough_delete_request(self):
+ path = 'data/openconfig-system:system/aaa/authentication/config/failthrough'
+ method = DELETE
+ request = {'path': path, 'method': method}
+ return request
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
new file mode 100644
index 000000000..fd4d5c57e
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp/bgp.py
@@ -0,0 +1,598 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_bgp 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
+
+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 (
+ 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 (
+ to_request,
+ 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
+
+PATCH = 'patch'
+POST = 'post'
+DELETE = 'delete'
+PUT = 'put'
+
+TEST_KEYS = [{'config': {'vrf_name': '', 'bgp_as': ''}}]
+
+
+class Bgp(ConfigBase):
+ """
+ The sonic_bgp class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'bgp',
+ ]
+
+ network_instance_path = '/data/openconfig-network-instance:network-instances/network-instance'
+ protocol_bgp_path = 'protocols/protocol=BGP,bgp/bgp'
+ log_neighbor_changes_path = 'logging-options/config/log-neighbor-state-changes'
+ holdtime_path = 'config/hold-time'
+ keepalive_path = 'config/keepalive-interval'
+
+ def __init__(self, module):
+ super(Bgp, self).__init__(module)
+
+ def get_bgp_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)
+ bgp_facts = facts['ansible_network_resources'].get('bgp')
+ if not bgp_facts:
+ bgp_facts = []
+ return bgp_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+ existing_bgp_facts = self.get_bgp_facts()
+ commands, requests = self.set_config(existing_bgp_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_bgp_facts = self.get_bgp_facts()
+
+ result['before'] = existing_bgp_facts
+ if result['changed']:
+ result['after'] = changed_bgp_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_bgp_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_bgp_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, 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, diff)
+ 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 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_bgp_requests(commands, have)
+ 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
+
+ :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
+ # if want is none, then delete all the bgps
+ if not want:
+ commands = have
+ is_delete_all = True
+ else:
+ commands = want
+
+ requests = self.get_delete_bgp_requests(commands, have, is_delete_all)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def get_delete_single_bgp_request(self, vrf_name):
+ delete_path = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ return ({'path': delete_path, 'method': DELETE})
+
+ def get_delete_max_med_requests(self, vrf_name, max_med, match):
+ requests = []
+
+ match_max_med = match.get('max_med', None)
+ if not max_med or not match_max_med:
+ return requests
+
+ generic_del_path = '%s=%s/%s/global/' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+
+ match_max_med_on_startup = match.get('max_med', {}).get('on_startup')
+ if match_max_med_on_startup:
+ requests.append({'path': generic_del_path + "max-med/config/time", 'method': DELETE})
+ requests.append({'path': generic_del_path + "max-med/config/max-med-val", 'method': DELETE})
+
+ return requests
+
+ def get_delete_bestpath_requests(self, vrf_name, bestpath, match):
+ requests = []
+
+ match_bestpath = match.get('bestpath', None)
+ if not bestpath or not match_bestpath:
+ return requests
+
+ route_selection_del_path = '%s=%s/%s/global/route-selection-options/config/' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ multi_paths_del_path = '%s=%s/%s/global/use-multiple-paths/ebgp/config/' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ generic_del_path = '%s=%s/%s/global/' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+
+ if bestpath.get('compare_routerid', None) and match_bestpath.get('compare_routerid', None):
+ url = '%s=%s/%s/global/route-selection-options' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ route_selection_cfg = {}
+ route_selection_cfg['external-compare-router-id'] = False
+ payload = {'route-selection-options': {'config': route_selection_cfg}}
+ requests.append({'path': url, 'data': payload, 'method': PATCH})
+ # requests.append({'path': route_selection_del_path + "external-compare-router-id", 'method': DELETE})
+
+ match_as_path = match_bestpath.get('as_path', None)
+ as_path = bestpath.get('as_path', None)
+ if as_path and match_as_path:
+ if as_path.get('confed', None) is not None and match_as_path.get('confed', None):
+ requests.append({'path': route_selection_del_path + "compare-confed-as-path", 'method': DELETE})
+ if as_path.get('ignore', None) is not None and match_as_path.get('ignore', None):
+ requests.append({'path': route_selection_del_path + "ignore-as-path-length", 'method': DELETE})
+ if as_path.get('multipath_relax', None) is not None and match_as_path.get('multipath_relax', None):
+ requests.append({'path': multi_paths_del_path + "allow-multiple-as", 'method': DELETE})
+ if as_path.get('multipath_relax_as_set', None) is not None and match_as_path.get('multipath_relax_as_set', None):
+ requests.append({'path': multi_paths_del_path + "as-set", 'method': DELETE})
+
+ match_med = match_bestpath.get('med', None)
+ med = bestpath.get('med', None)
+ if med and match_med:
+ if med.get('confed', None) is not None and match_med.get('confed', None):
+ requests.append({'path': route_selection_del_path + "med-confed", 'method': DELETE})
+ if med.get('missing_as_worst', None) is not None and match_med.get('missing_as_worst', None):
+ requests.append({'path': route_selection_del_path + "med-missing-as-worst", 'method': DELETE})
+ if med.get('always_compare_med', None) is not None and match_med.get('always_compare_med', None):
+ requests.append({'path': route_selection_del_path + "always-compare-med", 'method': DELETE})
+ if med.get('max_med_val', None) is not None and match_med.get('max_med_val', None):
+ requests.append({'path': generic_del_path + "max-med/config/admin-max-med-val", 'method': DELETE})
+
+ return requests
+
+ def get_delete_all_bgp_requests(self, commands):
+ requests = []
+ for cmd in commands:
+ requests.append(self.get_delete_single_bgp_request(cmd['vrf_name']))
+ return requests
+
+ def get_delete_specific_bgp_param_request(self, command, match):
+ vrf_name = command['vrf_name']
+ requests = []
+
+ router_id = command.get('router_id', None)
+ timers = command.get('timers', None)
+ holdtime = None
+ keepalive = None
+ if timers:
+ holdtime = command['timers'].get('holdtime', None)
+ keepalive = command['timers'].get('keepalive_interval', None)
+ log_neighbor_changes = command.get('log_neighbor_changes', None)
+ bestpath = command.get('bestpath', None)
+
+ if router_id and match.get('router_id', None):
+ 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 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})
+
+ if keepalive and match['timers'].get('keepalive_interval', None) != 60:
+ url = '%s=%s/%s/global/config/keepalive-interval' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ requests.append({"path": url, "method": DELETE})
+
+ # Delete the log_neighbor_changes only when existing values is True.
+ if log_neighbor_changes is not None and match.get('log_neighbor_changes', None):
+ del_log_neighbor_req = self.get_modify_log_change_request(vrf_name, False)
+ if del_log_neighbor_req:
+ requests.append(del_log_neighbor_req)
+
+ bestpath_del_reqs = self.get_delete_bestpath_requests(vrf_name, bestpath, match)
+ if bestpath_del_reqs:
+ requests.extend(bestpath_del_reqs)
+
+ max_med = command.get('max_med', None)
+ max_med_del_reqs = self.get_delete_max_med_requests(vrf_name, max_med, match)
+ if max_med_del_reqs:
+ requests.extend(max_med_del_reqs)
+
+ return requests
+
+ def get_delete_bgp_requests(self, commands, have, is_delete_all):
+ requests = []
+ if is_delete_all:
+ requests = self.get_delete_all_bgp_requests(commands)
+ else:
+ for cmd in commands:
+ vrf_name = cmd['vrf_name']
+ as_val = cmd['bgp_as']
+
+ match = next((cfg for cfg in have if cfg['vrf_name'] == vrf_name and cfg['bgp_as'] == as_val), None)
+ 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):
+ requests.extend(self.get_delete_specific_bgp_param_request(cmd, match))
+ else:
+ # delete entire bgp
+ requests.append(self.get_delete_single_bgp_request(vrf_name))
+
+ 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 requests
+
+ def get_modify_multi_paths_req(self, vrf_name, as_path):
+ request = None
+ if not as_path:
+ return request
+
+ method = PATCH
+ multipath_cfg = {}
+
+ as_path_multipath_relax = as_path.get('multipath_relax', None)
+ as_path_multipath_relax_as_set = as_path.get('multipath_relax_as_set', None)
+
+ if as_path_multipath_relax is not None:
+ multipath_cfg['allow-multiple-as'] = as_path_multipath_relax
+ if as_path_multipath_relax_as_set is not None:
+ multipath_cfg['as-set'] = as_path_multipath_relax_as_set
+
+ payload = {"openconfig-network-instance:config": multipath_cfg}
+ if payload:
+ url = '%s=%s/%s/global/use-multiple-paths/ebgp/config' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ request = {"path": url, "method": method, "data": payload}
+
+ return request
+
+ def get_modify_route_selection_req(self, vrf_name, compare_routerid, as_path, med):
+ requests = []
+ if compare_routerid is None and not as_path and not med:
+ return requests
+
+ route_selection_cfg = {}
+
+ as_path_confed = None
+ as_path_ignore = None
+
+ med_confed = None
+ med_missing_as_worst = None
+ always_compare_med = None
+
+ if compare_routerid is not None:
+ route_selection_cfg['external-compare-router-id'] = compare_routerid
+
+ if as_path:
+ as_path_confed = as_path.get('confed', None)
+ as_path_ignore = as_path.get('ignore', None)
+ if as_path_confed is not None:
+ route_selection_cfg['compare-confed-as-path'] = as_path_confed
+ if as_path_ignore is not None:
+ route_selection_cfg['ignore-as-path-length'] = as_path_ignore
+
+ if med:
+ med_confed = med.get('confed', None)
+ med_missing_as_worst = med.get('missing_as_worst', None)
+ always_compare_med = med.get('always_compare_med', None)
+ if med_confed is not None:
+ route_selection_cfg['med-confed'] = med_confed
+ if med_missing_as_worst is not None:
+ route_selection_cfg['med-missing-as-worst'] = med_missing_as_worst
+ if always_compare_med is not None:
+ route_selection_cfg['always-compare-med'] = always_compare_med
+ method = PATCH
+ payload = {'route-selection-options': {'config': route_selection_cfg}}
+
+ if payload:
+ url = '%s=%s/%s/global/route-selection-options' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ request = {"path": url, "method": method, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def get_modify_bestpath_requests(self, vrf_name, bestpath):
+ requests = []
+ if not bestpath:
+ return requests
+
+ compare_routerid = bestpath.get('compare_routerid', None)
+ as_path = bestpath.get('as_path', None)
+ med = bestpath.get('med', None)
+
+ route_selection_req = self.get_modify_route_selection_req(vrf_name, compare_routerid, as_path, med)
+ if route_selection_req:
+ requests.extend(route_selection_req)
+
+ multi_paths_req = self.get_modify_multi_paths_req(vrf_name, as_path)
+ if multi_paths_req:
+ requests.append(multi_paths_req)
+
+ return requests
+
+ def get_modify_max_med_requests(self, vrf_name, max_med):
+ request = None
+ method = PATCH
+ payload = {}
+ on_startup_time = max_med.get('on_startup', {}).get('timer')
+ on_startup_med = max_med.get('on_startup', {}).get('med_val')
+
+ if on_startup_med is not None:
+ payload = {
+ 'max-med': {
+ 'config': {
+ 'max-med-val': on_startup_med,
+ 'time': on_startup_time
+ }
+ }
+ }
+
+ if payload:
+ url = '%s=%s/%s/global/max-med' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ request = {"path": url, "method": method, "data": payload}
+
+ return [request]
+
+ def get_modify_log_change_request(self, vrf_name, log_neighbor_changes):
+ request = None
+ method = PATCH
+ payload = {}
+
+ if log_neighbor_changes is not None:
+ payload['log-neighbor-state-changes'] = log_neighbor_changes
+
+ if payload:
+ url = '%s=%s/%s/global/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, self.log_neighbor_changes_path)
+ request = {"path": url, "method": method, "data": payload}
+
+ return request
+
+ def get_modify_holdtime_request(self, vrf_name, holdtime):
+ request = None
+ method = PATCH
+ payload = {}
+
+ if holdtime is not None:
+ payload['hold-time'] = str(holdtime)
+
+ if payload:
+ url = '%s=%s/%s/global/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, self.holdtime_path)
+ request = {"path": url, "method": method, "data": payload}
+
+ return request
+
+ def get_modify_keepalive_request(self, vrf_name, keepalive_interval):
+ request = None
+ method = PATCH
+ payload = {}
+
+ if keepalive_interval is not None:
+ payload['keepalive-interval'] = str(keepalive_interval)
+
+ if payload:
+ url = '%s=%s/%s/global/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, self.keepalive_path)
+ request = {"path": url, "method": method, "data": payload}
+
+ return request
+
+ def get_new_bgp_request(self, vrf_name, as_val):
+ request = None
+ url = None
+ method = PATCH
+ payload = {}
+
+ cfg = {}
+ if as_val:
+ as_cfg = {'config': {'as': float(as_val)}}
+ global_cfg = {'global': as_cfg}
+ cfg = {'bgp': global_cfg}
+ cfg['name'] = "bgp"
+ cfg['identifier'] = "openconfig-policy-types:BGP"
+
+ if cfg:
+ payload['openconfig-network-instance:protocol'] = [cfg]
+ url = '%s=%s/protocols/protocol/' % (self.network_instance_path, vrf_name)
+ request = {"path": url, "method": method, "data": payload}
+
+ return request
+
+ def get_modify_global_config_request(self, vrf_name, router_id, as_val):
+ request = None
+ method = PATCH
+ payload = {}
+
+ cfg = {}
+ if router_id:
+ cfg['router-id'] = router_id
+ if as_val:
+ cfg['as'] = float(as_val)
+
+ if cfg:
+ payload['openconfig-network-instance:config'] = cfg
+ url = '%s=%s/%s/global/config' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ request = {"path": url, "method": method, "data": payload}
+
+ return request
+
+ def get_modify_bgp_requests(self, commands, have):
+ requests = []
+ if not commands:
+ return requests
+
+ # Create URL and payload
+ for conf in commands:
+ vrf_name = conf['vrf_name']
+ as_val = None
+ router_id = None
+ log_neighbor_changes = None
+ bestpath = None
+ max_med = None
+ holdtime = None
+ keepalive_interval = None
+
+ if 'bgp_as' in conf:
+ as_val = conf['bgp_as']
+ if 'router_id' in conf:
+ router_id = conf['router_id']
+ if 'log_neighbor_changes' in conf:
+ log_neighbor_changes = conf['log_neighbor_changes']
+ if 'bestpath' in conf:
+ bestpath = conf['bestpath']
+ if 'max_med' in conf:
+ max_med = conf['max_med']
+ if 'timers' in conf and conf['timers']:
+ if 'holdtime' in conf['timers']:
+ holdtime = conf['timers']['holdtime']
+ if 'keepalive_interval' in conf['timers']:
+ keepalive_interval = conf['timers']['keepalive_interval']
+
+ if not any(cfg for cfg in have if cfg['vrf_name'] == vrf_name and (cfg['bgp_as'] == as_val)):
+ new_bgp_req = self.get_new_bgp_request(vrf_name, as_val)
+ if new_bgp_req:
+ requests.append(new_bgp_req)
+
+ global_req = self.get_modify_global_config_request(vrf_name, router_id, as_val)
+ if global_req:
+ requests.append(global_req)
+
+ log_neighbor_changes_req = self.get_modify_log_change_request(vrf_name, log_neighbor_changes)
+ if log_neighbor_changes_req:
+ requests.append(log_neighbor_changes_req)
+
+ if holdtime:
+ holdtime_req = self.get_modify_holdtime_request(vrf_name, holdtime)
+ if holdtime_req:
+ requests.append(holdtime_req)
+
+ if keepalive_interval:
+ keepalive_req = self.get_modify_keepalive_request(vrf_name, keepalive_interval)
+ if keepalive_req:
+ requests.append(keepalive_req)
+
+ bestpath_reqs = self.get_modify_bestpath_requests(vrf_name, bestpath)
+ if bestpath_reqs:
+ requests.extend(bestpath_reqs)
+ if max_med:
+ max_med_reqs = self.get_modify_max_med_requests(vrf_name, max_med)
+ if max_med_reqs:
+ requests.extend(max_med_reqs)
+
+ return 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
new file mode 100644
index 000000000..2a5c4cfca
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_af/bgp_af.py
@@ -0,0 +1,848 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_bgp_af 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
+
+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 (
+ 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 (
+ to_request,
+ 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_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import (
+ validate_bgps,
+)
+from ansible.module_utils.connection import ConnectionError
+
+PATCH = 'patch'
+DELETE = 'delete'
+TEST_KEYS = [
+ {'config': {'vrf_name': '', 'bgp_as': ''}},
+ {'afis': {'afi': '', 'safi': ''}},
+ {'redistribute': {'protocol': ''}},
+ {'route_advertise_list': {'advertise_afi': ''}}
+]
+
+
+class Bgp_af(ConfigBase):
+ """
+ The sonic_bgp_af class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'bgp_af',
+ ]
+
+ network_instance_path = '/data/openconfig-network-instance:network-instances/network-instance'
+ 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'
+ afi_safi_path = 'global/afi-safis/afi-safi'
+ table_connection_path = 'table-connections/table-connection'
+
+ def __init__(self, module):
+ super(Bgp_af, self).__init__(module)
+
+ def get_bgp_af_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)
+ bgp_af_facts = facts['ansible_network_resources'].get('bgp_af')
+ if not bgp_af_facts:
+ bgp_af_facts = []
+ return bgp_af_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+ existing_bgp_af_facts = self.get_bgp_af_facts()
+ commands, requests = self.set_config(existing_bgp_af_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_bgp_af_facts = self.get_bgp_af_facts()
+
+ result['before'] = existing_bgp_af_facts
+ if result['changed']:
+ result['after'] = changed_bgp_af_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_bgp_af_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_bgp_af_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, 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, diff)
+ 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 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
+ validate_bgps(self._module, commands, have)
+ requests = self.get_modify_bgp_af_requests(commands, have)
+ 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
+
+ :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
+ """
+ # if want is none, then delete all the bgp_afs
+ is_delete_all = False
+ if not want:
+ commands = have
+ is_delete_all = True
+ else:
+ 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")
+ else:
+ commands = []
+ return commands, requests
+
+ def get_modify_address_family_request(self, vrf_name, conf_afi, conf_safi):
+ afi_safi = ("%s_%s" % (conf_afi, conf_safi)).upper()
+ 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_safis_load = {'afi-safis': {'afi-safi': [afi_safi_load]}}
+ pay_load = {'openconfig-network-instance:global': afi_safis_load}
+
+ return ({"path": url, "method": PATCH, "data": pay_load})
+
+ def get_modify_advertise_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)
+ conf_adv_pip_peer_ip = conf_addr_fam.get('advertise_pip_peer_ip', None)
+ 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)
+ afi_safi = ("%s_%s" % (conf_afi, conf_safi)).upper()
+ evpn_cfg = {}
+
+ if conf_adv_pip:
+ 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:
+ evpn_cfg['advertise-svi-ip'] = conf_adv_svi_ip
+
+ if conf_adv_all_vni:
+ evpn_cfg['advertise-all-vni'] = conf_adv_all_vni
+
+ if conf_adv_default_gw:
+ evpn_cfg['advertise-default-gw'] = conf_adv_default_gw
+
+ if evpn_cfg:
+ 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:config': evpn_cfg}
+ 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 = []
+ afi_safi = ('%s_%s' % (conf_afi, conf_safi)).upper()
+ route_advertise_list = conf_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)
+ 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)
+ cfg = None
+ if route_map:
+ route_map_list = [route_map]
+ cfg = {'advertise-afi-safi': advertise_afi_safi, 'route-map': route_map_list}
+ 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}}
+ request = {"path": url, "method": PATCH, "data": pay_load}
+ return request
+
+ def get_modify_redistribute_requests(self, vrf_name, conf_afi, conf_safi, conf_redis_arr):
+ requests = []
+ url = "%s=%s/table-connections" % (self.network_instance_path, vrf_name)
+ cfgs = []
+ for conf_redis in conf_redis_arr:
+ conf_metric = conf_redis.get('metric', None)
+ if conf_metric is not None:
+ conf_metric = float(conf_redis['metric'])
+
+ afi_cfg = "openconfig-types:%s" % (conf_afi.upper())
+ cfg_data = {'address-family': afi_cfg}
+ cfg_data['dst-protocol'] = "openconfig-policy-types:BGP"
+ conf_protocol = conf_redis['protocol'].upper()
+ if conf_protocol == 'CONNECTED':
+ conf_protocol = "DIRECTLY_CONNECTED"
+ cfg_data['src-protocol'] = "openconfig-policy-types:%s" % (conf_protocol)
+ cfg_data['config'] = {'address-family': afi_cfg}
+ if conf_metric is not None:
+ cfg_data['config']['metric'] = conf_metric
+
+ conf_route_map = conf_redis.get('route_map', None)
+ if conf_route_map:
+ cfg_data['config']['import-policy'] = [conf_route_map]
+
+ cfgs.append(cfg_data)
+
+ if cfgs:
+ pay_load = {'openconfig-network-instance:table-connections': {'table-connection': cfgs}}
+ requests.append({"path": url, "method": PATCH, "data": pay_load})
+ return requests
+
+ def get_modify_max_path_request(self, vrf_name, conf_afi, conf_safi, conf_max_path):
+ request = None
+ 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/use-multiple-paths' % (self.afi_safi_path, afi_safi)
+ conf_ebgp = conf_max_path.get('ebgp', None)
+ conf_ibgp = conf_max_path.get('ibgp', None)
+ max_path_load = {}
+ if conf_ebgp:
+ max_path_load['ebgp'] = {'config': {'maximum-paths': conf_ebgp}}
+ if conf_ibgp:
+ max_path_load['ibgp'] = {'config': {'maximum-paths': conf_ibgp}}
+
+ pay_load = {}
+ if max_path_load:
+ pay_load['openconfig-network-instance:use-multiple-paths'] = max_path_load
+
+ request = {"path": url, "method": PATCH, "data": pay_load}
+ return request
+
+ def get_modify_network_request(self, vrf_name, conf_afi, conf_safi, conf_network):
+ request = None
+ 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/network-config' % (self.afi_safi_path, afi_safi)
+ network_payload = []
+ for each in conf_network:
+ payload = {}
+ payload = {'config': {'prefix': each}, 'prefix': each}
+ network_payload.append(payload)
+ if network_payload:
+ new_payload = {'network-config': {'network': network_payload}}
+
+ request = {"path": url, "method": PATCH, "data": new_payload}
+ return request
+
+ def get_modify_dampening_request(self, vrf_name, conf_afi, conf_safi, conf_dampening):
+ request = None
+ 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/route-flap-damping' % (self.afi_safi_path, afi_safi)
+ damp_payload = {'route-flap-damping': {'config': {'enabled': conf_dampening}}}
+ if damp_payload:
+ request = {"path": url, "method": PATCH, "data": damp_payload}
+ return request
+
+ def get_modify_single_af_request(self, vrf_name, conf_afi, conf_safi, conf_addr_fam):
+ requests = []
+
+ requests.append(self.get_modify_address_family_request(vrf_name, conf_afi, conf_safi))
+ if conf_afi == 'ipv4' and conf_safi == 'unicast':
+ conf_dampening = conf_addr_fam.get('dampening', None)
+ if conf_dampening:
+ request = self.get_modify_dampening_request(vrf_name, conf_afi, conf_safi, conf_dampening)
+ if request:
+ requests.append(request)
+ if conf_afi in ['ipv4', 'ipv6'] and conf_safi == 'unicast':
+ conf_redis_arr = conf_addr_fam.get('redistribute', [])
+ if conf_redis_arr:
+ requests.extend(self.get_modify_redistribute_requests(vrf_name, conf_afi, conf_safi, conf_redis_arr))
+ conf_max_path = conf_addr_fam.get('max_path', None)
+ if conf_max_path:
+ request = self.get_modify_max_path_request(vrf_name, conf_afi, conf_safi, conf_max_path)
+ if request:
+ requests.append(request)
+ conf_network = conf_addr_fam.get('network', [])
+ if conf_network:
+ request = self.get_modify_network_request(vrf_name, conf_afi, conf_safi, conf_network)
+ 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)
+ return requests
+
+ def get_modify_all_af_requests(self, conf_addr_fams, vrf_name):
+ requests = []
+ for conf_addr_fam in conf_addr_fams:
+ conf_afi = conf_addr_fam.get('afi', None)
+ conf_safi = conf_addr_fam.get('safi', None)
+ if conf_afi and conf_safi:
+ requests.extend(self.get_modify_single_af_request(vrf_name, conf_afi, conf_safi, conf_addr_fam))
+ return requests
+
+ def get_modify_requests(self, conf, match, vrf_name):
+ requests = []
+ payload = {}
+ conf_addr_fams = conf.get('address_family', None)
+ if conf_addr_fams:
+ conf_addr_fams = conf_addr_fams.get('afis', [])
+
+ mat_addr_fams = []
+ if match:
+ mat_addr_fams = match.get('address_family', None)
+ if mat_addr_fams:
+ mat_addr_fams = mat_addr_fams.get('afis', [])
+
+ if conf_addr_fams and not mat_addr_fams:
+ requests.extend(self.get_modify_all_af_requests(conf_addr_fams, vrf_name))
+ else:
+ for conf_addr_fam in conf_addr_fams:
+ conf_afi = conf_addr_fam.get('afi', None)
+ conf_safi = conf_addr_fam.get('safi', None)
+
+ if conf_afi is None or conf_safi is None:
+ continue
+
+ mat_addr_fam = next((e_addr_fam for e_addr_fam in mat_addr_fams if (e_addr_fam['afi'] == conf_afi and e_addr_fam['safi'] == conf_safi)), None)
+
+ if mat_addr_fam is None:
+ requests.extend(self.get_modify_single_af_request(vrf_name, conf_afi, conf_safi, conf_addr_fam))
+ continue
+
+ if conf_afi == 'ipv4' and conf_safi == 'unicast':
+ conf_dampening = conf_addr_fam.get('dampening', None)
+ if conf_dampening:
+ 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)
+ 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 rt_adv_req:
+ requests.append(rt_adv_req)
+
+ elif conf_afi in ["ipv4", "ipv6"] and conf_safi == "unicast":
+ conf_redis_arr = conf_addr_fam.get('redistribute', [])
+ conf_max_path = conf_addr_fam.get('max_path', None)
+ conf_network = conf_addr_fam.get('network', [])
+ if not conf_redis_arr and not conf_max_path and not conf_network:
+ continue
+
+ url = "%s=%s/table-connections" % (self.network_instance_path, vrf_name)
+ pay_loads = []
+ modify_redis_arr = []
+ for conf_redis in conf_redis_arr:
+ conf_metric = conf_redis.get('metric', None)
+ if conf_metric is not None:
+ conf_metric = float(conf_redis['metric'])
+
+ conf_route_map = conf_redis.get('route_map', None)
+
+ 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:
+ 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))
+
+ modify_redis = {}
+ if conf_metric is not None:
+ modify_redis['metric'] = conf_metric
+ if conf_route_map:
+ modify_redis['route_map'] = conf_route_map
+
+ if modify_redis:
+ modify_redis['protocol'] = conf_redis['protocol']
+ modify_redis_arr.append(modify_redis)
+
+ if modify_redis_arr:
+ requests.extend(self.get_modify_redistribute_requests(vrf_name, conf_afi, conf_safi, modify_redis_arr))
+ if conf_max_path:
+ max_path_req = self.get_modify_max_path_request(vrf_name, conf_afi, conf_safi, conf_max_path)
+ if max_path_req:
+ requests.append(max_path_req)
+
+ if conf_network:
+ network_req = self.get_modify_network_request(vrf_name, conf_afi, conf_safi, conf_network)
+ if network_req:
+ requests.append(network_req)
+
+ return requests
+
+ def get_modify_bgp_af_requests(self, commands, have):
+ requests = []
+ if not commands:
+ return requests
+
+ # Create URL and payload
+ for conf in commands:
+ vrf_name = conf['vrf_name']
+ as_val = conf['bgp_as']
+
+ match = next((cfg for cfg in have if (cfg['vrf_name'] == vrf_name and (cfg['bgp_as'] == as_val))), None)
+ modify_reqs = self.get_modify_requests(conf, match, vrf_name)
+ if modify_reqs:
+ requests.extend(modify_reqs)
+
+ return requests
+
+ def get_delete_advertise_attribute_request(self, vrf_name, conf_afi, conf_safi, 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/%s' % (self.afi_safi_path, afi_safi, self.l2vpn_evpn_config_path, attr)
+
+ return ({"path": url, "method": DELETE})
+
+ def get_delete_route_advertise_request(self, vrf_name, conf_afi, conf_safi):
+ 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' % (self.afi_safi_path, afi_safi, self.l2vpn_evpn_route_advertise_path)
+
+ return ({'path': url, 'method': DELETE})
+
+ def get_delete_route_advertise_list_request(self, vrf_name, conf_afi, conf_safi, advertise_afi):
+ afi_safi = ('%s_%s' % (conf_afi, conf_safi)).upper()
+ 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/route-advertise-list=%s' % (self.afi_safi_path, afi_safi, self.l2vpn_evpn_route_advertise_path, advertise_afi_safi)
+
+ return ({'path': url, 'method': DELETE})
+
+ def get_delete_route_advertise_route_map_request(self, vrf_name, conf_afi, conf_safi, advertise_afi, route_map):
+ afi_safi = ('%s_%s' % (conf_afi, conf_safi)).upper()
+ 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/route-advertise-list=%s' % (self.afi_safi_path, afi_safi, self.l2vpn_evpn_route_advertise_path, advertise_afi_safi)
+ url += '/config/route-map=%s' % route_map
+
+ return ({'path': url, 'method': DELETE})
+
+ def get_delete_route_advertise_requests(self, commands, have, is_delete_all):
+ 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))
+
+ return requests
+
+ def get_delete_dampening_request(self, vrf_name, conf_afi, conf_safi):
+ 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/route-flap-damping/config/enabled' % (self.afi_safi_path, afi_safi)
+
+ return ({"path": url, "method": DELETE})
+
+ 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}
+
+ return request
+
+ def get_delete_single_bgp_af_request(self, conf, is_delete_all, match=None):
+ requests = []
+ vrf_name = conf['vrf_name']
+
+ conf_addr_fams = conf.get('address_family', None)
+ if conf_addr_fams is None:
+ return requests
+
+ conf_addr_fams = conf_addr_fams.get('afis', [])
+
+ if match and not conf_addr_fams:
+ conf_addr_fams = match.get('address_family', None)
+ if conf_addr_fams:
+ conf_addr_fams = conf_addr_fams.get('afis', [])
+ conf_addr_fams = [{'afi': af['afi'], 'safi': af['safi']} for af in conf_addr_fams]
+
+ if not conf_addr_fams:
+ return requests
+
+ for conf_addr_fam in conf_addr_fams:
+ conf_afi = conf_addr_fam.get('afi', None)
+ conf_safi = conf_addr_fam.get('safi', None)
+ if not conf_afi or not conf_safi:
+ continue
+ conf_redis_arr = conf_addr_fam.get('redistribute', [])
+ conf_adv_pip = conf_addr_fam.get('advertise_pip', None)
+ conf_adv_pip_ip = conf_addr_fam.get('advertise_pip_ip', None)
+ conf_adv_pip_peer_ip = conf_addr_fam.get('advertise_pip_peer_ip', None)
+ 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_max_path = conf_addr_fam.get('max_path', None)
+ conf_dampening = conf_addr_fam.get('dampening', None)
+ conf_network = conf_addr_fam.get('network', [])
+ 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:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-svi-ip'))
+ if conf_adv_all_vni:
+ 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:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-default-gw'))
+ 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))
+ 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)
+ elif match:
+ match_addr_fams = match.get('address_family', None)
+ if match_addr_fams:
+ match_addr_fams = match_addr_fams.get('afis', [])
+ if not match_addr_fams:
+ continue
+ for match_addr_fam in match_addr_fams:
+ mat_afi = match_addr_fam.get('afi', None)
+ mat_safi = match_addr_fam.get('safi', None)
+ if mat_afi and mat_safi and mat_afi == conf_afi and mat_safi == conf_safi:
+ mat_advt_pip = match_addr_fam.get('advertise_pip', None)
+ mat_advt_pip_ip = match_addr_fam.get('advertise_pip_ip', None)
+ mat_advt_pip_peer_ip = match_addr_fam.get('advertise_pip_peer_ip', None)
+ mat_advt_svi_ip = match_addr_fam.get('advertise_svi_ip', None)
+ mat_advt_all_vni = match_addr_fam.get('advertise_all_vni', None)
+ mat_redis_arr = match_addr_fam.get('redistribute', [])
+ mat_advt_defaut_gw = match_addr_fam.get('advertise_default_gw', None)
+ 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'))
+ 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:
+ 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:
+ requests.append(self.get_delete_dampening_request(vrf_name, conf_afi, conf_safi))
+ if mat_advt_defaut_gw:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-default-gw'))
+ 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))
+ 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:
+ 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:
+ 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:
+ 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:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-all-vni'))
+ if conf_dampening and 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:
+ requests.append(self.get_delete_advertise_attribute_request(vrf_name, conf_afi, conf_safi, 'advertise-default-gw'))
+ 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))
+ break
+
+ return requests
+
+ def get_delete_network_request(self, vrf_name, conf_afi, conf_safi, conf_network, is_delete_all, mat_network):
+ requests = []
+ 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/network-config/network=' % (self.afi_safi_path, afi_safi)
+ mat_list = []
+ for conf in conf_network:
+ if mat_network:
+ mat_prefix = next((pre for pre in mat_network if pre == conf), None)
+ if mat_prefix:
+ mat_list.append(mat_prefix)
+ if not is_delete_all and mat_list:
+ for each in mat_list:
+ tmp = each.replace('/', '%2f')
+ requests.append({'path': url + tmp, 'method': DELETE})
+ elif is_delete_all:
+ for each in conf_network:
+ tmp = each.replace('/', '%2f')
+ requests.append({'path': url + tmp, 'method': DELETE})
+ return requests
+
+ def get_delete_max_path_requests(self, vrf_name, conf_afi, conf_safi, conf_max_path, is_delete_all, mat_max_path):
+ requests = []
+ 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/use-multiple-paths/' % (self.afi_safi_path, afi_safi)
+
+ conf_ebgp = conf_max_path.get('ebgp', None)
+ conf_ibgp = conf_max_path.get('ibgp', None)
+ mat_ebgp = None
+ mat_ibgp = None
+ if mat_max_path:
+ 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})
+
+ return requests
+
+ def get_delete_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':
+ 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/import-policy=%s' % (src_protocol, dst_protocol, addr_family, conf_route_map)
+ 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:
+ addr_family = "openconfig-types:%s" % (conf_afi.upper())
+ conf_protocol = conf_redis['protocol'].upper()
+
+ ext_metric_flag = False
+ ext_route_flag = False
+ mat_redis = None
+ mat_metric = None
+ mat_route_map = None
+ if not is_delete_all:
+ mat_redis = next((redis_cfg for redis_cfg in mat_redis_arr if redis_cfg['protocol'].upper() == conf_protocol), None)
+ if mat_redis:
+ mat_metric = mat_redis.get('metric', None)
+ mat_route_map = mat_redis.get('route_map', None)
+ if mat_metric:
+ ext_metric_flag = True
+ if mat_route_map:
+ ext_route_flag = True
+
+ if conf_protocol == 'CONNECTED':
+ conf_protocol = "DIRECTLY_CONNECTED"
+
+ src_protocol = "openconfig-policy-types:%s" % (conf_protocol)
+ dst_protocol = "openconfig-policy-types:BGP"
+
+ conf_route_map = conf_redis.get('route_map', None)
+ conf_metric = conf_redis.get('metric', None)
+ if conf_metric is not None:
+ conf_metric = float(conf_redis['metric'])
+
+ url = '%s=%s/%s=' % (self.network_instance_path, vrf_name, self.table_connection_path)
+
+ new_metric_flag = conf_metric is not None
+ new_route_flag = conf_route_map is not None
+ is_delete_protocol = False
+ if is_delete_all:
+ is_delete_protocol = True
+ else:
+ is_delete_protocol = (new_metric_flag == ext_metric_flag) and (new_route_flag == ext_route_flag)
+
+ if is_delete_protocol:
+ url += '%s,%s,%s' % (src_protocol, dst_protocol, addr_family)
+ requests.append({'path': url, 'method': DELETE})
+ continue
+
+ if new_metric_flag and ext_metric_flag:
+ url += '%s,%s,%s/config/metric' % (src_protocol, dst_protocol, addr_family)
+ requests.append({'path': url, 'method': DELETE})
+
+ if new_route_flag and ext_route_flag:
+ url += '%s,%s,%s/config/import-policy=%s' % (src_protocol, dst_protocol, addr_family, conf_route_map)
+ requests.append({'path': url, 'method': DELETE})
+
+ return requests
+
+ def get_delete_bgp_af_requests(self, commands, have, is_delete_all):
+ requests = []
+ for cmd in commands:
+ vrf_name = cmd['vrf_name']
+ as_val = cmd['bgp_as']
+ match_cfg = None
+ if not is_delete_all:
+ 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
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
new file mode 100644
index 000000000..dc2b023b1
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_as_paths/bgp_as_paths.py
@@ -0,0 +1,304 @@
+#
+# -*- 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_bgp_as_paths 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 (
+ 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
+)
+from ansible.module_utils.connection import ConnectionError
+
+try:
+ from urllib.parse import urlencode
+except Exception:
+ from urllib import urlencode
+
+
+class Bgp_as_paths(ConfigBase):
+ """
+ The sonic_bgp_as_paths class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'bgp_as_paths',
+ ]
+
+ def __init__(self, module):
+ super(Bgp_as_paths, self).__init__(module)
+
+ def get_bgp_as_paths_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)
+ bgp_as_paths_facts = facts['ansible_network_resources'].get('bgp_as_paths')
+ if not bgp_as_paths_facts:
+ return []
+ return bgp_as_paths_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_bgp_as_paths_facts = self.get_bgp_as_paths_facts()
+ commands, requests = self.set_config(existing_bgp_as_paths_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_bgp_as_paths_facts = self.get_bgp_as_paths_facts()
+
+ result['before'] = existing_bgp_as_paths_facts
+ if result['changed']:
+ result['after'] = changed_bgp_as_paths_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_bgp_as_paths_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_bgp_as_paths_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']
+ 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)
+ 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, diff)
+ return commands, requests
+
+ @staticmethod
+ def _state_replaced(**kwargs):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ return commands
+
+ @staticmethod
+ def _state_overridden(**kwargs):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ return commands
+
+ 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_modify_as_path_list_requests(commands, have)
+ 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
+ """
+ # To Delete a single member
+ # data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/as-path-sets/as-path-set=xyz/config/as-path-set-member=11
+ # This will delete the as path and its all members
+ # data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/as-path-sets/as-path-set=xyz
+ # This will delete ALL as path completely
+ # data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/as-path-sets
+
+ is_delete_all = False
+ # if want is none, then delete ALL
+ if not want:
+ commands = have
+ is_delete_all = True
+ else:
+ commands = want
+
+ requests = self.get_delete_as_path_requests(commands, have, is_delete_all)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def get_new_add_request(self, conf):
+ request = None
+ members = conf.get('members', None)
+ permit = conf.get('permit', None)
+ permit_str = ""
+ if permit:
+ permit_str = "PERMIT"
+ else:
+ permit_str = "DENY"
+ if members:
+ url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/as-path-sets"
+ method = "PATCH"
+ cfg = {'as-path-set-name': conf['name'], 'as-path-set-member': members, 'openconfig-bgp-policy-ext:action': permit_str}
+ as_path_set = {'as-path-set-name': conf['name'], 'config': cfg}
+ payload = {'openconfig-bgp-policy:as-path-sets': {'as-path-set': [as_path_set]}}
+ request = {"path": url, "method": method, "data": payload}
+ return request
+
+ def get_delete_all_as_path_requests(self, commands):
+ url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/as-path-sets"
+ method = "DELETE"
+ requests = []
+ if commands:
+ request = {"path": url, "method": method}
+ requests.append(request)
+ return requests
+
+ def get_delete_single_as_path_member_requests(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"
+ members_params = {'as-path-set-member': ','.join(members)}
+ members_str = urlencode(members_params)
+ 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):
+ 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
+
+ def get_delete_as_path_requests(self, commands, have, is_delete_all):
+ requests = []
+ if is_delete_all:
+ requests = self.get_delete_all_as_path_requests(commands)
+ else:
+ for cmd in commands:
+ 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))
+
+ return requests
+
+ def get_modify_as_path_list_requests(self, commands, have):
+ requests = []
+ if not commands:
+ return requests
+
+ for conf in commands:
+ new_req = self.get_new_add_request(conf)
+ if new_req:
+ requests.append(new_req)
+ return requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_communities/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_communities/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_communities/__init__.py
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
new file mode 100644
index 000000000..670fb26d3
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_communities/bgp_communities.py
@@ -0,0 +1,368 @@
+#
+# -*- 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_bgp_communities 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 (
+ 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
+)
+from ansible.module_utils.connection import ConnectionError
+import json
+from ansible.module_utils._text import to_native
+import traceback
+try:
+ import jinja2
+ HAS_LIB = True
+except Exception as e:
+ HAS_LIB = False
+ ERR_MSG = to_native(e)
+ LIB_IMP_ERR = traceback.format_exc()
+
+try:
+ from urllib.parse import urlencode
+except Exception:
+ from urllib import urlencode
+
+
+class Bgp_communities(ConfigBase):
+ """
+ The sonic_bgp_communities class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'bgp_communities',
+ ]
+
+ def __init__(self, module):
+ super(Bgp_communities, self).__init__(module)
+
+ def get_bgp_communities_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)
+ bgp_communities_facts = facts['ansible_network_resources'].get('bgp_communities')
+ if not bgp_communities_facts:
+ return []
+ return bgp_communities_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_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:
+ 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_bgp_communities_facts = self.get_bgp_communities_facts()
+
+ result['before'] = existing_bgp_communities_facts
+ if result['changed']:
+ result['after'] = changed_bgp_communities_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_bgp_communities_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_bgp_communities_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)
+ # with open('/root/ansible_log.log', 'a+') as fp:
+ # fp.write('comm: want: ' + str(want) + '\n')
+ # 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)
+ 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, diff)
+ return commands, requests
+
+ @staticmethod
+ def _state_replaced(**kwargs):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ return commands
+
+ @staticmethod
+ def _state_overridden(**kwargs):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ return commands
+
+ 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_modify_bgp_community_requests(commands, have)
+ 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
+ """
+ # Delete a community
+ # https://100.94.81.19/restconf/data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/community-sets/community-set=extest
+ # Delete all members but not community
+ # https://100.94.81.19/restconf/data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/community-sets/community-set=extest/config/community-member
+ # Dete a memeber from the expanded community
+ # https://100.94.81.19/restconf/data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/community-sets/community-set=extest/config/community-member=REGEX%3A100.100
+ # Delete ALL Bgp_communities and its members
+ # https://100.94.81.19/restconf/data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/community-sets
+ is_delete_all = False
+ # if want is none, then delete ALL
+ if not want:
+ commands = have
+ is_delete_all = True
+ else:
+ commands = want
+
+ requests = self.get_delete_bgp_communities(commands, have, is_delete_all)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def get_delete_single_bgp_community_member_requests(self, name, type, 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_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"
+ request = {"path": url.format(name), "method": method}
+ return request
+
+ def get_delete_all_bgp_communities(self, commands):
+ url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/community-sets"
+ method = "DELETE"
+ requests = []
+ if commands:
+ request = {"path": url, "method": method}
+ requests.append(request)
+ 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:
+ requests.append(self.get_delete_single_bgp_community_requests(name))
+
+ # 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))
+
+ 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['permit']:
+ input_data['permit'] = "PERMIT"
+ else:
+ input_data['permit'] = "DENY"
+ payload_template = """
+ {
+ "openconfig-bgp-policy:community-sets": {
+ "community-set": [
+ {
+ "community-set-name": "{{name}}",
+ "config": {
+ "community-set-name": "{{name}}",
+ "community-member": [
+ {% for member in members_list %}"{{regex}}{{member}}"{%- if not loop.last -%},{% endif %}{%endfor%}
+ ],
+ "openconfig-bgp-policy-ext:action": "{{permit}}",
+ "match-set-options": "{{match}}"
+ }
+ }
+ ]
+ }
+ }"""
+ env = jinja2.Environment(autoescape=False)
+ t = env.from_string(payload_template)
+ 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):
+ 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']
+ new_req = self.get_new_add_request(conf)
+ if new_req:
+ requests.append(new_req)
+ return requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_ext_communities/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_ext_communities/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_ext_communities/__init__.py
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
new file mode 100644
index 000000000..751f88e48
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_ext_communities/bgp_ext_communities.py
@@ -0,0 +1,371 @@
+#
+# -*- 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_bgp_ext_communities 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 (
+ 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
+)
+import json
+from ansible.module_utils._text import to_native
+from ansible.module_utils.connection import ConnectionError
+import traceback
+try:
+ import jinja2
+ HAS_LIB = True
+except Exception as e:
+ HAS_LIB = False
+ ERR_MSG = to_native(e)
+ LIB_IMP_ERR = traceback.format_exc()
+
+try:
+ from urllib.parse import urlencode
+except Exception:
+ from urllib import urlencode
+
+
+class Bgp_ext_communities(ConfigBase):
+ """
+ The sonic_bgp_ext_communities class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'bgp_ext_communities',
+ ]
+
+ def __init__(self, module):
+ super(Bgp_ext_communities, self).__init__(module)
+
+ def get_bgp_ext_communities_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)
+ bgp_ext_communities_facts = facts['ansible_network_resources'].get('bgp_ext_communities')
+ if not bgp_ext_communities_facts:
+ return []
+ return bgp_ext_communities_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_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:
+ 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_bgp_ext_communities_facts = self.get_bgp_ext_communities_facts()
+
+ result['before'] = existing_bgp_ext_communities_facts
+ if result['changed']:
+ result['after'] = changed_bgp_ext_communities_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_bgp_ext_communities_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_bgp_ext_communities_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']
+ new_want = self.validate_type(want)
+ diff = get_diff(new_want, have)
+ 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, diff)
+ return commands, requests
+
+ @staticmethod
+ def _state_replaced(**kwargs):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ return commands
+
+ @staticmethod
+ def _state_overridden(**kwargs):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ return commands
+
+ 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_modify_bgp_ext_community_requests(commands, have)
+ 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
+ """
+ is_delete_all = False
+ # if want is none, then delete ALL
+ if not want:
+ commands = have
+ is_delete_all = True
+ else:
+ commands = want
+
+ requests = self.get_delete_bgp_ext_communities(commands, have, is_delete_all)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def get_delete_single_bgp_ext_community_member_requests(self, name, type, members):
+ requests = []
+ for member in members:
+ url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:"
+ url = url + "bgp-defined-sets/ext-community-sets/ext-community-set={name}/config/{members_param}"
+ method = "DELETE"
+ members_params = {'ext-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_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"
+ request = {"path": url.format(name), "method": method}
+ return request
+
+ def get_delete_all_bgp_ext_communities(self, commands):
+ url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/ext-community-sets"
+ method = "DELETE"
+ requests = []
+ if commands:
+ request = {"path": url, "method": method}
+ requests.append(request)
+ return requests
+
+ def get_delete_bgp_ext_communities(self, commands, have, is_delete_all):
+ requests = []
+ if is_delete_all:
+ requests = self.get_delete_all_bgp_ext_communities(commands)
+ 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']:
+ 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:
+ requests.append(self.get_delete_single_bgp_ext_community_requests(name))
+
+ 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)
+ 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 conf['permit']:
+ input_data['permit'] = "PERMIT"
+ else:
+ input_data['permit'] = "DENY"
+ payload_template = """
+ {
+ "openconfig-bgp-policy:ext-community-sets": {
+ "ext-community-set": [
+ {
+ "ext-community-set-name": "{{name}}",
+ "config": {
+ "ext-community-set-name": "{{name}}",
+ "ext-community-member": [
+ {% for member in members_list %}"{{member}}"{%- if not loop.last -%},{% endif %}{%endfor%}
+ ],
+ "openconfig-bgp-policy-ext:action": "{{permit}}",
+ "match-set-options": "{{match}}"
+ }
+ }
+ ]
+ }
+ }"""
+ env = jinja2.Environment(autoescape=False)
+ t = env.from_string(payload_template)
+ intended_payload = t.render(input_data)
+ ret_payload = json.loads(intended_payload)
+ request = {"path": url, "method": method, "data": ret_payload}
+ return request
+
+ def get_modify_bgp_ext_community_requests(self, commands, have):
+ 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
+ new_req = self.get_new_add_request(conf)
+ if new_req:
+ requests.append(new_req)
+ return requests
+
+ def validate_type(self, want):
+ new_want = []
+ if want:
+ for conf in want:
+ cfg = conf.copy()
+ cfg['type'] = 'standard'
+ members = conf.get('members', None)
+ if members and members.get('regex', None):
+ cfg['type'] = 'expanded'
+
+ new_want.append(cfg)
+ return new_want
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
new file mode 100644
index 000000000..31bbec78d
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors/bgp_neighbors.py
@@ -0,0 +1,1100 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_bgp_neighbors 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,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import (
+ validate_bgps,
+ normalize_neighbors_interface_name,
+ get_ip_afi_cfg_payload,
+ get_prefix_limit_payload
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import to_request
+from ansible.module_utils.connection import ConnectionError
+
+PATCH = 'patch'
+DELETE = 'delete'
+
+TEST_KEYS = [
+ {'config': {'vrf_name': '', 'bgp_as': ''}},
+ {'neighbors': {'neighbor': ''}},
+ {'peer_group': {'name': ''}},
+ {'afis': {'afi': '', 'safi': ''}},
+]
+
+
+class Bgp_neighbors(ConfigBase):
+ """
+ The sonic_bgp_neighbors class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'bgp_neighbors',
+ ]
+
+ network_instance_path = '/data/openconfig-network-instance:network-instances/network-instance'
+ protocol_bgp_path = 'protocols/protocol=BGP,bgp/bgp'
+ neighbor_path = 'neighbors/neighbor'
+
+ def __init__(self, module):
+ super(Bgp_neighbors, self).__init__(module)
+
+ def get_bgp_neighbors_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)
+ bgp_facts = facts['ansible_network_resources'].get('bgp_neighbors')
+ if not bgp_facts:
+ bgp_facts = []
+ return bgp_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+ existing_bgp_facts = self.get_bgp_neighbors_facts()
+ commands, requests = self.set_config(existing_bgp_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_bgp_facts = self.get_bgp_neighbors_facts()
+
+ result['before'] = existing_bgp_facts
+ if result['changed']:
+ result['after'] = changed_bgp_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_bgp_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']
+ normalize_neighbors_interface_name(want, self._module)
+ have = existing_bgp_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 == 'deleted':
+ commands, requests = self._state_deleted(want, have, diff)
+ elif state == 'merged':
+ commands, requests = self._state_merged(want, have, diff)
+ 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
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = []
+ requests = []
+ commands = diff
+ validate_bgps(self._module, commands, have)
+ requests = self.get_modify_bgp_requests(commands, have)
+ 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
+
+ :param want: the objects from which the configuration should be removed
+ :param 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
+ if not want:
+ is_delete_all = True
+ if is_delete_all:
+ commands = have
+ new_have = have
+ else:
+ new_have = self.remove_default_entries(have)
+ 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
+ requests = self.get_delete_bgp_neighbor_requests(commands, new_have, want, is_delete_all)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ 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 = []
+ for peer_group in cmd:
+ if peer_group:
+ bgp_peer_group = {}
+ peer_group_cfg = {}
+ tmp_bfd = {}
+ tmp_ebgp = {}
+ tmp_timers = {}
+ tmp_capability = {}
+ tmp_remote = {}
+ tmp_transport = {}
+ afi = []
+ if peer_group.get('name', None) is not None:
+ peer_group_cfg.update({'peer-group-name': peer_group['name']})
+ bgp_peer_group.update({'peer-group-name': peer_group['name']})
+ if peer_group.get('bfd', None) is not None:
+ if peer_group['bfd'].get('enabled', None) is not None:
+ tmp_bfd.update({'enabled': peer_group['bfd']['enabled']})
+ if peer_group['bfd'].get('check_failure', None) is not None:
+ tmp_bfd.update({'check-control-plane-failure': peer_group['bfd']['check_failure']})
+ if peer_group['bfd'].get('profile', None) is not None:
+ tmp_bfd.update({'bfd-profile': peer_group['bfd']['profile']})
+ if peer_group.get('auth_pwd', None) is not None:
+ if (peer_group['auth_pwd'].get('pwd', None) is not None and
+ peer_group['auth_pwd'].get('encrypted', None) is not None):
+ bgp_peer_group.update({'auth-password': {'config': {'password': peer_group['auth_pwd']['pwd'],
+ 'encrypted': peer_group['auth_pwd']['encrypted']}}})
+ if peer_group.get('ebgp_multihop', None) is not None:
+ if peer_group['ebgp_multihop'].get('enabled', None) is not None:
+ tmp_ebgp.update({'enabled': peer_group['ebgp_multihop']['enabled']})
+ if peer_group['ebgp_multihop'].get('multihop_ttl', None) is not None:
+ tmp_ebgp.update({'multihop-ttl': peer_group['ebgp_multihop']['multihop_ttl']})
+ if peer_group.get('timers', None) is not None:
+ if peer_group['timers'].get('holdtime', None) is not None:
+ tmp_timers.update({'hold-time': peer_group['timers']['holdtime']})
+ if peer_group['timers'].get('keepalive', None) is not None:
+ tmp_timers.update({'keepalive-interval': peer_group['timers']['keepalive']})
+ if peer_group['timers'].get('connect_retry', None) is not None:
+ tmp_timers.update({'connect-retry': peer_group['timers']['connect_retry']})
+ if peer_group.get('capability', None) is not None:
+ if peer_group['capability'].get('dynamic', None) is not None:
+ tmp_capability.update({'capability-dynamic': peer_group['capability']['dynamic']})
+ if peer_group['capability'].get('extended_nexthop', None) is not None:
+ tmp_capability.update({'capability-extended-nexthop': peer_group['capability']['extended_nexthop']})
+ if peer_group.get('pg_description', None) is not None:
+ peer_group_cfg.update({'description': peer_group['pg_description']})
+ if peer_group.get('disable_connected_check', None) is not None:
+ peer_group_cfg.update({'disable-ebgp-connected-route-check': peer_group['disable_connected_check']})
+ if peer_group.get('dont_negotiate_capability', None) is not None:
+ peer_group_cfg.update({'dont-negotiate-capability': peer_group['dont_negotiate_capability']})
+ if peer_group.get('enforce_first_as', None) is not None:
+ peer_group_cfg.update({'enforce-first-as': peer_group['enforce_first_as']})
+ if peer_group.get('enforce_multihop', None) is not None:
+ peer_group_cfg.update({'enforce-multihop': peer_group['enforce_multihop']})
+ if peer_group.get('override_capability', None) is not None:
+ peer_group_cfg.update({'override-capability': peer_group['override_capability']})
+ if peer_group.get('shutdown_msg', None) is not None:
+ peer_group_cfg.update({'shutdown-message': peer_group['shutdown_msg']})
+ if peer_group.get('solo', None) is not None:
+ peer_group_cfg.update({'solo-peer': peer_group['solo']})
+ if peer_group.get('strict_capability_match', None) is not None:
+ peer_group_cfg.update({'strict-capability-match': peer_group['strict_capability_match']})
+ if peer_group.get('ttl_security', None) is not None:
+ peer_group_cfg.update({'ttl-security-hops': peer_group['ttl_security']})
+ if peer_group.get('local_as', None) is not None:
+ if peer_group['local_as'].get('as', None) is not None:
+ peer_group_cfg.update({'local-as': peer_group['local_as']['as']})
+ if peer_group['local_as'].get('no_prepend', None) is not None:
+ peer_group_cfg.update({'local-as-no-prepend': peer_group['local_as']['no_prepend']})
+ if peer_group['local_as'].get('replace_as', None) is not None:
+ peer_group_cfg.update({'local-as-replace-as': peer_group['local_as']['replace_as']})
+ if peer_group.get('local_address', None) is not None:
+ tmp_transport.update({'local-address': peer_group['local_address']})
+ if peer_group.get('passive', None) is not None:
+ tmp_transport.update({'passive-mode': peer_group['passive']})
+ if peer_group.get('advertisement_interval', None) is not None:
+ tmp_timers.update({'minimum-advertisement-interval': peer_group['advertisement_interval']})
+ if peer_group.get('remote_as', None) is not None:
+ have_nei = self.find_pg(have, bgp_as, vrf_name, peer_group)
+ if peer_group['remote_as'].get('peer_as', None) is not None:
+ if have_nei:
+ if have_nei.get("remote_as", None) is not None:
+ if have_nei["remote_as"].get("peer_type", None) is not None:
+ del_nei = {}
+ del_nei.update({'name': have_nei['name']})
+ del_nei.update({'remote_as': have_nei['remote_as']})
+ requests.extend(self.delete_specific_peergroup_param_request(vrf_name, del_nei))
+ tmp_remote.update({'peer-as': peer_group['remote_as']['peer_as']})
+ if peer_group['remote_as'].get('peer_type', None) is not None:
+ if have_nei:
+ if have_nei.get("remote_as", None) is not None:
+ if have_nei["remote_as"].get("peer_as", None) is not None:
+ del_nei = {}
+ del_nei.update({'name': have_nei['name']})
+ del_nei.update({'remote_as': have_nei['remote_as']})
+ requests.extend(self.delete_specific_peergroup_param_request(vrf_name, del_nei))
+ tmp_remote.update({'peer-type': peer_group['remote_as']['peer_type'].upper()})
+ if peer_group.get('address_family', None) is not None:
+ if peer_group['address_family'].get('afis', None) is not None:
+ for each in peer_group['address_family']['afis']:
+ samp = {}
+ afi_safi_cfg = {}
+ pfx_lmt_cfg = {}
+ pfx_lst_cfg = {}
+ ip_dict = {}
+ if each.get('afi', None) is not None and each.get('safi', None) is not None:
+ afi_safi = each['afi'].upper() + "_" + each['safi'].upper()
+ if afi_safi is not None:
+ afi_safi_name = 'openconfig-bgp-types:' + afi_safi
+ if afi_safi_name is not None:
+ samp.update({'afi-safi-name': afi_safi_name})
+ samp.update({'config': {'afi-safi-name': afi_safi_name}})
+ 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}}})
+ else:
+ if each.get('ip_afi', None) is not None:
+ afi_safi_cfg = get_ip_afi_cfg_payload(each['ip_afi'])
+ if afi_safi_cfg:
+ ip_dict.update({'config': afi_safi_cfg})
+ if pfx_lmt_cfg:
+ ip_dict.update({'prefix-limit': {'config': pfx_lmt_cfg}})
+ if ip_dict and afi_safi == 'IPV4_UNICAST':
+ samp.update({'ipv4-unicast': ip_dict})
+ elif ip_dict and afi_safi == 'IPV6_UNICAST':
+ samp.update({'ipv6-unicast': ip_dict})
+ if each.get('activate', None) is not None:
+ enabled = each['activate']
+ if enabled is not None:
+ samp.update({'config': {'enabled': enabled}})
+ if each.get('allowas_in', None) is not None:
+ have_pg_af = self.find_af(have, bgp_as, vrf_name, peer_group, each['afi'], each['safi'])
+ if each['allowas_in'].get('origin', None) is not None:
+ if have_pg_af:
+ if have_pg_af.get('allowas_in', None) is not None:
+ if have_pg_af['allowas_in'].get('value', None) is not None:
+ del_nei = {}
+ del_nei.update({'name': peer_group['name']})
+ afis_list = []
+ temp_cfg = {'afi': each['afi'], 'safi': each['safi']}
+ temp_cfg['allowas_in'] = {'value': have_pg_af['allowas_in']['value']}
+ afis_list.append(temp_cfg)
+ del_nei.update({'address_family': {'afis': afis_list}})
+ requests.extend(self.delete_specific_peergroup_param_request(vrf_name, del_nei))
+ origin = each['allowas_in']['origin']
+ samp.update({'allow-own-as': {'config': {'origin': origin, "enabled": bool("true")}}})
+ if each['allowas_in'].get('value', None) is not None:
+ if have_pg_af:
+ if have_pg_af.get('allowas_in', None) is not None:
+ if have_pg_af['allowas_in'].get('origin', None) is not None:
+ del_nei = {}
+ del_nei.update({'name': peer_group['name']})
+ afis_list = []
+ temp_cfg = {'afi': each['afi'], 'safi': each['safi']}
+ temp_cfg['allowas_in'] = {'origin': have_pg_af['allowas_in']['origin']}
+ afis_list.append(temp_cfg)
+ del_nei.update({'address_family': {'afis': afis_list}})
+ requests.extend(self.delete_specific_peergroup_param_request(vrf_name, del_nei))
+ as_count = each['allowas_in']['value']
+ samp.update({'allow-own-as': {'config': {'as-count': as_count, "enabled": bool("true")}}})
+ if each.get('prefix_list_in', None) is not None:
+ prefix_list_in = each['prefix_list_in']
+ if prefix_list_in is not None:
+ pfx_lst_cfg.update({'import-policy': prefix_list_in})
+ if each.get('prefix_list_out', None) is not None:
+ prefix_list_out = each['prefix_list_out']
+ if prefix_list_out is not None:
+ pfx_lst_cfg.update({'export-policy': prefix_list_out})
+ if pfx_lst_cfg:
+ samp.update({'prefix-list': {'config': pfx_lst_cfg}})
+ if samp:
+ afi.append(samp)
+ if tmp_bfd:
+ bgp_peer_group.update({'enable-bfd': {'config': tmp_bfd}})
+ if tmp_ebgp:
+ bgp_peer_group.update({'ebgp-multihop': {'config': tmp_ebgp}})
+ if tmp_timers:
+ bgp_peer_group.update({'timers': {'config': tmp_timers}})
+ if tmp_transport:
+ bgp_peer_group.update({'transport': {'config': tmp_transport}})
+ if afi and len(afi) > 0:
+ bgp_peer_group.update({'afi-safis': {'afi-safi': afi}})
+ if tmp_capability:
+ peer_group_cfg.update(tmp_capability)
+ if tmp_remote:
+ peer_group_cfg.update(tmp_remote)
+ if peer_group_cfg:
+ bgp_peer_group.update({'config': peer_group_cfg})
+ if bgp_peer_group:
+ bgp_peer_group_list.append(bgp_peer_group)
+ payload = {'openconfig-network-instance:peer-groups': {'peer-group': bgp_peer_group_list}}
+ return payload, requests
+
+ def find_pg(self, have, bgp_as, vrf_name, peergroup):
+ mat_dict = next((m_peer for m_peer in have if m_peer['bgp_as'] == bgp_as and m_peer['vrf_name'] == vrf_name), None)
+ if mat_dict and mat_dict.get("peer_group", None) is not None:
+ mat_pg = next((m for m in mat_dict['peer_group'] if m["name"] == peergroup['name']), None)
+ return mat_pg
+
+ def find_af(self, have, bgp_as, vrf_name, peergroup, afi, safi):
+ mat_pg = self.find_pg(have, bgp_as, vrf_name, peergroup)
+ if mat_pg and mat_pg['address_family'].get('afis', None) is not None:
+ mat_af = next((af for af in mat_pg['address_family']['afis'] if af['afi'] == afi and af['safi'] == safi), None)
+ return mat_af
+
+ def find_nei(self, have, bgp_as, vrf_name, neighbor):
+ mat_dict = next((m_neighbor for m_neighbor in have if m_neighbor['bgp_as'] == bgp_as and m_neighbor['vrf_name'] == vrf_name), None)
+ if mat_dict and mat_dict.get("neighbors", None) is not None:
+ mat_neighbor = next((m for m in mat_dict['neighbors'] if m["neighbor"] == neighbor['neighbor']), None)
+ return mat_neighbor
+
+ def build_bgp_neighbors_payload(self, cmd, have, bgp_as, vrf_name):
+ bgp_neighbor_list = []
+ requests = []
+ for neighbor in cmd:
+ if neighbor:
+ bgp_neighbor = {}
+ neighbor_cfg = {}
+ tmp_bfd = {}
+ tmp_ebgp = {}
+ tmp_timers = {}
+ tmp_capability = {}
+ tmp_remote = {}
+ tmp_transport = {}
+ if neighbor.get('bfd', None) is not None:
+ if neighbor['bfd'].get('enabled', None) is not None:
+ tmp_bfd.update({'enabled': neighbor['bfd']['enabled']})
+ if neighbor['bfd'].get('check_failure', None) is not None:
+ tmp_bfd.update({'check-control-plane-failure': neighbor['bfd']['check_failure']})
+ if neighbor['bfd'].get('profile', None) is not None:
+ tmp_bfd.update({'bfd-profile': neighbor['bfd']['profile']})
+ if neighbor.get('auth_pwd', None) is not None:
+ if (neighbor['auth_pwd'].get('pwd', None) is not None and
+ neighbor['auth_pwd'].get('encrypted', None) is not None):
+ bgp_neighbor.update({'auth-password': {'config': {'password': neighbor['auth_pwd']['pwd'],
+ 'encrypted': neighbor['auth_pwd']['encrypted']}}})
+ if neighbor.get('ebgp_multihop', None) is not None:
+ if neighbor['ebgp_multihop'].get('enabled', None) is not None:
+ tmp_ebgp.update({'enabled': neighbor['ebgp_multihop']['enabled']})
+ if neighbor['ebgp_multihop'].get('multihop_ttl', None) is not None:
+ tmp_ebgp.update({'multihop-ttl': neighbor['ebgp_multihop']['multihop_ttl']})
+ if neighbor.get('timers', None) is not None:
+ if neighbor['timers'].get('holdtime', None) is not None:
+ tmp_timers.update({'hold-time': neighbor['timers']['holdtime']})
+ if neighbor['timers'].get('keepalive', None) is not None:
+ tmp_timers.update({'keepalive-interval': neighbor['timers']['keepalive']})
+ if neighbor['timers'].get('connect_retry', None) is not None:
+ tmp_timers.update({'connect-retry': neighbor['timers']['connect_retry']})
+ if neighbor.get('capability', None) is not None:
+ if neighbor['capability'].get('dynamic', None) is not None:
+ tmp_capability.update({'capability-dynamic': neighbor['capability']['dynamic']})
+ if neighbor['capability'].get('extended_nexthop', None) is not None:
+ tmp_capability.update({'capability-extended-nexthop': neighbor['capability']['extended_nexthop']})
+ if neighbor.get('advertisement_interval', None) is not None:
+ tmp_timers.update({'minimum-advertisement-interval': neighbor['advertisement_interval']})
+ if neighbor.get('neighbor', None) is not None:
+ bgp_neighbor.update({'neighbor-address': neighbor['neighbor']})
+ neighbor_cfg.update({'neighbor-address': neighbor['neighbor']})
+ if neighbor.get('peer_group', None) is not None:
+ neighbor_cfg.update({'peer-group': neighbor['peer_group']})
+ if neighbor.get('nbr_description', None) is not None:
+ neighbor_cfg.update({'description': neighbor['nbr_description']})
+ if neighbor.get('disable_connected_check', None) is not None:
+ neighbor_cfg.update({'disable-ebgp-connected-route-check': neighbor['disable_connected_check']})
+ if neighbor.get('dont_negotiate_capability', None) is not None:
+ neighbor_cfg.update({'dont-negotiate-capability': neighbor['dont_negotiate_capability']})
+ if neighbor.get('enforce_first_as', None) is not None:
+ neighbor_cfg.update({'enforce-first-as': neighbor['enforce_first_as']})
+ if neighbor.get('enforce_multihop', None) is not None:
+ neighbor_cfg.update({'enforce-multihop': neighbor['enforce_multihop']})
+ if neighbor.get('override_capability', None) is not None:
+ neighbor_cfg.update({'override-capability': neighbor['override_capability']})
+ if neighbor.get('port', None) is not None:
+ neighbor_cfg.update({'peer-port': neighbor['port']})
+ if neighbor.get('shutdown_msg', None) is not None:
+ neighbor_cfg.update({'shutdown-message': neighbor['shutdown_msg']})
+ if neighbor.get('solo', None) is not None:
+ neighbor_cfg.update({'solo-peer': neighbor['solo']})
+ if neighbor.get('strict_capability_match', None) is not None:
+ neighbor_cfg.update({'strict-capability-match': neighbor['strict_capability_match']})
+ if neighbor.get('ttl_security', None) is not None:
+ neighbor_cfg.update({'ttl-security-hops': neighbor['ttl_security']})
+ if neighbor.get('v6only', None) is not None:
+ neighbor_cfg.update({'openconfig-bgp-ext:v6only': neighbor['v6only']})
+ if neighbor.get('local_as', None) is not None:
+ if neighbor['local_as'].get('as', None) is not None:
+ neighbor_cfg.update({'local-as': neighbor['local_as']['as']})
+ if neighbor['local_as'].get('no_prepend', None) is not None:
+ neighbor_cfg.update({'local-as-no-prepend': neighbor['local_as']['no_prepend']})
+ if neighbor['local_as'].get('replace_as', None) is not None:
+ neighbor_cfg.update({'local-as-replace-as': neighbor['local_as']['replace_as']})
+ if neighbor.get('local_address', None) is not None:
+ tmp_transport.update({'local-address': neighbor['local_address']})
+ if neighbor.get('passive', None) is not None:
+ tmp_transport.update({'passive-mode': neighbor['passive']})
+ if neighbor.get('remote_as', None) is not None:
+ have_nei = self.find_nei(have, bgp_as, vrf_name, neighbor)
+ if neighbor['remote_as'].get('peer_as', None) is not None:
+ if have_nei:
+ if have_nei.get("remote_as", None) is not None:
+ if have_nei["remote_as"].get("peer_type", None) is not None:
+ del_nei = {}
+ del_nei.update({'neighbor': have_nei['neighbor']})
+ del_nei.update({'remote_as': have_nei['remote_as']})
+ requests.extend(self.delete_specific_param_request(vrf_name, del_nei))
+ tmp_remote.update({'peer-as': neighbor['remote_as']['peer_as']})
+ if neighbor['remote_as'].get('peer_type', None) is not None:
+ if have_nei:
+ if have_nei.get("remote_as", None) is not None:
+ if have_nei["remote_as"].get("peer_as", None) is not None:
+ del_nei = {}
+ del_nei.update({'neighbor': have_nei['neighbor']})
+ del_nei.update({'remote_as': have_nei['remote_as']})
+ requests.extend(self.delete_specific_param_request(vrf_name, del_nei))
+ tmp_remote.update({'peer-type': neighbor['remote_as']['peer_type'].upper()})
+ if tmp_bfd:
+ bgp_neighbor.update({'enable-bfd': {'config': tmp_bfd}})
+ if tmp_ebgp:
+ bgp_neighbor.update({'ebgp-multihop': {'config': tmp_ebgp}})
+ if tmp_timers:
+ bgp_neighbor.update({'timers': {'config': tmp_timers}})
+ if tmp_transport:
+ bgp_neighbor.update({'transport': {'config': tmp_transport}})
+ if tmp_capability:
+ neighbor_cfg.update(tmp_capability)
+ if tmp_remote:
+ neighbor_cfg.update(tmp_remote)
+ if neighbor_cfg:
+ bgp_neighbor.update({'config': neighbor_cfg})
+ if bgp_neighbor:
+ bgp_neighbor_list.append(bgp_neighbor)
+ payload = {'openconfig-network-instance:neighbors': {'neighbor': bgp_neighbor_list}}
+ return payload, requests
+
+ def get_modify_bgp_requests(self, commands, have):
+ requests = []
+ if not commands:
+ return requests
+
+ for cmd in commands:
+ edit_path = '%s=%s/%s' % (self.network_instance_path, cmd['vrf_name'], self.protocol_bgp_path)
+ if 'peer_group' in cmd and cmd['peer_group']:
+ edit_peer_groups_payload, edit_requests = self.build_bgp_peer_groups_payload(cmd['peer_group'], have, cmd['bgp_as'], cmd['vrf_name'])
+ edit_peer_groups_path = edit_path + '/peer-groups'
+ if edit_requests:
+ requests.extend(edit_requests)
+ requests.append({'path': edit_peer_groups_path, 'method': PATCH, 'data': edit_peer_groups_payload})
+ if 'neighbors' in cmd and cmd['neighbors']:
+ edit_neighbors_payload, edit_requests = self.build_bgp_neighbors_payload(cmd['neighbors'], have, cmd['bgp_as'], cmd['vrf_name'])
+ edit_neighbors_path = edit_path + '/neighbors'
+ if edit_requests:
+ requests.extend(edit_requests)
+ requests.append({'path': edit_neighbors_path, 'method': PATCH, 'data': edit_neighbors_payload})
+ return requests
+
+ def get_delete_specific_bgp_peergroup_param_request(self, vrf_name, cmd, want_match):
+ requests = []
+ want_peer_group = want_match.get('peer_group', None)
+ for each in cmd['peer_group']:
+ if each:
+ name = each.get('name', None)
+ remote_as = each.get('remote_as', None)
+ timers = each.get('timers', None)
+ advertisement_interval = each.get('advertisement_interval', None)
+ bfd = each.get('bfd', None)
+ capability = each.get('capability', 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:
+ 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']
+ 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:
+ requests.extend(self.delete_specific_peergroup_param_request(vrf_name, each))
+ return requests
+
+ def delete_specific_peergroup_param_request(self, vrf_name, cmd):
+ requests = []
+ delete_static_path = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ delete_static_path = delete_static_path + '/peer-groups/peer-group=%s' % (cmd['name'])
+ if cmd.get('remote_as', None) is not None:
+ if cmd['remote_as'].get('peer_as', None) is not None:
+ delete_path = delete_static_path + '/config/peer-as'
+ requests.append({'path': delete_path, 'method': DELETE})
+ elif cmd['remote_as'].get('peer_type', None) is not None:
+ delete_path = delete_static_path + '/config/peer-type'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('advertisement_interval', None) is not None:
+ delete_path = delete_static_path + '/timers/config/minimum-advertisement-interval'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('timers', None) is not None:
+ if cmd['timers'].get('holdtime', None) is not None:
+ delete_path = delete_static_path + '/timers/config/hold-time'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['timers'].get('keepalive', None) is not None:
+ delete_path = delete_static_path + '/timers/config/keepalive-interval'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['timers'].get('connect_retry', None) is not None:
+ delete_path = delete_static_path + '/timers/config/connect-retry'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('capability', None) is not None:
+ if cmd['capability'].get('dynamic', None) is not None:
+ delete_path = delete_static_path + '/config/capability-dynamic'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['capability'].get('extended_nexthop', None) is not None:
+ delete_path = delete_static_path + '/config/capability-extended-nexthop'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('pg_description', None) is not None:
+ delete_path = delete_static_path + '/config/description'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('disable_connected_check', None) is not None:
+ delete_path = delete_static_path + '/config/disable-ebgp-connected-route-check'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('dont_negotiate_capability', None) is not None:
+ delete_path = delete_static_path + '/config/dont-negotiate-capability'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('enforce_first_as', None) is not None:
+ delete_path = delete_static_path + '/config/enforce-first-as'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('enforce_multihop', None) is not None:
+ delete_path = delete_static_path + '/config/enforce-multihop'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('override_capability', None) is not None:
+ delete_path = delete_static_path + '/config/override-capability'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('shutdown_msg', None) is not None:
+ delete_path = delete_static_path + '/config/shutdown-message'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('solo', None) is not None:
+ delete_path = delete_static_path + '/config/solo-peer'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('strict_capability_match', None) is not None:
+ delete_path = delete_static_path + '/config/strict-capability-match'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('ttl_security', None) is not None:
+ delete_path = delete_static_path + '/config/ttl-security-hops'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('local_as', None) is not None:
+ if cmd['local_as'].get('as', None) is not None:
+ delete_path = delete_static_path + '/config/local-as'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['local_as'].get('no_prepend', None) is not None:
+ delete_path = delete_static_path + '/config/local-as-no-prepend'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['local_as'].get('replace_as', None) is not None:
+ delete_path = delete_static_path + '/config/local-as-replace-as'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('local_address', None) is not None:
+ delete_path = delete_static_path + '/transport/config/local-address'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('passive', None) is not None:
+ delete_path = delete_static_path + '/transport/config/passive-mode'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('bfd', None) is not None:
+ if cmd['bfd'].get('enabled', None) is not None:
+ delete_path = delete_static_path + '/enable-bfd/config/enabled'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['bfd'].get('check_failure', None) is not None:
+ delete_path = delete_static_path + '/enable-bfd/config/check-control-plane-failure'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['bfd'].get('profile', None) is not None:
+ delete_path = delete_static_path + '/enable-bfd/config/bfd-profile'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('auth_pwd', None) is not None:
+ if cmd['auth_pwd'].get('pwd', None) is not None:
+ delete_path = delete_static_path + '/auth-password/config/password'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['auth_pwd'].get('encrypted', None) is not None:
+ delete_path = delete_static_path + '/auth-password/config/encrypted'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('ebgp_multihop', None) is not None:
+ if cmd['ebgp_multihop'].get('enabled', None) is not None:
+ 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'
+ 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:
+ delete_path = delete_static_path + '/afi-safis/afi-safi'
+ requests.append({'path': delete_path, 'method': DELETE})
+ else:
+ for each in cmd['address_family']['afis']:
+ afi = each.get('afi', None)
+ safi = each.get('safi', None)
+ activate = each.get('activate', None)
+ allowas_in = each.get('allowas_in', None)
+ ip_afi = each.get('ip_afi', None)
+ prefix_limit = each.get('prefix_limit', None)
+ prefix_list_in = each.get('prefix_list_in', None)
+ prefix_list_out = each.get('prefix_list_out', None)
+ afi_safi = afi.upper() + '_' + safi.upper()
+ afi_safi_name = 'openconfig-bgp-types:' + afi_safi
+ if (afi and safi and not activate and not allowas_in and not ip_afi and not prefix_limit and not prefix_list_in
+ and not prefix_list_out):
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s' % (afi_safi_name)
+ requests.append({'path': delete_path, 'method': DELETE})
+ else:
+ if activate:
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s/config/enabled' % (afi_safi_name)
+ requests.append({'path': delete_path, 'method': DELETE})
+ if allowas_in:
+ if allowas_in.get('origin', None):
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s/allow-own-as/config/origin' % (afi_safi_name)
+ requests.append({'path': delete_path, 'method': DELETE})
+ if allowas_in.get('value', None):
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s/allow-own-as/config/as-count' % (afi_safi_name)
+ requests.append({'path': delete_path, 'method': DELETE})
+ if prefix_list_in:
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s/prefix-list/config/import-policy' % (afi_safi_name)
+ requests.append({'path': delete_path, 'method': DELETE})
+ if prefix_list_out:
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s/prefix-list/config/export-policy' % (afi_safi_name)
+ requests.append({'path': delete_path, 'method': DELETE})
+ if afi_safi == 'IPV4_UNICAST':
+ if ip_afi:
+ requests.extend(self.delete_ip_afi_requests(ip_afi, afi_safi_name, 'ipv4-unicast', delete_static_path))
+ if prefix_limit:
+ requests.extend(self.delete_prefix_limit_requests(prefix_limit, afi_safi_name, 'ipv4-unicast', delete_static_path))
+ elif afi_safi == 'IPV6_UNICAST':
+ if ip_afi:
+ 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
+
+ def delete_ip_afi_requests(self, ip_afi, afi_safi_name, afi_safi, delete_static_path):
+ requests = []
+ default_policy_name = ip_afi.get('default_policy_name', None)
+ send_default_route = ip_afi.get('send_default_route', None)
+ if default_policy_name:
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s/%s/config/default-policy-name' % (afi_safi_name, afi_safi)
+ requests.append({'path': delete_path, 'method': DELETE})
+ if send_default_route:
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s/%s/config/send_default_route' % (afi_safi_name, afi_safi)
+ requests.append({'path': delete_path, 'method': DELETE})
+
+ return requests
+
+ def delete_prefix_limit_requests(self, prefix_limit, afi_safi_name, afi_safi, delete_static_path):
+ requests = []
+ max_prefixes = prefix_limit.get('max_prefixes', None)
+ prevent_teardown = prefix_limit.get('prevent_teardown', None)
+ warning_threshold = prefix_limit.get('warning_threshold', None)
+ restart_timer = prefix_limit.get('restart_timer', None)
+ if max_prefixes:
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s/%s/prefix-limit/config/max-prefixes' % (afi_safi_name, afi_safi)
+ requests.append({'path': delete_path, 'method': DELETE})
+ if prevent_teardown:
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s/%s/prefix-limit/config/prevent-teardown' % (afi_safi_name, afi_safi)
+ requests.append({'path': delete_path, 'method': DELETE})
+ if warning_threshold:
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s/%s/prefix-limit/config/warning-threshold-pct' % (afi_safi_name, afi_safi)
+ requests.append({'path': delete_path, 'method': DELETE})
+ if restart_timer:
+ delete_path = delete_static_path + '/afi-safis/afi-safi=%s/%s/prefix-limit/config/restart-timer' % (afi_safi_name, afi_safi)
+ requests.append({'path': delete_path, 'method': DELETE})
+
+ return requests
+
+ def get_delete_specific_bgp_param_request(self, vrf_name, cmd, want_match):
+ requests = []
+ want_neighbors = want_match.get('neighbors', None)
+ for each in cmd['neighbors']:
+ if each:
+ neighbor = each.get('neighbor', None)
+ remote_as = each.get('remote_as', None)
+ peer_group = each.get('peer_group', None)
+ timers = each.get('timers', None)
+ 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:
+ 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']
+ if not any(want_nei_match.get(key, None) for key in keys):
+ requests.append(self.delete_neighbor_whole_request(vrf_name, neighbor))
+ else:
+ requests.extend(self.delete_specific_param_request(vrf_name, each))
+ return requests
+
+ def delete_neighbor_whole_request(self, vrf_name, neighbor):
+ requests = []
+ url = '%s=%s/%s/%s=%s/' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, self.neighbor_path, neighbor)
+ return ({'path': url, 'method': DELETE})
+
+ def delete_specific_param_request(self, vrf_name, cmd):
+ requests = []
+ delete_static_path = '%s=%s/%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path)
+ delete_static_path = delete_static_path + '/neighbors/neighbor=%s' % (cmd['neighbor'])
+ if cmd.get('remote_as', None) is not None:
+ if cmd['remote_as'].get('peer_as', None) is not None:
+ delete_path = delete_static_path + '/config/peer-as'
+ requests.append({'path': delete_path, 'method': DELETE})
+ elif cmd['remote_as'].get('peer_type', None) is not None:
+ delete_path = delete_static_path + '/config/peer-type'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('peer_group', None) is not None:
+ delete_path = delete_static_path + '/config/peer-group'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('nbr_description', None) is not None:
+ delete_path = delete_static_path + '/config/description'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('disable_connected_check', None) is not None:
+ delete_path = delete_static_path + '/config/disable-ebgp-connected-route-check'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('dont_negotiate_capability', None) is not None:
+ delete_path = delete_static_path + '/config/dont-negotiate-capability'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('enforce_first_as', None) is not None:
+ delete_path = delete_static_path + '/config/enforce-first-as'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('enforce_multihop', None) is not None:
+ delete_path = delete_static_path + '/config/enforce-multihop'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('override_capability', None) is not None:
+ delete_path = delete_static_path + '/config/override-capability'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('port', None) is not None:
+ delete_path = delete_static_path + '/config/peer-port'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('shutdown_msg', None) is not None:
+ delete_path = delete_static_path + '/config/shutdown-message'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('solo', None) is not None:
+ delete_path = delete_static_path + '/config/solo-peer'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('strict_capability_match', None) is not None:
+ delete_path = delete_static_path + '/config/strict-capability-match'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('ttl_security', None) is not None:
+ delete_path = delete_static_path + '/config/ttl-security-hops'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('v6only', None) is not None:
+ delete_path = delete_static_path + '/config/openconfig-bgp-ext:v6only'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('local_as', None) is not None:
+ if cmd['local_as'].get('as', None) is not None:
+ delete_path = delete_static_path + '/config/local-as'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['local_as'].get('no_prepend', None) is not None:
+ delete_path = delete_static_path + '/config/local-as-no-prepend'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['local_as'].get('replace_as', None) is not None:
+ delete_path = delete_static_path + '/config/local-as-replace-as'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('local_address', None) is not None:
+ delete_path = delete_static_path + '/transport/config/local-address'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('passive', None) is not None:
+ delete_path = delete_static_path + '/transport/config/passive-mode'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('advertisement_interval', None) is not None:
+ delete_path = delete_static_path + '/timers/config/minimum-advertisement-interval'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('timers', None) is not None:
+ if cmd['timers'].get('holdtime', None) is not None:
+ delete_path = delete_static_path + '/timers/config/hold-time'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['timers'].get('keepalive', None) is not None:
+ delete_path = delete_static_path + '/timers/config/keepalive-interval'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['timers'].get('connect_retry', None) is not None:
+ delete_path = delete_static_path + '/timers/config/connect-retry'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('capability', None) is not None:
+ if cmd['capability'].get('dynamic', None) is not None:
+ delete_path = delete_static_path + '/config/capability-dynamic'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['capability'].get('extended_nexthop', None) is not None:
+ delete_path = delete_static_path + '/config/capability-extended-nexthop'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('bfd', None) is not None:
+ if cmd['bfd'].get('enabled', None) is not None:
+ delete_path = delete_static_path + '/enable-bfd/config/enabled'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['bfd'].get('check_failure', None) is not None:
+ delete_path = delete_static_path + '/enable-bfd/config/check-control-plane-failure'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['bfd'].get('profile', None) is not None:
+ delete_path = delete_static_path + '/enable-bfd/config/bfd-profile'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('auth_pwd', None) is not None:
+ if cmd['auth_pwd'].get('pwd', None) is not None:
+ delete_path = delete_static_path + '/auth-password/config/password'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd['auth_pwd'].get('encrypted', None) is not None:
+ delete_path = delete_static_path + '/auth-password/config/encrypted'
+ requests.append({'path': delete_path, 'method': DELETE})
+ if cmd.get('ebgp_multihop', None) is not None:
+ if cmd['ebgp_multihop'].get('enabled', None) is not None:
+ 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'
+ requests.append({'path': delete_path, 'method': DELETE})
+
+ return requests
+
+ def get_delete_vrf_specific_neighbor_request(self, vrf_name, have):
+ requests = []
+ for each in have:
+ if each.get('neighbor', None):
+ requests.append(self.delete_neighbor_whole_request(vrf_name, each['neighbor']))
+ return requests
+
+ def get_delete_vrf_specific_peergroup_request(self, vrf_name, peergroup_name):
+ requests = []
+ delete_neighbor_path = '%s=%s/%s/peer-groups/peer-group=%s' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, peergroup_name)
+ return ({'path': delete_neighbor_path, 'method': DELETE})
+
+ def get_delete_all_bgp_neighbor_requests(self, commands):
+ requests = []
+ for cmd in commands:
+ if cmd.get('neighbors', None):
+ requests.extend(self.get_delete_vrf_specific_neighbor_request(cmd['vrf_name'], cmd['neighbors']))
+ if 'peer_group' in cmd and cmd['peer_group']:
+ for each in cmd['peer_group']:
+ requests.append(self.get_delete_vrf_specific_peergroup_request(cmd['vrf_name'], each['name']))
+ return requests
+
+ def get_delete_bgp_neighbor_requests(self, commands, have, want, is_delete_all):
+ requests = []
+ if is_delete_all:
+ requests = self.get_delete_all_bgp_neighbor_requests(commands)
+ else:
+ for cmd in commands:
+ vrf_name = cmd['vrf_name']
+ as_val = cmd['bgp_as']
+ neighbors = cmd.get('neighbors', None)
+ peer_group = cmd.get('peer_group', None)
+ want_match = next((cfg for cfg in want if vrf_name == cfg['vrf_name'] and as_val == cfg['bgp_as']), None)
+ want_neighbors = want_match.get('neighbors', None)
+ want_peer_group = want_match.get('peer_group', None)
+ if neighbors is None and peer_group is None and want_neighbors is None and want_peer_group is None:
+ new_cmd = {}
+ for each in have:
+ if vrf_name == each['vrf_name'] and as_val == each['bgp_as']:
+ new_neighbors = []
+ new_pg = []
+ if each.get('neighbors', None):
+ new_neighbors = [{'neighbor': i['neighbor']} for i in each.get('neighbors', None)]
+ if each.get('peer_group', None):
+ new_pg = [{'name': i['name']} for i in each.get('peer_group', None)]
+ if new_neighbors:
+ new_cmd['neighbors'] = new_neighbors
+ requests.extend(self.get_delete_vrf_specific_neighbor_request(vrf_name, new_cmd['neighbors']))
+ if new_pg:
+ new_cmd['name'] = new_pg
+ for each in new_cmd['name']:
+ requests.append(self.get_delete_vrf_specific_peergroup_request(vrf_name, each['name']))
+ break
+ else:
+ if neighbors:
+ requests.extend(self.get_delete_specific_bgp_param_request(vrf_name, cmd, want_match))
+ if peer_group:
+ requests.extend(self.get_delete_specific_bgp_peergroup_param_request(vrf_name, cmd, want_match))
+ 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
new file mode 100644
index 000000000..15f46f966
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/bgp_neighbors_af/bgp_neighbors_af.py
@@ -0,0 +1,584 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_bgp_neighbors_af 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
+
+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 (
+ 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,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import (
+ validate_bgps,
+ normalize_neighbors_interface_name,
+ get_ip_afi_cfg_payload,
+ get_prefix_limit_payload
+)
+from ansible.module_utils.connection import ConnectionError
+
+PATCH = 'patch'
+DELETE = 'delete'
+TEST_KEYS = [
+ {'config': {'vrf_name': '', 'bgp_as': ''}},
+ {'neighbors': {'neighbor': ''}},
+ {'address_family': {'afi': '', 'safi': ''}},
+ {'route_map': {'name': '', 'direction': ''}},
+]
+
+
+class Bgp_neighbors_af(ConfigBase):
+ """
+ The sonic_bgp_neighbors_af class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'bgp_neighbors_af',
+ ]
+
+ network_instance_path = '/data/openconfig-network-instance:network-instances/network-instance'
+ protocol_bgp_path = 'protocols/protocol=BGP,bgp/bgp'
+ neighbor_path = 'neighbors/neighbor'
+ afi_safi_path = 'afi-safis/afi-safi'
+ activate_path = "/config/enabled"
+ ref_client_path = "/config/route-reflector-client"
+ serv_client_path = "/config/route-server-client"
+ allowas_origin_path = "/allow-own-as/config/origin"
+ allowas_value_path = "/allow-own-as/config/as-count"
+ allowas_enabled_path = "/allow-own-as/config/enabled"
+ prefix_list_in_path = "/prefix-list/config/import-policy"
+ prefix_list_out_path = "/prefix-list/config/export-policy"
+ def_policy_name_path = "/%s/config/default-policy-name"
+ send_def_route_path = "/%s/config/send-default-route"
+ max_prefixes_path = "/%s/prefix-limit/config/max-prefixes"
+ prv_teardown_path = "/%s/prefix-limit/config/prevent-teardown"
+ restart_timer_path = "/%s/prefix-limit/config/restart-timer"
+ wrn_threshold_path = "/%s/prefix-limit/config/warning-threshold-pct"
+
+ def __init__(self, module):
+ super(Bgp_neighbors_af, self).__init__(module)
+
+ def get_bgp_neighbors_af_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)
+ bgp_neighbors_af_facts = facts['ansible_network_resources'].get('bgp_neighbors_af')
+ if not bgp_neighbors_af_facts:
+ bgp_neighbors_af_facts = []
+ return bgp_neighbors_af_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+ existing_bgp_neighbors_af_facts = self.get_bgp_neighbors_af_facts()
+ commands, requests = self.set_config(existing_bgp_neighbors_af_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_bgp_neighbors_af_facts = self.get_bgp_neighbors_af_facts()
+
+ result['before'] = existing_bgp_neighbors_af_facts
+ if result['changed']:
+ result['after'] = changed_bgp_neighbors_af_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_bgp_neighbors_af_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']
+ normalize_neighbors_interface_name(want, self._module)
+ have = existing_bgp_neighbors_af_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, 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, diff)
+ 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 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
+ validate_bgps(self._module, want, have)
+ requests = self.get_modify_bgp_neighbors_af_requests(commands, have)
+ 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
+
+ :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
+ """
+ # if want is none, then delete all the bgp_neighbors_afs
+ is_delete_all = False
+ if not want:
+ commands = have
+ is_delete_all = True
+ else:
+ commands = want
+
+ requests = self.get_delete_bgp_neighbors_af_requests(commands, have, is_delete_all)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def set_val(self, cfg, var, src_key, des_key):
+ value = var.get(src_key, None)
+ if value is not None:
+ cfg[des_key] = value
+
+ def get_allowas_in(self, match, conf_neighbor_val, conf_afi, conf_safi):
+ mat_allowas_in = None
+ if match:
+ mat_neighbors = match.get('neighbors', None)
+ if mat_neighbors:
+ mat_neighbor = next((nei for nei in mat_neighbors if nei['neighbor'] == conf_neighbor_val), None)
+ if mat_neighbor:
+ mat_nei_addr_fams = mat_neighbor.get('address_family', [])
+ if mat_nei_addr_fams:
+ mat_nei_addr_fam = next((af for af in mat_nei_addr_fams if (af['afi'] == conf_afi and af['safi'] == conf_safi)), None)
+ if mat_nei_addr_fam:
+ mat_allowas_in = mat_nei_addr_fam.get('allowas_in', None)
+ return mat_allowas_in
+
+ def get_single_neighbors_af_modify_request(self, match, vrf_name, conf_neighbor_val, conf_neighbor):
+ requests = []
+ conf_nei_addr_fams = conf_neighbor.get('address_family', [])
+ url = '%s=%s/%s/%s=%s/afi-safis' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, self.neighbor_path, conf_neighbor_val)
+ payload = {}
+ afi_safis = []
+ if not conf_nei_addr_fams:
+ return requests
+
+ for conf_nei_addr_fam in conf_nei_addr_fams:
+ afi_safi = {}
+ conf_afi = conf_nei_addr_fam.get('afi', None)
+ conf_safi = conf_nei_addr_fam.get('safi', None)
+ afi_safi_val = ("%s_%s" % (conf_afi, conf_safi)).upper()
+ del_url = '%s=%s/%s/%s=%s/' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, self.neighbor_path, conf_neighbor_val)
+ del_url += '%s=openconfig-bgp-types:%s' % (self.afi_safi_path, afi_safi_val)
+
+ afi_safi_cfg = {}
+ if conf_afi and conf_safi:
+ afi_safi_name = ("%s_%s" % (conf_afi, conf_safi)).upper()
+ afi_safi['afi-safi-name'] = afi_safi_name
+ afi_safi_cfg['afi-safi-name'] = afi_safi_name
+
+ self.set_val(afi_safi_cfg, conf_nei_addr_fam, 'activate', 'enabled')
+ self.set_val(afi_safi_cfg, conf_nei_addr_fam, 'route_reflector_client', 'route-reflector-client')
+ self.set_val(afi_safi_cfg, conf_nei_addr_fam, 'route_server_client', 'route-server-client')
+
+ if afi_safi_cfg:
+ afi_safi['config'] = afi_safi_cfg
+
+ policy_cfg = {}
+ conf_route_map = conf_nei_addr_fam.get('route_map', None)
+ if conf_route_map:
+ for route in conf_route_map:
+ policy_key = "import-policy" if "in" == route['direction'] else "export-policy"
+ route_name = route['name']
+ policy_cfg[policy_key] = [route_name]
+ if policy_cfg:
+ afi_safi['apply-policy'] = {'config': policy_cfg}
+
+ pfx_lst_cfg = {}
+ conf_prefix_list_in = conf_nei_addr_fam.get('prefix_list_in', None)
+ conf_prefix_list_out = conf_nei_addr_fam.get('prefix_list_out', None)
+ if conf_prefix_list_in:
+ pfx_lst_cfg['import-policy'] = conf_prefix_list_in
+ if conf_prefix_list_out:
+ pfx_lst_cfg['export-policy'] = conf_prefix_list_out
+ if pfx_lst_cfg:
+ afi_safi['prefix-list'] = {'config': pfx_lst_cfg}
+
+ ip_dict = {}
+ ip_afi_cfg = {}
+ pfx_lmt_cfg = {}
+ conf_ip_afi = conf_nei_addr_fam.get('ip_afi')
+ conf_prefix_limit = conf_nei_addr_fam.get('prefix_limit')
+ 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}}
+ else:
+ if conf_ip_afi:
+ ip_afi_cfg = get_ip_afi_cfg_payload(conf_ip_afi)
+ if ip_afi_cfg:
+ ip_dict['config'] = ip_afi_cfg
+ if pfx_lmt_cfg:
+ ip_dict['prefix-limit'] = {'config': pfx_lmt_cfg}
+ if ip_dict and afi_safi_val == 'IPV4_UNICAST':
+ afi_safi['ipv4-unicast'] = ip_dict
+ elif ip_dict and afi_safi_val == 'IPV6_UNICAST':
+ afi_safi['ipv6-unicast'] = ip_dict
+
+ allowas_in_cfg = {}
+ conf_allowas_in = conf_nei_addr_fam.get('allowas_in', None)
+ if conf_allowas_in:
+ mat_allowas_in = self.get_allowas_in(match, conf_neighbor_val, conf_afi, conf_safi)
+ origin = conf_allowas_in.get('origin', None)
+ if origin is not None:
+ if mat_allowas_in:
+ mat_value = mat_allowas_in.get('value', None)
+ if mat_value:
+ self.append_delete_request(requests, mat_value, mat_allowas_in, 'value', del_url, self.allowas_value_path)
+ allowas_in_cfg['origin'] = origin
+ else:
+ value = conf_allowas_in.get('value', None)
+ if value is not None:
+ if mat_allowas_in:
+ mat_origin = mat_allowas_in.get('origin', None)
+ if mat_origin:
+ self.append_delete_request(requests, mat_origin, mat_allowas_in, 'origin', del_url, self.allowas_origin_path)
+ allowas_in_cfg['as-count'] = value
+ if allowas_in_cfg:
+ allowas_in_cfg['enabled'] = True
+ afi_safi['allow-own-as'] = {'config': allowas_in_cfg}
+
+ if afi_safi:
+ afi_safis.append(afi_safi)
+
+ if afi_safis:
+ payload = {"openconfig-network-instance:afi-safis": {"afi-safi": afi_safis}}
+ requests.append({'path': url, 'method': PATCH, 'data': payload})
+
+ return requests
+
+ def get_delete_neighbor_af_routemaps_requests(self, vrf_name, conf_neighbor_val, afi, safi, routes):
+ requests = []
+ for route in routes:
+ afi_safi_name = ("%s_%s" % (afi, safi)).upper()
+ policy_type = "import-policy" if "in" == route['direction'] else "export-policy"
+ url = '%s=%s/%s/%s=%s/' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, self.neighbor_path, conf_neighbor_val)
+ url += ('%s=%s/apply-policy/config/%s' % (self.afi_safi_path, afi_safi_name, policy_type))
+ requests.append({'path': url, 'method': DELETE})
+ return requests
+
+ def get_all_neighbors_af_modify_requests(self, match, conf_neighbors, vrf_name):
+ requests = []
+ for conf_neighbor in conf_neighbors:
+ conf_neighbor_val = conf_neighbor.get('neighbor', None)
+ if conf_neighbor_val:
+ requests.extend(self.get_single_neighbors_af_modify_request(match, vrf_name, conf_neighbor_val, conf_neighbor))
+ return requests
+
+ def get_modify_requests(self, conf, match, vrf_name):
+ requests = []
+ conf_neighbors = conf.get('neighbors', [])
+ mat_neighbors = []
+ if match and match.get('neighbors', None):
+ mat_neighbors = match.get('neighbors')
+
+ if conf_neighbors:
+ for conf_neighbor in conf_neighbors:
+ conf_neighbor_val = conf_neighbor.get('neighbor', None)
+ if conf_neighbor_val is None:
+ continue
+
+ mat_neighbor = next((e_neighbor for e_neighbor in mat_neighbors if e_neighbor['neighbor'] == conf_neighbor_val), None)
+ if mat_neighbor is None:
+ continue
+
+ conf_nei_addr_fams = conf_neighbor.get('address_family', None)
+ mat_nei_addr_fams = mat_neighbor.get('address_family', None)
+ if conf_nei_addr_fams is None or mat_nei_addr_fams is None:
+ continue
+
+ for conf_nei_addr_fam in conf_nei_addr_fams:
+ afi = conf_nei_addr_fam.get('afi', None)
+ safi = conf_nei_addr_fam.get('safi', None)
+ if afi is None or safi is None:
+ continue
+
+ mat_nei_addr_fam = next((addr_fam for addr_fam in mat_nei_addr_fams if (addr_fam['afi'] == afi and addr_fam['safi'] == safi)), None)
+ if mat_nei_addr_fam is None:
+ continue
+
+ conf_route_map = conf_nei_addr_fam.get('route_map', None)
+ mat_route_map = mat_nei_addr_fam.get('route_map', None)
+ if conf_route_map is None or mat_route_map is None:
+ continue
+
+ del_routes = []
+ for route in conf_route_map:
+ exist_route = next((e_route for e_route in mat_route_map if e_route['direction'] == route['direction']), None)
+ if exist_route:
+ del_routes.append(exist_route)
+ if del_routes:
+ requests.extend(self.get_delete_neighbor_af_routemaps_requests(vrf_name, conf_neighbor_val, afi, safi, del_routes))
+
+ requests.extend(self.get_all_neighbors_af_modify_requests(match, conf_neighbors, vrf_name))
+ return requests
+
+ def get_modify_bgp_neighbors_af_requests(self, commands, have):
+ requests = []
+ if not commands:
+ return requests
+
+ # Create URL and payload
+ for conf in commands:
+ vrf_name = conf['vrf_name']
+ as_val = conf['bgp_as']
+
+ match = next((cfg for cfg in have if (cfg['vrf_name'] == vrf_name and (cfg['bgp_as'] == as_val))), None)
+ modify_reqs = self.get_modify_requests(conf, match, vrf_name)
+ if modify_reqs:
+ requests.extend(modify_reqs)
+
+ return requests
+
+ def append_delete_request(self, requests, cur_var, mat_var, key, url, path):
+ ret_value = False
+ request = None
+ if cur_var is not None and mat_var.get(key, None):
+ requests.append({'path': url + path, 'method': DELETE})
+ ret_value = True
+ return ret_value
+
+ def delete_ip_afi_requests(self, conf_ip_afi, mat_ip_afi, conf_afi_safi_val, url):
+ requests = []
+ default_policy_name = conf_ip_afi.get('default_policy_name', None)
+ send_default_route = conf_ip_afi.get('send_default_route', None)
+ if default_policy_name:
+ self.append_delete_request(requests, default_policy_name, mat_ip_afi, 'default_policy_name', url, self.def_policy_name_path % (conf_afi_safi_val))
+ if send_default_route:
+ self.append_delete_request(requests, send_default_route, mat_ip_afi, 'send_default_route', url, self.send_def_route_path % (conf_afi_safi_val))
+
+ return requests
+
+ def delete_prefix_limit_requests(self, conf_prefix_limit, mat_prefix_limit, conf_afi_safi_val, url):
+ requests = []
+ max_prefixes = conf_prefix_limit.get('max_prefixes', None)
+ prevent_teardown = conf_prefix_limit.get('prevent_teardown', None)
+ restart_timer = conf_prefix_limit.get('restart_timer', None)
+ warning_threshold = conf_prefix_limit.get('warning_threshold', None)
+ if max_prefixes:
+ self.append_delete_request(requests, max_prefixes, mat_prefix_limit, 'max_prefixes', url, self.max_prefixes_path % (conf_afi_safi_val))
+ if prevent_teardown:
+ self.append_delete_request(requests, prevent_teardown, mat_prefix_limit, 'prevent_teardown', url, self.prv_teardown_path % (conf_afi_safi_val))
+ if restart_timer:
+ self.append_delete_request(requests, restart_timer, mat_prefix_limit, 'restart_timer', url, self.restart_timer_path % (conf_afi_safi_val))
+ if warning_threshold:
+ self.append_delete_request(requests, warning_threshold, mat_prefix_limit, 'warning_threshold', url, self.wrn_threshold_path % (conf_afi_safi_val))
+
+ return requests
+
+ def process_delete_specific_params(self, vrf_name, conf_neighbor_val, conf_nei_addr_fam, conf_afi, conf_safi, matched_nei_addr_fams, url):
+ requests = []
+ conf_afi_safi_val = ("%s-%s" % (conf_afi, conf_safi))
+
+ mat_nei_addr_fam = None
+ if matched_nei_addr_fams:
+ mat_nei_addr_fam = next((e_af for e_af in matched_nei_addr_fams if (e_af['afi'] == conf_afi and e_af['safi'] == conf_safi)), None)
+
+ if mat_nei_addr_fam:
+ conf_alllowas_in = conf_nei_addr_fam.get('allowas_in', None)
+ conf_activate = conf_nei_addr_fam.get('activate', None)
+ conf_route_map = conf_nei_addr_fam.get('route_map', None)
+ conf_route_reflector_client = conf_nei_addr_fam.get('route_reflector_client', None)
+ conf_route_server_client = conf_nei_addr_fam.get('route_server_client', None)
+ conf_prefix_list_in = conf_nei_addr_fam.get('prefix_list_in', None)
+ conf_prefix_list_out = conf_nei_addr_fam.get('prefix_list_out', None)
+ conf_ip_afi = conf_nei_addr_fam.get('ip_afi', None)
+ conf_prefix_limit = conf_nei_addr_fam.get('prefix_limit', None)
+
+ var_list = [conf_alllowas_in, conf_activate, conf_route_map, conf_route_reflector_client, conf_route_server_client,
+ conf_prefix_list_in, conf_prefix_list_out, conf_ip_afi, conf_prefix_limit]
+ if len(list(filter(lambda var: (var is None), var_list))) == len(var_list):
+ requests.append({'path': url, 'method': DELETE})
+ else:
+ mat_route_map = mat_nei_addr_fam.get('route_map', None)
+ if conf_route_map and mat_route_map:
+ del_routes = []
+ for route in conf_route_map:
+ if any(e_route for e_route in mat_route_map if route['direction'] == e_route['direction']):
+ del_routes.append(route)
+ if del_routes:
+ requests.extend(self.get_delete_neighbor_af_routemaps_requests(vrf_name, conf_neighbor_val, conf_afi, conf_safi, del_routes))
+
+ self.append_delete_request(requests, conf_activate, mat_nei_addr_fam, 'activate', url, self.activate_path)
+ self.append_delete_request(requests, conf_route_reflector_client, mat_nei_addr_fam, 'route_reflector_client', url, self.ref_client_path)
+ self.append_delete_request(requests, conf_route_server_client, mat_nei_addr_fam, 'route_server_client', url, self.serv_client_path)
+ self.append_delete_request(requests, conf_prefix_list_in, mat_nei_addr_fam, 'prefix_list_in', url, self.prefix_list_in_path)
+ self.append_delete_request(requests, conf_prefix_list_out, mat_nei_addr_fam, 'prefix_list_out', url, self.prefix_list_out_path)
+
+ mat_alllowas_in = mat_nei_addr_fam.get('allowas_in', None)
+ if conf_alllowas_in is not None and mat_alllowas_in:
+ origin = conf_alllowas_in.get('origin', None)
+ if origin is not None:
+ if self.append_delete_request(requests, origin, mat_alllowas_in, 'origin', url, self.allowas_origin_path):
+ self.append_delete_request(requests, True, {'enabled': True}, 'enabled', url, self.allowas_enabled_path)
+ else:
+ value = conf_alllowas_in.get('value', None)
+ if value is not None:
+ if self.append_delete_request(requests, value, mat_alllowas_in, 'value', url, self.allowas_value_path):
+ self.append_delete_request(requests, True, {'enabled': True}, 'enabled', url, self.allowas_enabled_path)
+
+ mat_ip_afi = mat_nei_addr_fam.get('ip_afi', None)
+ mat_prefix_limit = mat_nei_addr_fam.get('prefix_limit', None)
+ if conf_ip_afi and mat_ip_afi:
+ requests.extend(self.delete_ip_afi_requests(conf_ip_afi, mat_ip_afi, conf_afi_safi_val, url))
+ if conf_prefix_limit and mat_prefix_limit:
+ requests.extend(self.delete_prefix_limit_requests(conf_prefix_limit, mat_prefix_limit, conf_afi_safi_val, url))
+
+ return requests
+
+ def process_neighbor_delete_address_families(self, vrf_name, conf_nei_addr_fams, matched_nei_addr_fams, neighbor_val, is_delete_all):
+ requests = []
+
+ for conf_nei_addr_fam in conf_nei_addr_fams:
+ conf_afi = conf_nei_addr_fam.get('afi', None)
+ conf_safi = conf_nei_addr_fam.get('safi', None)
+ if not conf_afi or not conf_safi:
+ continue
+ afi_safi = ("%s_%s" % (conf_afi, conf_safi)).upper()
+ url = '%s=%s/%s/%s=%s/' % (self.network_instance_path, vrf_name, self.protocol_bgp_path, self.neighbor_path, neighbor_val)
+ url += '%s=openconfig-bgp-types:%s' % (self.afi_safi_path, afi_safi)
+ if is_delete_all:
+ requests.append({'path': url, 'method': DELETE})
+ else:
+ requests.extend(self.process_delete_specific_params(vrf_name, neighbor_val, conf_nei_addr_fam, conf_afi, conf_safi, matched_nei_addr_fams, url))
+
+ return requests
+
+ def get_delete_single_bgp_neighbors_af_request(self, conf, is_delete_all, match=None):
+ requests = []
+ vrf_name = conf['vrf_name']
+ conf_neighbors = conf.get('neighbors', [])
+
+ if match and not conf_neighbors:
+ conf_neighbors = match.get('neighbors', [])
+ if conf_neighbors:
+ conf_neighbors = [{'neighbor': nei['neighbor']} for nei in conf_neighbors]
+
+ if not conf_neighbors:
+ return requests
+ mat_neighbors = None
+ if match:
+ mat_neighbors = match.get('neighbors', [])
+
+ for conf_neighbor in conf_neighbors:
+ conf_neighbor_val = conf_neighbor.get('neighbor', None)
+ if not conf_neighbor_val:
+ continue
+
+ mat_neighbor = None
+ if mat_neighbors:
+ mat_neighbor = next((e_nei for e_nei in mat_neighbors if e_nei['neighbor'] == conf_neighbor_val), None)
+
+ conf_nei_addr_fams = conf_neighbor.get('address_family', None)
+ if mat_neighbor and not conf_nei_addr_fams:
+ conf_nei_addr_fams = mat_neighbor.get('address_family', None)
+ if conf_nei_addr_fams:
+ conf_nei_addr_fams = [{'afi': af['afi'], 'safi': af['safi']} for af in conf_nei_addr_fams]
+
+ if not conf_nei_addr_fams:
+ continue
+
+ mat_nei_addr_fams = None
+ if mat_neighbor:
+ mat_nei_addr_fams = mat_neighbor.get('address_family', None)
+
+ requests.extend(self.process_neighbor_delete_address_families(vrf_name, conf_nei_addr_fams, mat_nei_addr_fams, conf_neighbor_val, is_delete_all))
+
+ return requests
+
+ def get_delete_bgp_neighbors_af_requests(self, commands, have, is_delete_all):
+ requests = []
+ for cmd in commands:
+ vrf_name = cmd['vrf_name']
+ as_val = cmd['bgp_as']
+ match = None
+ if not is_delete_all:
+ match = 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_neighbors_af_request(cmd, is_delete_all, match))
+ return requests
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
new file mode 100644
index 000000000..acf985ebf
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/interfaces/interfaces.py
@@ -0,0 +1,354 @@
+#
+# -*- 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_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
+
+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 (
+ 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.interfaces_util import (
+ build_interfaces_create_request,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import (
+ get_diff,
+ update_states,
+ normalize_interface_name
+)
+from ansible.module_utils._text import to_native
+from ansible.module_utils.connection import ConnectionError
+import traceback
+
+LIB_IMP_ERR = None
+ERR_MSG = None
+try:
+ import requests
+ HAS_LIB = True
+except Exception as e:
+ HAS_LIB = False
+ ERR_MSG = to_native(e)
+ LIB_IMP_ERR = traceback.format_exc()
+
+PATCH = 'patch'
+DELETE = 'delete'
+
+
+class Interfaces(ConfigBase):
+ """
+ The sonic_interfaces class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'interfaces',
+ ]
+
+ params = ('description', 'mtu', 'enabled')
+ delete_flag = False
+
+ def __init__(self, module):
+ super(Interfaces, self).__init__(module)
+
+ def get_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)
+ interfaces_facts = facts['ansible_network_resources'].get('interfaces')
+ if not interfaces_facts:
+ return []
+
+ return interfaces_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+
+ existing_interfaces_facts = self.get_interfaces_facts()
+ commands, requests = self.set_config(existing_interfaces_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_interfaces_facts = self.get_interfaces_facts()
+
+ result['before'] = existing_interfaces_facts
+ if result['changed']:
+ result['after'] = changed_interfaces_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_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']
+ normalize_interface_name(want, self._module)
+ have = existing_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']
+ # diff method works on dict, so creating temp dict
+ diff = get_diff(want, have)
+ # 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)
+ 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, 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 interface_type: interface type
+ :rtype: A list
+ :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 = []
+
+ 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 obj_in_have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ 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)
+
+ 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 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_interface_requests(commands, have)
+ 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
+
+ :param want: the objects from which the configuration should be removed
+ :param obj_in_have: the current configuration as a dictionary
+ :param interface_type: interface type
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ # if want is none, then delete all the interfaces
+ if not want:
+ commands = have
+ 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 = []
+
+ 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
+
+ def filter_comands_to_change(self, configs, have):
+ commands = []
+ if configs:
+ for conf in configs:
+ if self.is_this_change_required(conf, have):
+ 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)
+
+ return self.get_interface_requests(commands, have)
+
+ def get_interface_requests(self, configs, have):
+ requests = []
+ if not configs:
+ return requests
+
+ # 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}
+ 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)
+
+ 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 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
+ else:
+ ret_flag = True
+
+ return ret_flag
+
+ def build_create_payload(self, conf):
+ temp_conf = dict()
+ temp_conf['name'] = conf['name']
+
+ 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
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
new file mode 100644
index 000000000..fccba7707
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l2_interfaces/l2_interfaces.py
@@ -0,0 +1,414 @@
+#
+# -*- 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_l2_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
+
+import json
+
+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,
+ update_states,
+ normalize_interface_name
+)
+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.module_utils._text import to_native
+from ansible.module_utils.connection import ConnectionError
+import traceback
+
+LIB_IMP_ERR = None
+ERR_MSG = None
+try:
+ import requests
+ HAS_LIB = True
+except Exception as e:
+ HAS_LIB = False
+ ERR_MSG = to_native(e)
+ LIB_IMP_ERR = traceback.format_exc()
+
+PATCH = 'patch'
+intf_key = 'openconfig-if-ethernet:ethernet'
+port_chnl_key = 'openconfig-if-aggregate:aggregation'
+
+TEST_KEYS = [
+ {'allowed_vlans': {'vlan': ''}},
+]
+
+
+class L2_interfaces(ConfigBase):
+ """
+ The sonic_l2_interfaces class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'l2_interfaces',
+ ]
+
+ def __init__(self, module):
+ super(L2_interfaces, self).__init__(module)
+
+ def get_l2_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)
+ l2_interfaces_facts = facts['ansible_network_resources'].get('l2_interfaces')
+ if not l2_interfaces_facts:
+ return []
+ return l2_interfaces_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+
+ existing_l2_interfaces_facts = self.get_l2_interfaces_facts()
+ commands, requests = self.set_config(existing_l2_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
+ result['commands'] = commands
+
+ changed_l2_interfaces_facts = self.get_l2_interfaces_facts()
+
+ result['before'] = existing_l2_interfaces_facts
+ if result['changed']:
+ result['after'] = changed_l2_interfaces_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_l2_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']
+ normalize_interface_name(want, self._module)
+ have = existing_l2_interfaces_facts
+
+ for intf in have:
+ if not intf.get('access'):
+ intf.update({'access': None})
+ if not intf.get('trunk'):
+ intf.update({'trunk': None})
+
+ 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)
+
+ 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, 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
+ """
+
+ 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 = []
+
+ 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)
+ 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)
+
+ 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)
+
+ 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 at position-0
+ 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")
+ 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
+ """
+
+ # 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)
+ else:
+ commands = want
+ requests = self.get_delete_specifig_switchport_requests(want, have)
+ if len(requests) == 0:
+ commands = []
+
+ if commands:
+ commands = update_states(commands, "deleted")
+
+ return commands, requests
+
+ def get_trunk_delete_switchport_request(self, config, match_config):
+ method = "DELETE"
+ name = config['name']
+ 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
+
+ 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):
+ requests = []
+ if not configs:
+ return requests
+ # Create URL and payload
+ url = "data/openconfig-interfaces:interfaces/interface={}/{}/openconfig-vlan:switched-vlan/config"
+ method = "DELETE"
+ for intf in configs:
+ name = intf.get("name")
+ key = intf_key
+ if name.startswith('PortChannel'):
+ key = port_chnl_key
+ request = {"path": url.format(name, key),
+ "method": method,
+ }
+ requests.append(request)
+
+ return requests
+
+ def get_delete_specifig_switchport_requests(self, configs, have):
+ 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"
+ for conf in configs:
+ name = conf.get('name')
+ if name == "eth0":
+ continue
+ key = intf_key
+ if name.startswith('PortChannel'):
+ key = port_chnl_key
+ payload = self.build_create_payload(conf)
+ request = {"path": url.format(name, key),
+ "method": method,
+ "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
+ 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
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
new file mode 100644
index 000000000..d1b735251
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/l3_interfaces/l3_interfaces.py
@@ -0,0 +1,515 @@
+#
+# -*- 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_l3_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,
+)
+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.module_utils._text import to_native
+from ansible.module_utils.connection import ConnectionError
+
+TEST_KEYS = [
+ {"addresses": {"address": "", "secondary": ""}}
+]
+
+DELETE = "DELETE"
+PATCH = "PATCH"
+
+
+class L3_interfaces(ConfigBase):
+ """
+ The sonic_l3_interfaces class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min'
+ ]
+
+ gather_network_resources = [
+ 'l3_interfaces',
+ ]
+
+ def __init__(self, module):
+ super(L3_interfaces, self).__init__(module)
+
+ def get_l3_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)
+ l3_interfaces_facts = facts['ansible_network_resources'].get('l3_interfaces')
+ if not l3_interfaces_facts:
+ return []
+ return l3_interfaces_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+
+ existing_l3_interfaces_facts = self.get_l3_interfaces_facts()
+ commands, requests = self.set_config(existing_l3_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
+ result['commands'] = commands
+
+ changed_l3_interfaces_facts = self.get_l3_interfaces_facts()
+
+ result['before'] = existing_l3_interfaces_facts
+ if result['changed']:
+ result['after'] = changed_l3_interfaces_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_l3_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']
+ normalize_interface_name(want, self._module)
+ have = existing_l3_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']
+ 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, diff)
+ elif state == 'merged':
+ commands, requests = self._state_merged(want, have, diff)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have, diff)
+ ret_commands = 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
+ """
+ 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)
+ if diff:
+ delete_l3_interfaces_requests = self.get_delete_all_requests(want)
+ ret_requests.extend(delete_l3_interfaces_requests)
+ commands.extend(update_states(want, "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"))
+ return commands, ret_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
+ """
+ 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)
+ 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)
+ ret_requests.extend(interfaces_to_create_requests)
+ commands.extend(update_states(diff, "merged"))
+
+ return commands, ret_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
+ """
+ self.validate_primary_ips(want)
+ commands = diff
+ requests = self.get_create_l3_interfaces_requests(commands, have, want)
+ 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 = list()
+ if not want:
+ commands = have
+ requests = self.get_delete_all_completely_requests(commands)
+ else:
+ commands = want
+ requests = self.get_delete_l3_interfaces_requests(commands, have)
+ if len(requests) == 0:
+ commands = []
+ if commands:
+ commands = update_states(commands, "deleted")
+ return commands, requests
+
+ def get_object(self, have, want):
+ objects = list()
+ names = [i.get('name', None) for i in want]
+ for obj in have:
+ if 'name' in obj and obj['name'] in names:
+ objects.append(obj.copy())
+ return objects
+
+ def get_address(self, ip_str, have_obj):
+ to_return = list()
+ for i in have_obj:
+ if i.get(ip_str) and i[ip_str].get('addresses'):
+ for ip in i[ip_str]['addresses']:
+ to_return.append(ip['address'])
+ return to_return
+
+ def get_delete_l3_interfaces_requests(self, want, have):
+ requests = []
+ ipv4_addrs_url_all = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv4/addresses'
+ ipv6_addrs_url_all = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/addresses'
+ ipv4_anycast_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv4'
+ ipv4_anycast_url += '/openconfig-interfaces-ext:sag-ipv4/config/static-anycast-gateway={anycast_ip}'
+ ipv4_addr_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv4/addresses/address={address}'
+ 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'
+
+ for each_l3 in want:
+ l3 = each_l3.copy()
+ name = l3.pop('name')
+ sub_intf = self.get_sub_interface_name(name)
+ have_obj = next((e_cfg for e_cfg in have if e_cfg['name'] == name), None)
+ if not have_obj:
+ continue
+ have_ipv4_addrs = list()
+ have_ipv4_anycast_addrs = list()
+ have_ipv6_addrs = list()
+ have_ipv6_enabled = None
+
+ if have_obj.get('ipv4'):
+ if 'addresses' in have_obj['ipv4']:
+ have_ipv4_addrs = have_obj['ipv4']['addresses']
+ if 'anycast_addresses' in have_obj['ipv4']:
+ have_ipv4_anycast_addrs = have_obj['ipv4']['anycast_addresses']
+
+ have_ipv6_addrs = self.get_address('ipv6', [have_obj])
+ if have_obj.get('ipv6') and 'enabled' in have_obj['ipv6']:
+ have_ipv6_enabled = have_obj['ipv6']['enabled']
+
+ ipv4 = l3.get('ipv4', None)
+ ipv6 = l3.get('ipv6', None)
+
+ ipv4_addrs = None
+ ipv6_addrs = None
+
+ is_del_ipv4 = None
+ is_del_ipv6 = None
+ 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'):
+ is_del_ipv4 = True
+ elif ipv6 and not ipv6.get('addresses') and ipv6.get('enabled') is None:
+ is_del_ipv6 = True
+
+ if is_del_ipv4:
+ if have_ipv4_addrs and len(have_ipv4_addrs) != 0:
+ ipv4_addrs_delete_request = {"path": ipv4_addrs_url_all.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE}
+ requests.append(ipv4_addrs_delete_request)
+ if have_ipv4_anycast_addrs and len(have_ipv4_anycast_addrs) != 0:
+ for 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)
+ else:
+ ipv4_addrs = []
+ ipv4_anycast_addrs = []
+ if l3.get('ipv4'):
+ if l3['ipv4'].get('addresses'):
+ ipv4_addrs = l3['ipv4']['addresses']
+ if l3['ipv4'].get('anycast_addresses'):
+ ipv4_anycast_addrs = l3['ipv4']['anycast_addresses']
+
+ # 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 is_del_ipv6:
+ if have_ipv6_addrs and len(have_ipv6_addrs) != 0:
+ ipv6_addrs_delete_request = {"path": ipv6_addrs_url_all.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE}
+ requests.append(ipv6_addrs_delete_request)
+
+ if have_ipv6_enabled:
+ ipv6_enabled_delete_request = {"path": ipv6_enabled_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE}
+ requests.append(ipv6_enabled_delete_request)
+ else:
+ ipv6_addrs = []
+ ipv6_enabled = None
+ if l3.get('ipv6'):
+ if l3['ipv6'].get('addresses'):
+ 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 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}
+ requests.append(request)
+ return requests
+
+ 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)
+ return self.get_delete_all_requests(delete_requests)
+
+ def get_delete_all_requests(self, configs):
+ requests = []
+ ipv4_addrs_url_all = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv4/addresses'
+ ipv4_anycast_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv4'
+ ipv4_anycast_url += '/openconfig-interfaces-ext:sag-ipv4/config/static-anycast-gateway={anycast_ip}'
+ ipv6_addrs_url_all = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/addresses'
+ ipv6_enabled_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config/enabled'
+ for l3 in configs:
+ name = l3.get('name')
+ ipv4_addrs = []
+ ipv4_anycast = []
+ if l3.get('ipv4'):
+ if l3['ipv4'].get('addresses'):
+ ipv4_addrs = l3['ipv4']['addresses']
+ if l3['ipv4'].get('anycast_addresses', None):
+ ipv4_anycast = l3['ipv4']['anycast_addresses']
+
+ ipv6_addrs = []
+ ipv6_enabled = None
+ if l3.get('ipv6'):
+ if l3['ipv6'].get('addresses'):
+ ipv6_addrs = l3['ipv6']['addresses']
+ if 'enabled' in l3['ipv6']:
+ ipv6_enabled = l3['ipv6']['enabled']
+
+ sub_intf = self.get_sub_interface_name(name)
+
+ if ipv4_addrs:
+ ipv4_addrs_delete_request = {"path": ipv4_addrs_url_all.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE}
+ requests.append(ipv4_addrs_delete_request)
+ if ipv4_anycast:
+ for ip in ipv4_anycast:
+ 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 ipv6_addrs:
+ ipv6_addrs_delete_request = {"path": ipv6_addrs_url_all.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE}
+ requests.append(ipv6_addrs_delete_request)
+ if ipv6_enabled:
+ ipv6_enabled_delete_request = {"path": ipv6_enabled_url.format(intf_name=name, sub_intf_name=sub_intf), "method": DELETE}
+ requests.append(ipv6_enabled_delete_request)
+ return requests
+
+ def get_create_l3_interfaces_requests(self, configs, have, want):
+ requests = []
+ if not configs:
+ return requests
+
+ ipv4_addrs_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv4/addresses'
+ ipv4_anycast_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv4/'
+ ipv4_anycast_url += 'openconfig-interfaces-ext:sag-ipv4/config/static-anycast-gateway'
+ ipv6_addrs_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/addresses'
+ ipv6_enabled_url = 'data/openconfig-interfaces:interfaces/interface={intf_name}/{sub_intf_name}/openconfig-if-ip:ipv6/config'
+
+ for l3 in configs:
+ l3_interface_name = l3.get('name')
+ if l3_interface_name == "eth0":
+ continue
+
+ sub_intf = self.get_sub_interface_name(l3_interface_name)
+
+ ipv4_addrs = []
+ ipv4_anycast = []
+ if l3.get('ipv4'):
+ if l3['ipv4'].get('addresses'):
+ ipv4_addrs = l3['ipv4']['addresses']
+ if l3['ipv4'].get('anycast_addresses'):
+ ipv4_anycast = l3['ipv4']['anycast_addresses']
+
+ ipv6_addrs = []
+ ipv6_enabled = None
+ if l3.get('ipv6'):
+ if l3['ipv6'].get('addresses'):
+ ipv6_addrs = l3['ipv6']['addresses']
+ if 'enabled' in l3['ipv6']:
+ ipv6_enabled = l3['ipv6']['enabled']
+
+ if ipv4_addrs:
+ ipv4_addrs_pri_payload = []
+ ipv4_addrs_sec_payload = []
+ for item in ipv4_addrs:
+ ipv4_addr_mask = item['address'].split('/')
+ ipv4 = ipv4_addr_mask[0]
+ ipv4_mask = ipv4_addr_mask[1]
+ ipv4_secondary = item['secondary']
+ if ipv4_secondary:
+ ipv4_addrs_sec_payload.append(self.build_create_addr_payload(ipv4, ipv4_mask, ipv4_secondary))
+ else:
+ ipv4_addrs_pri_payload.append(self.build_create_addr_payload(ipv4, ipv4_mask, ipv4_secondary))
+ if ipv4_addrs_pri_payload:
+ payload = self.build_create_payload(ipv4_addrs_pri_payload)
+ ipv4_addrs_req = {"path": ipv4_addrs_url.format(intf_name=l3_interface_name, sub_intf_name=sub_intf), "method": PATCH, "data": payload}
+ requests.append(ipv4_addrs_req)
+ if ipv4_addrs_sec_payload:
+ payload = self.build_create_payload(ipv4_addrs_sec_payload)
+ ipv4_addrs_req = {"path": ipv4_addrs_url.format(intf_name=l3_interface_name, sub_intf_name=sub_intf), "method": PATCH, "data": payload}
+ requests.append(ipv4_addrs_req)
+
+ if ipv4_anycast:
+ anycast_payload = {'openconfig-interfaces-ext:static-anycast-gateway': ipv4_anycast}
+ anycast_url = ipv4_anycast_url.format(intf_name=l3_interface_name, sub_intf_name=sub_intf)
+ requests.append({'path': anycast_url, 'method': PATCH, 'data': anycast_payload})
+
+ if ipv6_addrs:
+ ipv6_addrs_payload = []
+ for item in ipv6_addrs:
+ ipv6_addr_mask = item['address'].split('/')
+ ipv6 = ipv6_addr_mask[0]
+ ipv6_mask = ipv6_addr_mask[1]
+ ipv6_addrs_payload.append(self.build_create_addr_payload(ipv6, ipv6_mask))
+ if ipv6_addrs_payload:
+ payload = self.build_create_payload(ipv6_addrs_payload)
+ ipv6_addrs_req = {"path": ipv6_addrs_url.format(intf_name=l3_interface_name, sub_intf_name=sub_intf), "method": PATCH, "data": payload}
+ requests.append(ipv6_addrs_req)
+
+ if ipv6_enabled is not None:
+ payload = self.build_update_ipv6_enabled(ipv6_enabled)
+ ipv6_enabled_req = {"path": ipv6_enabled_url.format(intf_name=l3_interface_name, sub_intf_name=sub_intf), "method": PATCH, "data": payload}
+ requests.append(ipv6_enabled_req)
+
+ return requests
+
+ def validate_primary_ips(self, want):
+ error_intf = {}
+ for l3 in want:
+ l3_interface_name = l3.get('name')
+
+ ipv4_addrs = []
+ if l3.get('ipv4') and l3['ipv4'].get('addresses'):
+ ipv4_addrs = l3['ipv4']['addresses']
+
+ if ipv4_addrs:
+ ipv4_pri_addrs = [addr['address'] for addr in ipv4_addrs if not addr['secondary']]
+ if len(ipv4_pri_addrs) > 1:
+ error_intf[l3_interface_name] = ipv4_pri_addrs
+
+ if error_intf:
+ err = "Multiple ipv4 primary ips found! " + str(error_intf)
+ self._module.fail_json(msg=str(err), code=300)
+
+ def build_create_payload(self, addrs_payload):
+ payload = {'openconfig-if-ip:addresses': {'address': addrs_payload}}
+ return payload
+
+ def build_create_addr_payload(self, ip, mask, secondary=None):
+ cfg = {'ip': ip, 'prefix-length': float(mask)}
+ if secondary:
+ cfg['secondary'] = secondary
+ addr_payload = {'ip': ip, 'openconfig-if-ip:config': cfg}
+ return addr_payload
+
+ def get_sub_interface_name(self, name):
+ sub_intf = "subinterfaces/subinterface=0"
+ if name.startswith("Vlan"):
+ sub_intf = "openconfig-vlan:routed-vlan"
+ return sub_intf
+
+ def build_update_ipv6_enabled(self, ipv6_enabled):
+ payload = {'config': {'enabled': ipv6_enabled}}
+ return payload
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
new file mode 100644
index 000000000..541de2c4c
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/lag_interfaces/lag_interfaces.py
@@ -0,0 +1,421 @@
+#
+# -*- 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_lag_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
+
+try:
+ from urllib import quote
+except ImportError:
+ from urllib.parse import quote
+
+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.utils.utils import (
+ update_states,
+ get_diff,
+ normalize_interface_name,
+ remove_empties_from_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.module_utils._text import to_native
+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()
+
+
+PUT = 'put'
+PATCH = 'patch'
+DELETE = 'delete'
+TEST_KEYS = [
+ {'interfaces': {'member': ''}},
+]
+
+
+class Lag_interfaces(ConfigBase):
+ """
+ The sonic_lag_interfaces class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'lag_interfaces',
+ ]
+
+ params = ('name', 'members')
+
+ def __init__(self, module):
+ super(Lag_interfaces, self).__init__(module)
+
+ def get_lag_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)
+ lag_interfaces_facts = facts['ansible_network_resources'].get('lag_interfaces')
+ if not lag_interfaces_facts:
+ return []
+ return lag_interfaces_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_lag_interfaces_facts = self.get_lag_interfaces_facts()
+ commands, requests = self.set_config(existing_lag_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
+ result['commands'] = commands
+
+ changed_lag_interfaces_facts = self.get_lag_interfaces_facts()
+
+ result['before'] = existing_lag_interfaces_facts
+ if result['changed']:
+ result['after'] = changed_lag_interfaces_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_lag_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']
+ normalize_interface_name(want, self._module)
+ have = existing_lag_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
+ """
+ commands = []
+ diff = get_diff(want, have, TEST_KEYS)
+ if diff:
+ diff_members, diff_portchannels = self.diff_list_for_member_creation(diff)
+ else:
+ diff_members = []
+ diff_portchannels = []
+
+ state = self._module.params['state']
+ if state in ('overridden', 'merged', 'replaced') and not want:
+ self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
+
+ if state == 'overridden':
+ commands, requests = self._state_overridden(want, have, diff_members, diff_portchannels)
+ elif state == 'deleted':
+ commands, requests = self._state_deleted(want, have, diff)
+ elif state == 'merged':
+ commands, requests = self._state_merged(want, have, diff_members, diff_portchannels)
+ elif state == 'replaced':
+ commands, requests = self._state_replaced(want, have, diff_members, diff_portchannels)
+
+ return commands, requests
+
+ def _state_replaced(self, want, have, diff_members, diff_portchannels):
+ """ The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ requests = list()
+ commands = list()
+ 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)
+ if requests:
+ commands.extend(update_states(replaced_list, "replaced"))
+ replaced_commands, replaced_requests = self.template_for_lag_creation(have, diff_members, diff_portchannels, "replaced")
+ if replaced_requests:
+ commands.extend(replaced_commands)
+ requests.extend(replaced_requests)
+
+ return commands, requests
+
+ def _state_overridden(self, want, have, diff_members, diff_portchannels):
+ """ The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ requests = list()
+ commands = list()
+ 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)
+ 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):
+ """ The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.template_for_lag_creation(have, diff_members, diff_portchannels, "merged")
+
+ 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 = list()
+ requests = list()
+ portchannel_requests = list()
+ # if want is none, then delete all the lag interfaces and all portchannels
+ if not want:
+ 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"))
+ else: # delete specific lag interfaces and specific portchannels
+ commands = get_diff(want, diff, TEST_KEYS)
+ commands = remove_empties_from_list(commands)
+ want_members, want_portchannels = self.diff_list_for_member_creation(commands)
+ commands, requests = self.template_for_lag_deletion(have, want_members, want_portchannels, "deleted")
+ return commands, requests
+
+ def diff_list_for_member_creation(self, diff):
+ diff_members = [x for x in diff if "members" in x.keys()]
+ diff_portchannels = [x for x in diff if ("name" in x.keys() and "members" not in x.keys())]
+ return diff_members, diff_portchannels
+
+ def template_for_lag_creation(self, have, diff_members, diff_portchannels, state_name):
+ commands = list()
+ requests = list()
+ if diff_members:
+ commands_portchannels, requests = self.call_create_port_channel(diff_members, have)
+ if commands_portchannels:
+ po_list = [{'name': x['name']} for x in commands_portchannels if x['name']]
+ else:
+ po_list = []
+ if po_list:
+ commands.extend(update_states(po_list, state_name))
+ diff_members_remove_none = [x for x in diff_members if x["members"]]
+ if diff_members_remove_none:
+ request = self.create_lag_interfaces_requests(diff_members_remove_none)
+ if request:
+ requests.extend(request)
+ else:
+ requests = request
+ commands.extend(update_states(diff_members, state_name))
+ if diff_portchannels:
+ portchannels, po_requests = self.call_create_port_channel(diff_portchannels, have)
+ requests.extend(po_requests)
+ commands.extend(update_states(portchannels, state_name))
+ return commands, requests
+
+ def template_for_lag_deletion(self, have, delete_members, delete_portchannels, state_name):
+ commands = list()
+ requests = list()
+ portchannel_requests = list()
+ if delete_members:
+ delete_members_remove_none = [x for x in delete_members if x["members"]]
+ requests = self.get_delete_lag_interfaces_requests(delete_members_remove_none)
+ delete_all_members = [x for x in delete_members if "members" in x.keys() and not x["members"]]
+ delete_all_list = list()
+ if delete_all_members:
+ for i in delete_all_members:
+ list_obj = search_obj_in_list(i['name'], have, "name")
+ if list_obj['members']:
+ delete_all_list.append(list_obj)
+ if delete_all_list:
+ deleteall_requests = self.get_delete_lag_interfaces_requests(delete_all_list)
+ else:
+ deleteall_requests = []
+ if requests and deleteall_requests:
+ requests.extend(deleteall_requests)
+ elif deleteall_requests:
+ requests = deleteall_requests
+ if requests:
+ 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))
+ if requests:
+ requests.extend(portchannel_requests)
+ else:
+ requests = portchannel_requests
+ return commands, requests
+
+ def create_lag_interfaces_requests(self, commands):
+ requests = []
+ for i in commands:
+ if i.get('members') and i['members'].get('interfaces'):
+ interfaces = i['members']['interfaces']
+ else:
+ continue
+ for each in interfaces:
+ edit_payload = self.build_create_payload_member(i['name'])
+ template = 'data/openconfig-interfaces:interfaces/interface=%s/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id'
+ edit_path = template % quote(each['member'], safe='')
+ request = {'path': edit_path, 'method': PATCH, 'data': edit_payload}
+ requests.append(request)
+ return requests
+
+ 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}
+ env = jinja2.Environment(autoescape=False)
+ t = env.from_string(payload_template)
+ intended_payload = t.render(input_data)
+ ret_payload = json.loads(intended_payload)
+ return ret_payload
+
+ def build_create_payload_portchannel(self, name, mode):
+ payload_template = """{\n"openconfig-interfaces:interfaces": {"interface": [{\n"name": "{{name}}",\n"config": {\n"name": "{{name}}"\n}"""
+ input_data = {"name": name}
+ if mode == "static":
+ payload_template += """,\n "openconfig-if-aggregation:aggregation": {\n"config": {\n"lag-type": "{{mode}}"\n}\n}\n"""
+ input_data["mode"] = mode.upper()
+ payload_template += """}\n]\n}\n}"""
+ env = jinja2.Environment(autoescape=False)
+ t = env.from_string(payload_template)
+ intended_payload = t.render(input_data)
+ ret_payload = json.loads(intended_payload)
+ return ret_payload
+
+ def create_port_channel(self, cmd):
+ requests = []
+ path = 'data/openconfig-interfaces:interfaces'
+ for i in cmd:
+ payload = self.build_create_payload_portchannel(i['name'], i.get('mode', None))
+ request = {'path': path, 'method': PATCH, 'data': payload}
+ requests.append(request)
+ return requests
+
+ def call_create_port_channel(self, commands, have):
+ commands_list = list()
+ for c in commands:
+ if not any(d['name'] == c['name'] for d in have):
+ commands_list.append(c)
+ requests = self.create_port_channel(commands_list)
+ return commands_list, requests
+
+ def get_delete_all_lag_interfaces_requests(self):
+ requests = []
+ delete_all_lag_url = 'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL_MEMBER/PORTCHANNEL_MEMBER_LIST'
+ method = DELETE
+ delete_all_lag_request = {"path": delete_all_lag_url, "method": method}
+ requests.append(delete_all_lag_request)
+ return requests
+
+ def get_delete_all_portchannel_requests(self):
+ requests = []
+ delete_all_lag_url = 'data/sonic-portchannel:sonic-portchannel/PORTCHANNEL/PORTCHANNEL_LIST'
+ method = DELETE
+ delete_all_lag_request = {"path": delete_all_lag_url, "method": method}
+ requests.append(delete_all_lag_request)
+ return requests
+
+ def get_delete_lag_interfaces_requests(self, commands):
+ requests = []
+ # Create URL and payload
+ url = 'data/openconfig-interfaces:interfaces/interface={}/openconfig-if-ethernet:ethernet/config/openconfig-if-aggregate:aggregate-id'
+ method = DELETE
+ for c in commands:
+ if c.get('members') and c['members'].get('interfaces'):
+ interfaces = c['members']['interfaces']
+ else:
+ continue
+
+ for each in interfaces:
+ ifname = each["member"]
+ request = {"path": url.format(ifname), "method": method}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_portchannel_requests(self, commands):
+ requests = []
+ # Create URL and payload
+ url = 'data/openconfig-interfaces:interfaces/interface={}'
+ method = DELETE
+ for c in commands:
+ name = c["name"]
+ request = {"path": url.format(name), "method": method}
+ requests.append(request)
+
+ return requests
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
new file mode 100644
index 000000000..88215e8fc
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/mclag/mclag.py
@@ -0,0 +1,323 @@
+#
+# -*- 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_mclag 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 (
+ update_states,
+ get_diff,
+ get_normalize_interface_name,
+ normalize_interface_name
+)
+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'
+
+TEST_KEYS = [
+ {'config': {'domain_id': ''}},
+ {'vlans': {'vlan': ''}},
+ {'portchannels': {'lag': ''}},
+]
+
+
+class Mclag(ConfigBase):
+ """
+ The sonic_mclag class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'mclag',
+ ]
+
+ def __init__(self, module):
+ super(Mclag, self).__init__(module)
+
+ def get_mclag_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)
+ mclag_facts = facts['ansible_network_resources'].get('mclag')
+ if not mclag_facts:
+ return []
+ return mclag_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_mclag_facts = self.get_mclag_facts()
+ commands, requests = self.set_config(existing_mclag_facts)
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ self.edit_config(requests)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_mclag_facts = self.get_mclag_facts()
+
+ result['before'] = existing_mclag_facts
+ if result['changed']:
+ result['after'] = changed_mclag_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def edit_config(self, requests):
+ try:
+ response = edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ def set_config(self, existing_mclag_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:
+ peer_link = want.get("peer_link", None)
+ if peer_link:
+ want['peer_link'] = get_normalize_interface_name(want['peer_link'], self._module)
+ unique_ip = want.get('unique_ip', None)
+ if unique_ip:
+ vlans_list = unique_ip['vlans']
+ if vlans_list:
+ normalize_interface_name(vlans_list, self._module, 'vlan')
+ members = want.get('members', None)
+ if members:
+ portchannels_list = members['portchannels']
+ if portchannels_list:
+ normalize_interface_name(portchannels_list, self._module, 'lag')
+ have = existing_mclag_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 = self._state_deleted(want, have)
+ elif state == 'merged':
+ diff = get_diff(want, have, TEST_KEYS)
+ commands = self._state_merged(want, have, diff)
+ return commands
+
+ 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
+ """
+ requests = []
+ commands = []
+ if diff:
+ requests = self.get_create_mclag_request(want, diff)
+ if len(requests) > 0:
+ 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:
+ if have:
+ requests = self.get_delete_all_mclag_domain_request()
+ if len(requests) > 0:
+ commands = update_states(have, "deleted")
+ else:
+ 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 diff_want:
+ requests = self.get_delete_mclag_attribute_request(want, diff_want)
+ if len(requests) > 0:
+ commands = update_states(diff_want, "deleted")
+ return commands, requests
+
+ def remove_default_entries(self, data):
+ new_data = {}
+ if not data:
+ return new_data
+ else:
+ default_val_dict = {
+ 'keepalive': 1,
+ 'session_timeout': 30,
+ }
+ for key, val in data.items():
+ if not (val is None or (key in default_val_dict and val == default_val_dict[key])):
+ new_data[key] = val
+
+ return new_data
+
+ def get_delete_mclag_attribute_request(self, want, command):
+ requests = []
+ url_common = 'data/openconfig-mclag:mclag/mclag-domains/mclag-domain=%s/config' % (want["domain_id"])
+ method = DELETE
+ if 'source_address' in command and command["source_address"] is not None:
+ url = url_common + '/source-address'
+ request = {'path': url, 'method': method}
+ requests.append(request)
+ if 'peer_address' in command and command["peer_address"] is not None:
+ url = url_common + '/peer-address'
+ request = {'path': url, 'method': method}
+ requests.append(request)
+ if 'peer_link' in command and command["peer_link"] is not None:
+ url = url_common + '/peer-link'
+ request = {'path': url, 'method': method}
+ requests.append(request)
+ if 'keepalive' in command and command["keepalive"] is not None:
+ url = url_common + '/keepalive-interval'
+ request = {'path': url, 'method': method}
+ requests.append(request)
+ if 'session_timeout' in command and command["session_timeout"] is not None:
+ url = url_common + '/session-timeout'
+ request = {'path': url, 'method': method}
+ requests.append(request)
+ if 'system_mac' in command and command["system_mac"] is not None:
+ url = url_common + '/mclag-system-mac'
+ request = {'path': 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)
+ 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}
+ requests.append(request)
+ elif command['members']['portchannels'] is not None:
+ for each in command['members']['portchannels']:
+ if each:
+ portchannel_url = 'data/openconfig-mclag:mclag/interfaces/interface=%s' % (each['lag'])
+ request = {'path': portchannel_url, 'method': method}
+ requests.append(request)
+ return requests
+
+ def get_delete_all_mclag_domain_request(self):
+ requests = []
+ path = 'data/openconfig-mclag:mclag/mclag-domains'
+ method = DELETE
+ request = {'path': path, 'method': method}
+ requests.append(request)
+ return requests
+
+ def get_create_mclag_request(self, want, commands):
+ requests = []
+ path = 'data/openconfig-mclag:mclag/mclag-domains/mclag-domain'
+ method = PATCH
+ payload = self.build_create_payload(want, commands)
+ if payload:
+ request = {'path': path, 'method': method, 'data': 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'
+ unique_ip_method = PATCH
+ 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 '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'
+ portchannel_method = PATCH
+ portchannel_payload = self.build_create_portchannel_payload(want, commands['members']['portchannels'])
+ request = {'path': portchannel_path, 'method': portchannel_method, 'data': portchannel_payload}
+ requests.append(request)
+ return requests
+
+ def build_create_payload(self, want, commands):
+ temp = {}
+ if 'session_timeout' in commands and commands['session_timeout'] is not None:
+ temp['session-timeout'] = commands['session_timeout']
+ if 'keepalive' in commands and commands['keepalive'] is not None:
+ temp['keepalive-interval'] = commands['keepalive']
+ if 'source_address' in commands and commands['source_address'] is not None:
+ temp['source-address'] = commands['source_address']
+ if 'peer_address' in commands and commands['peer_address'] is not None:
+ temp['peer-address'] = commands['peer_address']
+ if 'peer_link' in commands and commands['peer_link'] is not None:
+ 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'])
+ mclag_dict = {}
+ if temp:
+ domain_id = {"domain-id": want["domain_id"]}
+ mclag_dict.update(domain_id)
+ config = {"config": temp}
+ mclag_dict.update(config)
+ payload = {"openconfig-mclag:mclag-domain": [mclag_dict]}
+ else:
+ payload = {}
+ return payload
+
+ 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"}})
+ return payload
+
+ def build_create_portchannel_payload(self, want, commands):
+ payload = {"openconfig-mclag:interface": []}
+ for each in commands:
+ payload['openconfig-mclag:interface'].append({"name": each['lag'], "config": {"name": each['lag'], "mclag-domain-id": want['domain_id']}})
+ return payload
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
new file mode 100644
index 000000000..a4fdc7e0a
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/ntp/ntp.py
@@ -0,0 +1,548 @@
+#
+# -*- 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_ntp 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 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,
+ normalize_interface_name,
+ normalize_interface_name_list
+)
+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": ""}
+ }
+]
+
+
+class Ntp(ConfigBase):
+ """
+ The sonic_ntp class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'ntp',
+ ]
+
+ def __init__(self, module):
+ super(Ntp, self).__init__(module)
+
+ def get_ntp_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)
+ ntp_facts = facts['ansible_network_resources'].get('ntp')
+
+ if not ntp_facts:
+ return []
+ return ntp_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_ntp_facts = self.get_ntp_facts()
+
+ commands, requests = self.set_config(existing_ntp_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_ntp_facts = self.get_ntp_facts()
+
+ result['before'] = existing_ntp_facts
+ if result['changed']:
+ result['after'] = changed_ntp_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_ntp_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_ntp_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)
+
+ 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
+ """
+ diff = get_diff(want, have, TEST_KEYS)
+
+ want_none = {'enable_ntp_auth': None, 'ntp_keys': None,
+ 'servers': None, 'source_interfaces': [],
+ 'trusted_keys': None, 'vrf': 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 diff:
+ commands = want_any
+ else:
+ commands = get_diff(want_any, diff, 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 validate_want(self, want, state):
+
+ if state == 'deleted':
+ if 'servers' in want and want['servers'] is not None:
+ for server in want['servers']:
+ 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:
+ err_msg = "NTP server parameter(s) can not be deleted."
+ self._module.fail_json(msg=err_msg, code=405)
+
+ if 'ntp_keys' in want and want['ntp_keys'] is not None:
+ for ntp_key in want['ntp_keys']:
+ encrypted_config = ntp_key.get('encrypted', None)
+ key_type_config = ntp_key.get('key_type', None)
+ key_value_config = ntp_key.get('key_value', None)
+ if encrypted_config or key_type_config or key_value_config:
+ err_msg = "NTP ntp_key parameter(s) can not be deleted."
+ self._module.fail_json(msg=err_msg, code=405)
+
+ def preprocess_want(self, want, state):
+
+ if 'source_interfaces' in want:
+ want['source_interfaces'] = normalize_interface_name_list(want['source_interfaces'], self._module)
+
+ if state == 'deleted':
+ enable_auth_want = want.get('enable_ntp_auth', None)
+ if enable_auth_want is not None:
+ want['enable_ntp_auth'] = True
+
+ elif state == 'merged':
+ if 'servers' in want and want['servers'] is not None:
+ for server in want['servers']:
+ if 'key_id' in server and not server['key_id']:
+ server.pop('key_id')
+ if 'minpoll' in server and not server['minpoll']:
+ server.pop('minpoll')
+ if 'maxpoll' in server and not server['maxpoll']:
+ server.pop('maxpoll')
+
+ def get_merge_requests(self, configs, have):
+
+ requests = []
+
+ enable_auth_config = configs.get('enable_ntp_auth', None)
+ if enable_auth_config is not None:
+ enable_auth_request = self.get_create_enable_ntp_auth_requests(enable_auth_config, have)
+ if enable_auth_request:
+ requests.extend(enable_auth_request)
+
+ src_intf_config = configs.get('source_interfaces', None)
+ if src_intf_config:
+ src_intf_request = self.get_create_source_interface_requests(src_intf_config, have)
+ if src_intf_request:
+ requests.extend(src_intf_request)
+
+ keys_config = configs.get('ntp_keys', None)
+ if keys_config:
+ keys_request = self.get_create_keys_requests(keys_config, have)
+ if keys_request:
+ requests.extend(keys_request)
+
+ servers_config = configs.get('servers', None)
+ if servers_config:
+ servers_request = self.get_create_servers_requests(servers_config, have)
+ if servers_request:
+ requests.extend(servers_request)
+
+ trusted_key_config = configs.get('trusted_keys', None)
+ if trusted_key_config:
+ trusted_key_request = self.get_create_trusted_key_requests(trusted_key_config, have)
+ if trusted_key_request:
+ requests.extend(trusted_key_request)
+
+ vrf_config = configs.get('vrf', None)
+ if vrf_config:
+ vrf_request = self.get_create_vrf_requests(vrf_config, have)
+ if vrf_request:
+ requests.extend(vrf_request)
+
+ return requests
+
+ def get_delete_requests(self, configs, delete_all):
+
+ requests = []
+
+ if delete_all:
+ all_ntp_request = self.get_delete_all_ntp_requests(configs)
+ if all_ntp_request:
+ requests.extend(all_ntp_request)
+ return requests
+
+ src_intf_config = configs.get('source_interfaces', None)
+ if src_intf_config:
+ src_intf_request = self.get_delete_source_interface_requests(src_intf_config)
+ if src_intf_request:
+ requests.extend(src_intf_request)
+
+ servers_config = configs.get('servers', None)
+ if servers_config:
+ servers_request = self.get_delete_servers_requests(servers_config)
+ if servers_request:
+ requests.extend(servers_request)
+
+ trusted_key_config = configs.get('trusted_keys', None)
+ if trusted_key_config:
+ trusted_key_request = self.get_delete_trusted_key_requests(trusted_key_config)
+ if trusted_key_request:
+ requests.extend(trusted_key_request)
+
+ keys_config = configs.get('ntp_keys', None)
+ if keys_config:
+ keys_request = self.get_delete_keys_requests(keys_config)
+ if keys_request:
+ requests.extend(keys_request)
+
+ enable_auth_config = configs.get('enable_ntp_auth', None)
+ if enable_auth_config is not None:
+ enable_auth_request = self.get_delete_enable_ntp_auth_requests(enable_auth_config)
+ if enable_auth_request:
+ requests.extend(enable_auth_request)
+
+ vrf_config = configs.get('vrf', None)
+ if vrf_config:
+ vrf_request = self.get_delete_vrf_requests(vrf_config)
+ if vrf_request:
+ requests.extend(vrf_request)
+
+ return requests
+
+ def get_create_source_interface_requests(self, configs, have):
+
+ requests = []
+
+ # Create URL and payload
+ method = PATCH
+ url = 'data/openconfig-system:system/ntp/config/source-interface'
+ payload = {"openconfig-system:source-interface": configs}
+ request = {"path": url, "method": method, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def get_create_servers_requests(self, configs, have):
+
+ requests = []
+
+ # Create URL and payload
+ method = PATCH
+ url = 'data/openconfig-system:system/ntp/servers'
+ server_configs = []
+ for config in configs:
+ if 'key_id' in config:
+ config['key-id'] = config['key_id']
+ config.pop('key_id')
+ server_addr = config['address']
+ server_config = {"address": server_addr, "config": config}
+ server_configs.append(server_config)
+
+ payload = {"openconfig-system:servers": {"server": server_configs}}
+ request = {"path": url, "method": method, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def get_create_vrf_requests(self, configs, have):
+
+ requests = []
+
+ # Create URL and payload
+ method = PATCH
+ url = 'data/openconfig-system:system/ntp/config/network-instance'
+ payload = {"openconfig-system:network-instance": configs}
+ request = {"path": url, "method": method, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def get_create_enable_ntp_auth_requests(self, configs, have):
+
+ requests = []
+
+ # Create URL and payload
+ method = PATCH
+ url = 'data/openconfig-system:system/ntp/config/enable-ntp-auth'
+ payload = {"openconfig-system:enable-ntp-auth": configs}
+ request = {"path": url, "method": method, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def get_create_trusted_key_requests(self, configs, have):
+
+ requests = []
+
+ # Create URL and payload
+ method = PATCH
+ url = 'data/openconfig-system:system/ntp/config/trusted-key'
+ payload = {"openconfig-system:trusted-key": configs}
+ request = {"path": url, "method": method, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def get_create_keys_requests(self, configs, have):
+
+ requests = []
+
+ # Create URL and payload
+ method = PATCH
+ url = 'data/openconfig-system:system/ntp/ntp-keys'
+ key_configs = []
+ for config in configs:
+ key_id = config['key_id']
+ if 'key_id' in config:
+ config['key-id'] = config['key_id']
+ config.pop('key_id')
+ if 'key_type' in config:
+ config['key-type'] = config['key_type']
+ config.pop('key_type')
+ if 'key_value' in config:
+ config['key-value'] = config['key_value']
+ config.pop('key_value')
+
+ key_config = {"key-id": key_id, "config": config}
+ key_configs.append(key_config)
+
+ payload = {"openconfig-system:ntp-keys": {"ntp-key": key_configs}}
+ request = {"path": url, "method": method, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_all_ntp_requests(self, configs):
+
+ requests = []
+
+ # 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 \
+ trusted_key_config or enable_auth_config is not None:
+ url = 'data/openconfig-system:system/ntp'
+ 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'
+ request = {"path": url, "method": method}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_source_interface_requests(self, configs):
+
+ requests = []
+
+ # Create URL and payload
+ method = DELETE
+ for config in configs:
+ url = 'data/openconfig-system:system/ntp/config/source-interface={0}'.format(config)
+ request = {"path": url, "method": method}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_servers_requests(self, configs):
+
+ requests = []
+
+ # Create URL and payload
+ method = DELETE
+ for config in configs:
+ server_addr = config['address']
+ url = 'data/openconfig-system:system/ntp/servers/server={0}'.format(server_addr)
+ request = {"path": url, "method": method}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_vrf_requests(self, configs):
+
+ requests = []
+
+ # Create URL and payload
+ method = DELETE
+ url = 'data/openconfig-system:system/ntp/config/network-instance'
+ request = {"path": url, "method": method}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_enable_ntp_auth_requests(self, configs):
+
+ requests = []
+
+ # Create URL and payload
+ method = DELETE
+ url = 'data/openconfig-system:system/ntp/config/enable-ntp-auth'
+ request = {"path": url, "method": method}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_trusted_key_requests(self, configs):
+
+ requests = []
+
+ # Create URL and payload
+ method = DELETE
+ for config in configs:
+ url = 'data/openconfig-system:system/ntp/config/trusted-key={0}'.format(config)
+ request = {"path": url, "method": method}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_keys_requests(self, configs):
+
+ requests = []
+
+ # Create URL and payload
+ method = DELETE
+ key_configs = []
+ for config in configs:
+ key_id = config['key_id']
+ url = 'data/openconfig-system:system/ntp/ntp-keys/ntp-key={0}'.format(key_id)
+ request = {"path": url, "method": method}
+ requests.append(request)
+
+ 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
new file mode 100644
index 000000000..371019d04
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/port_breakout/port_breakout.py
@@ -0,0 +1,260 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_port_breakout 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.module_utils.connection import ConnectionError
+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_speed_from_breakout_mode,
+ get_breakout_mode,
+)
+
+PATCH = 'patch'
+DELETE = 'delete'
+POST = 'post'
+
+
+class Port_breakout(ConfigBase):
+ """
+ The sonic_port_breakout class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'port_breakout',
+ ]
+
+ def __init__(self, module):
+ super(Port_breakout, self).__init__(module)
+
+ def get_port_breakout_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_breakout_facts = facts['ansible_network_resources'].get('port_breakout')
+ if not port_breakout_facts:
+ return []
+ return port_breakout_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+ existing_port_breakout_facts = self.get_port_breakout_facts()
+ commands, requests = self.set_config(existing_port_breakout_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_breakout_facts = self.get_port_breakout_facts()
+
+ result['before'] = existing_port_breakout_facts
+ if result['changed']:
+ result['after'] = changed_port_breakout_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_port_breakout_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_breakout_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 = []
+
+ have_new = self.get_all_breakout_mode(have)
+ diff = get_diff(want, have_new)
+
+ 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, diff)
+ 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 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, "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 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
+ """
+ # if want is none, then delete all the port_breakouti except admin
+ if not want:
+ commands = have
+ else:
+ commands = want
+
+ requests = self.get_delete_port_breakout_requests(commands, have)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def get_port_breakout_payload(self, name, mode, match):
+ payload = {}
+ speed = get_speed_from_breakout_mode(mode)
+ if speed:
+ num_breakouts = int(mode[0])
+ mode_cfg = {'groups': {'group': [{'index': 1, 'config': {'index': 1, 'num-breakouts': num_breakouts, 'breakout-speed': speed}}]}}
+ port_cfg = {'openconfig-platform-port:breakout-mode': mode_cfg}
+ compo_cfg = {'name': name, 'port': port_cfg}
+ payload = {'openconfig-platform:components': {'component': [compo_cfg]}}
+ return payload
+
+ def get_delete_single_port_breakout(self, name, match):
+ del_req = None
+ if match:
+ del_url = 'data/openconfig-platform:components/component=%s/port/openconfig-platform-port:breakout-mode' % (name.replace('/', '%2f'))
+ del_req = {'path': del_url, 'method': DELETE}
+ return del_req
+
+ def get_modify_port_breakout_request(self, conf, match):
+ request = None
+ name = conf.get('name', None)
+ mode = conf.get('mode', None)
+ url = 'data/openconfig-platform:components'
+ payload = self.get_port_breakout_payload(name, mode, match)
+ request = {'path': url, 'method': PATCH, 'data': payload}
+ return request
+
+ def get_modify_port_breakout_requests(self, commands, have):
+ requests = []
+ if not commands:
+ return requests
+
+ for conf in commands:
+ match = next((cfg for cfg in have if cfg['name'] == conf['name']), None)
+ req = self.get_modify_port_breakout_request(conf, match)
+ if req:
+ 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:
+ return requests
+
+ have_new = self.get_all_breakout_mode(have)
+ for conf in commands:
+ name = conf['name']
+ match = next((cfg for cfg in have_new if cfg['name'] == name), None)
+ req = self.get_delete_single_port_breakout(name, match)
+ if req:
+ requests.append(req)
+ return requests
+
+ def get_all_breakout_mode(self, have):
+ new_have = []
+ for cfg in have:
+ name = cfg['name']
+ mode = get_breakout_mode(self._module, name)
+ if mode:
+ new_have.append({'name': name, 'mode': mode})
+ return new_have
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/prefix_lists/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/prefix_lists/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/prefix_lists/__init__.py
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
new file mode 100644
index 000000000..d5c36d3e2
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/prefix_lists/prefix_lists.py
@@ -0,0 +1,458 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_prefix_lists class
+It is in this file that 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
+
+TEST_KEYS = [
+ {"config": {"afi": "", "name": ""}},
+ {"prefixes": {"action": "", "ge": "", "le": "", "prefix": "", "sequence": ""}}
+]
+
+DELETE = "delete"
+PATCH = "patch"
+
+
+class Prefix_lists(ConfigBase):
+ """
+ The sonic_prefix_lists class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'prefix_lists',
+ ]
+
+ prefix_sets_uri = 'data/openconfig-routing-policy:routing-policy/defined-sets/prefix-sets'
+ prefix_set_uri = 'data/openconfig-routing-policy:routing-policy/defined-sets/\
+prefix-sets/prefix-set'
+ prefix_set_delete_uri = 'data/openconfig-routing-policy:routing-policy/defined-sets/\
+prefix-sets/prefix-set={}'
+ prefix_set_delete_all_prefixes_uri = 'data/openconfig-routing-policy:routing-policy/\
+defined-sets/prefix-sets/prefix-set={}/openconfig-routing-policy-ext:extended-prefixes'
+ prefix_set_delete_prefix_uri = 'data/openconfig-routing-policy:routing-policy/\
+defined-sets/prefix-sets/prefix-set={}/\
+openconfig-routing-policy-ext:extended-prefixes/extended-prefix={},{},{}'
+ prefix_set_data_path = 'openconfig-routing-policy:prefix-set'
+ ext_prefix_set_data_path = 'openconfig-routing-policy-ext:extended-prefixes'
+
+ def __init__(self, module):
+ super(Prefix_lists, self).__init__(module)
+
+ def get_prefix_lists_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)
+ prefix_lists_facts = facts['ansible_network_resources'].get('prefix_lists', None)
+ if not prefix_lists_facts:
+ return []
+ return prefix_lists_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+
+ existing_prefix_lists_facts = self.get_prefix_lists_facts()
+ commands, requests = self.set_config(existing_prefix_lists_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_prefix_lists_facts = self.get_prefix_lists_facts()
+
+ result['before'] = existing_prefix_lists_facts
+ if result['changed']:
+ result['after'] = changed_prefix_lists_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_prefix_lists_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_prefix_lists_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 == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'merged':
+ commands, requests = self._state_merged(diff)
+ ret_commands = commands
+ return ret_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_prefix_lists_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 = list()
+ if not want or want == []:
+ commands = have
+ requests = self.get_delete_all_prefix_list_cfg_requests()
+ else:
+ commands = want
+ requests = self.get_delete_prefix_lists_cfg_requests(commands, have)
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+ 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,
+ create and return the appropriate set of REST API requests to modify
+ the prefix set specified by the current command.'''
+
+ requests = []
+ if not commands:
+ return requests
+
+ # Create URL and payload
+ prefix_set_payload_list = []
+ for command in commands:
+ prefix_set_payload = self.get_modify_single_prefix_set_request(command)
+ if prefix_set_payload:
+ prefix_set_payload_list.append(prefix_set_payload)
+ prefix_set_data = {self.prefix_set_data_path: prefix_set_payload_list}
+ request = {'path': self.prefix_set_uri, 'method': PATCH, 'data': prefix_set_data}
+ requests.append(request)
+ return requests
+
+ def get_modify_single_prefix_set_request(self, command):
+ '''Create and return the appropriate set of REST API requests to modfy
+ the prefix set configuration specified by the current "command".'''
+
+ request = {}
+ if not command:
+ return request
+
+ conf_afi = command.get('afi', None)
+ conf_name = command.get('name', None)
+ if not conf_afi or not conf_name:
+ return request
+
+ prefix_set_payload_header = {'name': conf_name,
+ 'config': {'name': conf_name, 'mode': conf_afi.upper()}}
+
+ pfx_conf_list = []
+ prefixes = command.get('prefixes', None)
+
+ if prefixes:
+ for prefix in prefixes:
+ pfx_payload = self.get_modify_prefix_request(prefix, conf_afi)
+ if pfx_payload:
+ pfx_conf_list.append(pfx_payload)
+
+ ext_prefix_list_payload = {'extended-prefix': pfx_conf_list}
+ ext_prefix_list_data = {self.ext_prefix_set_data_path: ext_prefix_list_payload}
+
+ prefix_set_payload = prefix_set_payload_header
+ prefix_set_payload.update(ext_prefix_list_data)
+ return prefix_set_payload
+
+ def get_modify_prefix_request(self, prefix, conf_afi):
+ '''Create a REST API request to update/merge/create the prefix specified by the
+ "prefix" input parameter.'''
+
+ pfx_payload = {}
+ prefix_val = prefix.get('prefix', None)
+ sequence = prefix.get('sequence', None)
+ action = prefix.get('action', None)
+ if not prefix_val or not sequence or not action:
+ return None
+
+ prefix_net = self.set_ipaddress_net_attrs(prefix_val, conf_afi)
+ ge = prefix.get('ge', None)
+ le = prefix.get('le', None)
+ pfx_payload['ip-prefix'] = prefix_val
+ pfx_payload['sequence-number'] = sequence
+ masklength_range_str = self.get_masklength_range_string(ge, le, prefix_net)
+ pfx_payload['masklength-range'] = masklength_range_str
+ pfx_config = {}
+ pfx_config['sequence-number'] = sequence
+ pfx_config['ip-prefix'] = prefix_val
+ pfx_config['masklength-range'] = pfx_payload['masklength-range']
+ pfx_config['openconfig-routing-policy-ext:action'] = action.upper()
+ pfx_payload['config'] = pfx_config
+
+ return pfx_payload
+
+ def get_create_prefix_lists_cfg_requests(self, commands):
+ '''Placeholder function Modify this function if necessary to enable
+ separate actions for "CREATE" vs "MERGE" ("PATCH") requests'''
+
+ return self.get_modify_prefix_lists_requests(commands)
+
+ def get_delete_prefix_lists_cfg_requests(self, commands, have):
+ '''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 prefix set configuration specified by the current "command".'''
+ requests = []
+ for command in commands:
+ new_requests = self.get_delete_single_prefix_cfg_requests(command, have)
+ if new_requests and len(new_requests) > 0:
+ requests.extend(new_requests)
+ return requests
+
+ def get_delete_single_prefix_cfg_requests(self, command, have):
+ '''Create and return the appropriate set of REST API requests to delete
+ the prefix set configuration specified by the current "command".'''
+
+ requests = list()
+ pfx_set_name = command.get('name', None)
+ if not pfx_set_name:
+ return requests
+
+ cfg_prefix_set = self.prefix_set_in_config(pfx_set_name, have)
+ if not cfg_prefix_set:
+ return requests
+
+ prefixes = command.get('prefixes', None)
+ if not prefixes or prefixes == []:
+ requests = self.get_delete_prefix_set_cfg(command)
+ else:
+ requests = self.get_delete_one_prefix_list_cfg(cfg_prefix_set, command)
+ return requests
+
+ def get_delete_prefix_set_cfg(self, command):
+ '''Create and return a REST API request to delete the prefix set specified
+ by the current "command".'''
+
+ pfx_set_name = command.get('name', None)
+
+ requests = [{'path': self.prefix_set_delete_uri.format(pfx_set_name), 'method': DELETE}]
+ return requests
+
+ def get_delete_one_prefix_list_cfg(self, cfg_prefix_set, command):
+ '''Create the list of REST API prefix deletion requests needed for deletion
+ of the the requested set of prefixes from the currently configured
+ prefix set specified by "cfg_prefix_set".'''
+
+ pfx_delete_cfg_list = list()
+ prefixes = command.get('prefixes', None)
+
+ for prefix in prefixes:
+ pfx_delete_cfg = self.prefix_get_delete_single_prefix_cfg(prefix,
+ cfg_prefix_set,
+ command)
+ if pfx_delete_cfg and len(pfx_delete_cfg) > 0:
+ pfx_delete_cfg_list.append(pfx_delete_cfg)
+ return pfx_delete_cfg_list
+
+ def prefix_get_delete_single_prefix_cfg(self, prefix, cfg_prefix_set, command):
+ '''Create the REST API request to delete the prefix specified by the "prefix"
+ input parameter from the configured prefix set specified by "cfg_prefix_set".
+ Return an empty request if the prefix is not present in the confgured prefix set.'''
+
+ pfx_delete_cfg_request = {}
+ if not self.prefix_in_prefix_list_cfg(prefix, cfg_prefix_set):
+ return pfx_delete_cfg_request
+
+ conf_afi = command.get('afi', None)
+ if not conf_afi:
+ return pfx_delete_cfg_request
+
+ pfx_set_name = command.get('name', None)
+ pfx_seq = prefix.get("sequence", None)
+ pfx_val = prefix.get("prefix", None)
+ pfx_ge = prefix.get("ge", None)
+ pfx_le = prefix.get("le", None)
+
+ if not pfx_seq or not pfx_val:
+ return pfx_delete_cfg_request
+
+ prefix_net = self.set_ipaddress_net_attrs(pfx_val, conf_afi)
+ masklength_range_str = self.get_masklength_range_string(pfx_ge, pfx_le, prefix_net)
+ prefix_string = pfx_val.replace("/", "%2F")
+ extended_pfx_cfg_str = self.prefix_set_delete_prefix_uri.format(pfx_set_name,
+ int(pfx_seq),
+ prefix_string,
+ masklength_range_str)
+ pfx_delete_cfg_request = {'path': extended_pfx_cfg_str, 'method': DELETE}
+ return pfx_delete_cfg_request
+
+ def get_delete_all_prefix_list_cfg_requests(self):
+ '''Delete all prefix list configuration'''
+ requests = list()
+ requests = [{'path': self.prefix_sets_uri, 'method': DELETE}]
+ return requests
+
+ def get_masklength_range_string(self, pfx_ge, pfx_le, prefix_net):
+ '''Determine the "masklength range" string required for the openconfig
+ REST API to configure the affected prefix.'''
+ if not pfx_ge and not pfx_le:
+ masklength_range_string = "exact"
+ elif pfx_ge and not pfx_le:
+ masklength_range_string = str(pfx_ge) + ".." + str(prefix_net['max_prefixlen'])
+ elif not pfx_ge and pfx_le:
+ masklength_range_string = str(prefix_net['prefixlen']) + ".." + str(pfx_le)
+ else:
+ masklength_range_string = str(pfx_ge) + ".." + str(pfx_le)
+
+ return masklength_range_string
+
+ def prefix_set_in_config(self, pfx_set_name, have):
+ '''Determine if the prefix set specifid by "pfx_set_name" is present in
+ the current switch configuration. If it is present, return the "found"
+ prefix set. (Otherwise, return "None"'''
+ for cfg_prefix_set in have:
+ cfg_prefix_set_name = cfg_prefix_set.get('name', None)
+ if cfg_prefix_set_name and cfg_prefix_set_name == pfx_set_name:
+ return cfg_prefix_set
+
+ return None
+
+ def prefix_in_prefix_list_cfg(self, prefix, cfg_prefix_set):
+ '''Determine, based on the keys, if the "target" prefix specified by the "prefix"
+ input parameter is present in the currently configured prefix set specified
+ ty the "cfg_prefix_set" input parameter. Return "True" if the prifix is found,
+ or "False" if it isn't.'''
+ req_pfx = prefix.get("prefix", None)
+ req_seq = prefix.get("sequence", None)
+ req_ge = prefix.get("ge", None)
+ req_le = prefix.get("le", None)
+
+ cfg_prefix_list = cfg_prefix_set.get("prefixes", None)
+ if not cfg_prefix_list: # The configured prefix set has no prefix list
+ return False
+
+ for cfg_prefix in cfg_prefix_list:
+ cfg_pfx = cfg_prefix.get("prefix", None)
+ cfg_seq = cfg_prefix.get("sequence", None)
+ cfg_ge = cfg_prefix.get("ge", None)
+ cfg_le = cfg_prefix.get("le", None)
+
+ # Check for matching key attributes
+ if not (req_pfx and cfg_pfx and req_pfx == cfg_pfx):
+ continue
+ if not (req_seq and cfg_seq and req_seq == cfg_seq):
+ continue
+
+ # Check for ge match
+ if not req_ge:
+ if cfg_ge:
+ continue
+ else:
+ if not cfg_ge or req_ge != cfg_ge:
+ continue
+
+ # Check for le match
+ if not req_le:
+ if cfg_le:
+ continue
+ else:
+ if not cfg_le or req_le != cfg_le:
+ continue
+
+ # All key attributes match for this cfg_prefix
+ return True
+
+ # No matching configured prefixes were found in the prefix set.
+ return False
+
+ def set_ipaddress_net_attrs(self, prefix_val, conf_afi):
+ '''Create and return a dictionary containing the values for any prefix-related
+ attributes needed for handling of prefix configuration requests. NOTE: This
+ method should be replaced with use of the Python "ipaddress" module after
+ Ansible drops downward compatibility support for Python 2.7.'''
+
+ prefix_net = dict()
+ if conf_afi == 'ipv4':
+ prefix_net['max_prefixlen'] = 32
+ else: # Assuming IPv6 for this case
+ prefix_net['max_prefixlen'] = 128
+
+ prefix_net['prefixlen'] = int(prefix_val.split("/")[1])
+ return prefix_net
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
new file mode 100644
index 000000000..dfa65482f
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/radius_server/radius_server.py
@@ -0,0 +1,362 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_radius_server 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.module_utils.connection import ConnectionError
+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,
+ normalize_interface_name,
+)
+
+PATCH = 'patch'
+DELETE = 'delete'
+TEST_KEYS = [
+ {'host': {'name': ''}},
+]
+
+
+class Radius_server(ConfigBase):
+ """
+ The sonic_radius_server class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'radius_server',
+ ]
+
+ def __init__(self, module):
+ super(Radius_server, self).__init__(module)
+
+ def get_radius_server_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)
+ radius_server_facts = facts['ansible_network_resources'].get('radius_server')
+ if not radius_server_facts:
+ return []
+ return radius_server_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+ existing_radius_server_facts = self.get_radius_server_facts()
+ commands, requests = self.set_config(existing_radius_server_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_radius_server_facts = self.get_radius_server_facts()
+
+ result['before'] = existing_radius_server_facts
+ if result['changed']:
+ result['after'] = changed_radius_server_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_radius_server_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 and want.get('servers', None) and want['servers'].get('host', None):
+ normalize_interface_name(want['servers']['host'], self._module, 'source_interface')
+
+ have = existing_radius_server_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, 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, diff)
+ 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 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 = []
+ command = diff
+ requests = self.get_modify_radius_server_requests(command, have)
+ if command and len(requests) > 0:
+ commands = update_states([command], "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 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
+ """
+ # if want is none, then delete all the radius_serveri except admin
+ commands = []
+ if not want:
+ command = have
+ else:
+ command = want
+
+ requests = self.get_delete_radius_server_requests(command, have)
+
+ if command and len(requests) > 0:
+ commands = update_states([command], "deleted")
+
+ return commands, requests
+
+ def get_radius_global_payload(self, conf):
+ payload = {}
+ global_cfg = {}
+
+ if conf.get('auth_type', None):
+ global_cfg['auth-type'] = conf['auth_type']
+ if conf.get('key', None):
+ global_cfg['secret-key'] = conf['key']
+ if conf.get('timeout', None):
+ global_cfg['timeout'] = conf['timeout']
+
+ if global_cfg:
+ payload = {'openconfig-system:config': global_cfg}
+
+ return payload
+
+ def get_radius_global_ext_payload(self, conf):
+ payload = {}
+ global_ext_cfg = {}
+
+ if conf.get('nas_ip', None):
+ global_ext_cfg['nas-ip-address'] = conf['nas_ip']
+ if conf.get('retransmit', None):
+ global_ext_cfg['retransmit-attempts'] = conf['retransmit']
+ if conf.get('statistics', None):
+ global_ext_cfg['statistics'] = conf['statistics']
+
+ if global_ext_cfg:
+ payload = {'openconfig-aaa-radius-ext:config': global_ext_cfg}
+
+ return payload
+
+ def get_radius_server_payload(self, hosts):
+ payload = {}
+ servers_load = []
+ for host in hosts:
+ if host.get('name', None):
+ host_cfg = {'address': host['name']}
+ if host.get('auth_type', None):
+ host_cfg['auth-type'] = host['auth_type']
+ if host.get('priority', None):
+ host_cfg['priority'] = host['priority']
+ if host.get('vrf', None):
+ host_cfg['vrf'] = host['vrf']
+ if host.get('timeout', None):
+ host_cfg['timeout'] = host['timeout']
+
+ radius_port_key_cfg = {}
+ if host.get('port', None):
+ radius_port_key_cfg['auth-port'] = host['port']
+ if host.get('key', None):
+ radius_port_key_cfg['secret-key'] = host['key']
+ if host.get('retransmit', None):
+ radius_port_key_cfg['retransmit-attempts'] = host['retransmit']
+ if host.get('source_interface', None):
+ radius_port_key_cfg['openconfig-aaa-radius-ext:source-interface'] = host['source_interface']
+
+ if radius_port_key_cfg:
+ consolidated_load = {'address': host['name']}
+ consolidated_load['config'] = host_cfg
+ consolidated_load['radius'] = {'config': radius_port_key_cfg}
+ servers_load.append(consolidated_load)
+
+ if servers_load:
+ payload = {'openconfig-system:servers': {'server': servers_load}}
+
+ return payload
+
+ def get_modify_servers_request(self, command):
+ request = None
+
+ hosts = []
+ if command.get('servers', None) and command['servers'].get('host', None):
+ hosts = command['servers']['host']
+ if hosts:
+ url = 'data/openconfig-system:system/aaa/server-groups/server-group=RADIUS/servers'
+ payload = self.get_radius_server_payload(hosts)
+ if payload:
+ request = {'path': url, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_modify_global_config_request(self, conf):
+ request = None
+
+ url = 'data/openconfig-system:system/aaa/server-groups/server-group=RADIUS/config'
+ payload = self.get_radius_global_payload(conf)
+ if payload:
+ request = {'path': url, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_modify_global_ext_config_request(self, conf):
+ request = None
+
+ url = 'data/openconfig-system:system/aaa/server-groups/server-group=RADIUS/openconfig-aaa-radius-ext:radius/config'
+ payload = self.get_radius_global_ext_payload(conf)
+ if payload:
+ request = {'path': url, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_modify_radius_server_requests(self, command, have):
+ requests = []
+ if not command:
+ return requests
+
+ request = self.get_modify_global_config_request(command)
+ if request:
+ requests.append(request)
+
+ request = self.get_modify_global_ext_config_request(command)
+ if request:
+ requests.append(request)
+
+ request = self.get_modify_servers_request(command)
+ if request:
+ requests.append(request)
+
+ return requests
+
+ def get_delete_global_ext_params(self, conf, match):
+
+ requests = []
+
+ url = 'data/openconfig-system:system/aaa/server-groups/server-group=RADIUS/openconfig-aaa-radius-ext:radius/config/'
+ if conf.get('nas_ip', None) and match.get('nas_ip', None):
+ requests.append({'path': url + 'nas-ip-address', 'method': DELETE})
+ if conf.get('retransmit', None) and match.get('retransmit', None):
+ requests.append({'path': url + 'retransmit-attempts', 'method': DELETE})
+ if conf.get('statistics', None) and match.get('statistics', None):
+ requests.append({'path': url + 'statistics', 'method': DELETE})
+
+ return requests
+
+ def get_delete_global_params(self, conf, match):
+
+ requests = []
+
+ url = 'data/openconfig-system:system/aaa/server-groups/server-group=RADIUS/config/'
+ if conf.get('auth_type', None) and match.get('auth_type', None) and match['auth_type'] != 'pap':
+ requests.append({'path': url + 'auth-type', 'method': DELETE})
+ if conf.get('key', None) and match.get('key', None):
+ requests.append({'path': url + 'secret-key', 'method': DELETE})
+ if conf.get('timeout', None) and match.get('timeout', None) and match['timeout'] != 5:
+ requests.append({'path': url + 'timeout', 'method': DELETE})
+
+ return requests
+
+ def get_delete_servers(self, command, have):
+ requests = []
+ url = 'data/openconfig-system:system/aaa/server-groups/server-group=RADIUS/servers/server='
+
+ mat_hosts = []
+ if have.get('servers', None) and have['servers'].get('host', None):
+ mat_hosts = have['servers']['host']
+
+ if command.get('servers', None):
+ if command['servers'].get('host', None):
+ hosts = command['servers']['host']
+ else:
+ hosts = mat_hosts
+
+ if mat_hosts and hosts:
+ for host in hosts:
+ if next((m_host for m_host in mat_hosts if m_host['name'] == host['name']), None):
+ requests.append({'path': url + host['name'], 'method': DELETE})
+
+ return requests
+
+ def get_delete_radius_server_requests(self, command, have):
+ requests = []
+ if not command:
+ return requests
+
+ requests.extend(self.get_delete_global_params(command, have))
+ requests.extend(self.get_delete_global_ext_params(command, have))
+ requests.extend(self.get_delete_servers(command, have))
+
+ return requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/static_routes/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/static_routes/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/static_routes/__init__.py
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
new file mode 100644
index 000000000..047357470
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/static_routes/static_routes.py
@@ -0,0 +1,344 @@
+#
+# -*- 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_static_routes 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,
+)
+
+network_instance_path = '/data/openconfig-network-instance:network-instances/network-instance'
+protocol_static_routes_path = 'protocols/protocol=STATIC,static/static-routes'
+
+PATCH = 'patch'
+DELETE = 'delete'
+TEST_KEYS = [
+ {'config': {'vrf_name': ''}},
+ {'static_list': {'prefix': ''}},
+ {'next_hops': {'index': ''}},
+]
+
+
+class Static_routes(ConfigBase):
+ """
+ The sonic_static_routes class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'static_routes',
+ ]
+
+ def __init__(self, module):
+ super(Static_routes, self).__init__(module)
+
+ def get_static_routes_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)
+ static_routes_facts = facts['ansible_network_resources'].get('static_routes')
+ if not static_routes_facts:
+ return []
+ return static_routes_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = []
+ commands = []
+ existing_static_routes_facts = self.get_static_routes_facts()
+ commands, requests = self.set_config(existing_static_routes_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_static_routes_facts = self.get_static_routes_facts()
+
+ result['before'] = existing_static_routes_facts
+ if result['changed']:
+ result['after'] = changed_static_routes_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_static_routes_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_static_routes_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 == 'deleted':
+ commands, requests = self._state_deleted(want, have, diff)
+ elif state == 'merged':
+ commands, requests = self._state_merged(want, have, diff)
+ 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_modify_static_routes_requests(commands, have)
+
+ 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
+ """
+ is_delete_all = False
+ # if want is none, then delete ALL
+ if not want:
+ commands = have
+ is_delete_all = True
+ else:
+ commands = want
+
+ requests = self.get_delete_static_routes_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_static_routes_requests(self, commands, have):
+ requests = []
+
+ if not commands:
+ return requests
+
+ for conf in commands:
+ vrf_name = conf.get('vrf_name', None)
+ static_list = conf.get('static_list', [])
+ for static in static_list:
+ prefix = static.get('prefix', None)
+ next_hops = static.get('next_hops', [])
+ if next_hops:
+ for next_hop in next_hops:
+ requests.append(self.get_modify_static_route_request(vrf_name, prefix, next_hop))
+
+ return requests
+
+ def get_modify_static_route_request(self, vrf_name, prefix, next_hop):
+ request = None
+ next_hop_cfg = {}
+ index = next_hop.get('index', {})
+ blackhole = index.get('blackhole', None)
+ interface = index.get('interface', None)
+ nexthop_vrf = index.get('nexthop_vrf', None)
+ next_hop_attr = index.get('next_hop', None)
+ metric = next_hop.get('metric', None)
+ track = next_hop.get('track', None)
+ tag = next_hop.get('tag', None)
+ idx = self.generate_index(index)
+ if idx:
+ next_hop_cfg['index'] = idx
+ if blackhole:
+ next_hop_cfg['blackhole'] = blackhole
+ if nexthop_vrf:
+ next_hop_cfg['network-instance'] = nexthop_vrf
+ if next_hop:
+ next_hop_cfg['next-hop'] = next_hop_attr
+ if metric:
+ next_hop_cfg['metric'] = metric
+ if track:
+ next_hop_cfg['track'] = track
+ if tag:
+ next_hop_cfg['tag'] = tag
+
+ url = '%s=%s/%s' % (network_instance_path, vrf_name, protocol_static_routes_path)
+ next_hops_cfg = {'next-hop': [{'index': idx, 'config': next_hop_cfg}]}
+ if interface:
+ next_hops_cfg['next-hop'][0]['interface-ref'] = {'config': {'interface': interface}}
+ payload = {'openconfig-network-instance:static-routes': {'static': [{'prefix': prefix, 'config': {'prefix': prefix}, 'next-hops': next_hops_cfg}]}}
+ request = {'path': url, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def generate_index(self, index):
+ idx = None
+ blackhole = index.get('blackhole', None)
+ interface = index.get('interface', None)
+ nexthop_vrf = index.get('nexthop_vrf', None)
+ next_hop = index.get('next_hop', None)
+
+ if blackhole is True:
+ idx = 'DROP'
+ else:
+ if interface:
+ if not next_hop and not nexthop_vrf:
+ idx = interface
+ elif next_hop and not nexthop_vrf:
+ idx = interface + '_' + next_hop
+ elif nexthop_vrf and not next_hop:
+ idx = interface + '_' + nexthop_vrf
+ else:
+ idx = interface + '_' + next_hop + '_' + nexthop_vrf
+ else:
+ if next_hop and not nexthop_vrf:
+ idx = next_hop
+ elif next_hop and nexthop_vrf:
+ idx = next_hop + '_' + nexthop_vrf
+
+ return idx
+
+ def get_delete_static_routes_requests(self, commands, have, is_delete_all):
+ requests = []
+ if is_delete_all:
+ for cmd in commands:
+ vrf_name = cmd.get('vrf_name', None)
+ if vrf_name:
+ requests.append(self.get_delete_static_routes_for_vrf(vrf_name))
+ else:
+ for cmd in commands:
+ vrf_name = cmd.get('vrf_name', None)
+ static_list = cmd.get('static_list', [])
+ for cfg in have:
+ cfg_vrf_name = cfg.get('vrf_name', None)
+ if vrf_name == cfg_vrf_name:
+ if not static_list:
+ requests.append(self.get_delete_static_routes_for_vrf(vrf_name))
+ else:
+ for static in static_list:
+ prefix = static.get('prefix', None)
+ next_hops = static.get('next_hops', [])
+ cfg_static_list = cfg.get('static_list', [])
+ for cfg_static in cfg_static_list:
+ cfg_prefix = cfg_static.get('prefix', None)
+ if prefix == cfg_prefix:
+ if prefix and not next_hops:
+ requests.append(self.get_delete_static_routes_prefix_request(vrf_name, prefix))
+ else:
+ for next_hop in next_hops:
+ index = next_hop.get('index', {})
+ idx = self.generate_index(index)
+ metric = next_hop.get('metric', None)
+ track = next_hop.get('track', None)
+ tag = next_hop.get('tag', None)
+
+ cfg_next_hops = cfg_static.get('next_hops', [])
+ if cfg_next_hops:
+ for cfg_next_hop in cfg_next_hops:
+ cfg_index = cfg_next_hop.get('index', {})
+ cfg_idx = self.generate_index(cfg_index)
+ if idx == cfg_idx:
+ cfg_metric = cfg_next_hop.get('metric', None)
+ cfg_track = cfg_next_hop.get('track', None)
+ cfg_tag = cfg_next_hop.get('tag', None)
+ if not metric and not track and not tag:
+ requests.append(self.get_delete_static_routes_next_hop_request(vrf_name, prefix, idx))
+ else:
+ if metric == cfg_metric:
+ requests.append(self.get_delete_next_hop_config_attr_request(vrf_name, prefix, idx,
+ 'metric'))
+ if track == cfg_track:
+ requests.append(self.get_delete_next_hop_config_attr_request(vrf_name, prefix, idx,
+ 'track'))
+ if tag == cfg_tag:
+ requests.append(self.get_delete_next_hop_config_attr_request(vrf_name, prefix, idx, 'tag'))
+
+ return requests
+
+ def get_delete_static_routes_for_vrf(self, vrf_name):
+ url = '%s=%s/%s' % (network_instance_path, vrf_name, protocol_static_routes_path)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_static_routes_prefix_request(self, vrf_name, prefix):
+ prefix = prefix.replace('/', '%2F')
+ url = '%s=%s/%s/static=%s' % (network_instance_path, vrf_name, protocol_static_routes_path, prefix)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_static_routes_next_hop_request(self, vrf_name, prefix, index):
+ prefix = prefix.replace('/', '%2F')
+ url = '%s=%s/%s/static=%s' % (network_instance_path, vrf_name, protocol_static_routes_path, prefix)
+ url += '/next-hops/next-hop=%s' % (index)
+ request = {'path': url, 'method': DELETE}
+
+ return request
+
+ def get_delete_next_hop_config_attr_request(self, vrf_name, prefix, index, attr):
+ prefix = prefix.replace('/', '%2F')
+ url = '%s=%s/%s/static=%s' % (network_instance_path, vrf_name, protocol_static_routes_path, prefix)
+ url += '/next-hops/next-hop=%s/config/%s' % (index, attr)
+ request = {'path': url, 'method': DELETE}
+
+ return request
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/system/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/system/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/system/__init__.py
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
new file mode 100644
index 000000000..21d575a1f
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/system/system.py
@@ -0,0 +1,294 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 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_system 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.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+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,
+ edit_config
+)
+
+PATCH = 'patch'
+DELETE = 'delete'
+
+
+class System(ConfigBase):
+ """
+ The sonic_system class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'system',
+ ]
+
+ def __init__(self, module):
+ super(System, self).__init__(module)
+
+ def get_system_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)
+ system_facts = facts['ansible_network_resources'].get('system')
+ if not system_facts:
+ return []
+ return system_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_system_facts = self.get_system_facts()
+ commands, requests = self.set_config(existing_system_facts)
+ if commands and len(requests) > 0:
+ if not self._module.check_mode:
+ self.edit_config(requests)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_system_facts = self.get_system_facts()
+
+ result['before'] = existing_system_facts
+ if result['changed']:
+ result['after'] = changed_system_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def edit_config(self, requests):
+ try:
+ response = edit_config(self._module, to_request(self._module, requests))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ def set_config(self, existing_system_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_system_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 = self._state_deleted(want, have)
+ elif state == 'merged':
+ diff = get_diff(want, have)
+ commands = self._state_merged(want, have, diff)
+ return commands
+
+ 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
+ """
+ requests = []
+ commands = []
+ if diff:
+ 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):
+ """ 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 = []
+ new_have = self.remove_default_entries(have)
+ if not want:
+ if have:
+ requests = self.get_delete_all_system_request(new_have)
+ if len(requests) > 0:
+ commands = update_states(have, "deleted")
+ else:
+ want = utils.remove_empties(want)
+ d_diff = get_diff(want, new_have, is_skeleton=True)
+ diff_want = get_diff(want, d_diff, is_skeleton=True)
+ if diff_want:
+ requests = self.get_delete_all_system_request(diff_want)
+ if len(requests) > 0:
+ commands = update_states(diff_want, "deleted")
+ return commands, requests
+
+ def get_create_system_request(self, want, commands):
+ requests = []
+ host_path = 'data/openconfig-system:system/config'
+ method = PATCH
+ hostname_payload = self.build_create_hostname_payload(commands)
+ if hostname_payload:
+ request = {'path': host_path, 'method': method, 'data': hostname_payload}
+ requests.append(request)
+ name_path = 'data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/intf_naming_mode'
+ name_payload = self.build_create_name_payload(commands)
+ if name_payload:
+ request = {'path': name_path, 'method': method, 'data': name_payload}
+ requests.append(request)
+ anycast_path = 'data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST/'
+ anycast_payload = self.build_create_anycast_payload(commands)
+ if anycast_payload:
+ request = {'path': anycast_path, 'method': method, 'data': anycast_payload}
+ requests.append(request)
+ return requests
+
+ def build_create_hostname_payload(self, commands):
+ payload = {"openconfig-system:config": {}}
+ if "hostname" in commands and commands["hostname"]:
+ payload['openconfig-system:config'].update({"hostname": commands["hostname"]})
+ return payload
+
+ def build_create_name_payload(self, commands):
+ payload = {}
+ if "interface_naming" in commands and commands["interface_naming"]:
+ payload.update({'sonic-device-metadata:intf_naming_mode': commands["interface_naming"]})
+ return payload
+
+ def build_create_anycast_payload(self, commands):
+ payload = {}
+ if "anycast_address" in commands and commands["anycast_address"]:
+ payload = {"sonic-sag:SAG_GLOBAL_LIST": []}
+ temp = {}
+ if "ipv4" in commands["anycast_address"] and commands["anycast_address"]["ipv4"]:
+ temp.update({'IPv4': "enable"})
+ if "ipv4" in commands["anycast_address"] and not commands["anycast_address"]["ipv4"]:
+ temp.update({'IPv4': "disable"})
+ if "ipv6" in commands["anycast_address"] and commands["anycast_address"]["ipv6"]:
+ temp.update({'IPv6': "enable"})
+ if "ipv6" in commands["anycast_address"] and not commands["anycast_address"]["ipv6"]:
+ temp.update({'IPv6': "disable"})
+ if "mac_address" in commands["anycast_address"] and commands["anycast_address"]["mac_address"]:
+ temp.update({'gwmac': commands["anycast_address"]["mac_address"]})
+ if temp:
+ temp.update({"table_distinguisher": "IP"})
+ payload["sonic-sag:SAG_GLOBAL_LIST"].append(temp)
+ return payload
+
+ def remove_default_entries(self, data):
+ new_data = {}
+ if not data:
+ return new_data
+ else:
+ hostname = data.get('hostname', None)
+ if hostname != "sonic":
+ new_data["hostname"] = hostname
+ intf_name = data.get('interface_naming', None)
+ if intf_name != "native":
+ new_data["interface_naming"] = intf_name
+ new_anycast = {}
+ anycast = data.get('anycast_address', None)
+ if anycast:
+ ipv4 = anycast.get("ipv4", None)
+ if ipv4 is not True:
+ new_anycast["ipv4"] = ipv4
+ ipv6 = anycast.get("ipv6", None)
+ if ipv6 is not True:
+ new_anycast["ipv6"] = ipv6
+ mac = anycast.get("mac_address", None)
+ if mac is not None:
+ new_anycast["mac_address"] = mac
+ new_data["anycast_address"] = new_anycast
+ return new_data
+
+ def get_delete_all_system_request(self, have):
+ requests = []
+ if "hostname" in have:
+ request = self.get_hostname_delete_request()
+ requests.append(request)
+ if "interface_naming" in have:
+ request = self.get_intfname_delete_request()
+ requests.append(request)
+ if "anycast_address" in have:
+ request = self.get_anycast_delete_request(have["anycast_address"])
+ requests.extend(request)
+ return requests
+
+ def get_hostname_delete_request(self):
+ path = 'data/openconfig-system:system/config/'
+ method = PATCH
+ payload = {"openconfig-system:config": {}}
+ payload['openconfig-system:config'].update({"hostname": "sonic"})
+ request = {'path': path, 'method': method, 'data': payload}
+ return request
+
+ def get_intfname_delete_request(self):
+ path = 'data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost/intf_naming_mode'
+ method = DELETE
+ request = {'path': path, 'method': method}
+ return request
+
+ def get_anycast_delete_request(self, anycast):
+ requests = []
+ if "ipv4" in anycast:
+ path = 'data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST=IP/IPv4'
+ method = DELETE
+ request = {'path': path, 'method': method}
+ requests.append(request)
+ if "ipv6" in anycast:
+ path = 'data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST=IP/IPv6'
+ method = DELETE
+ request = {'path': path, 'method': method}
+ requests.append(request)
+ if "mac_address" in anycast:
+ path = 'data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST=IP/gwmac'
+ method = DELETE
+ request = {'path': path, 'method': method}
+ requests.append(request)
+ return requests
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
new file mode 100644
index 000000000..498fcbe28
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/tacacs_server/tacacs_server.py
@@ -0,0 +1,318 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_tacacs_server 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.module_utils.connection import ConnectionError
+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_normalize_interface_name,
+)
+
+PATCH = 'patch'
+DELETE = 'delete'
+TEST_KEYS = [
+ {'host': {'name': ''}},
+]
+
+
+class Tacacs_server(ConfigBase):
+ """
+ The sonic_tacacs_server class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'tacacs_server',
+ ]
+
+ def __init__(self, module):
+ super(Tacacs_server, self).__init__(module)
+
+ def get_tacacs_server_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)
+ tacacs_server_facts = facts['ansible_network_resources'].get('tacacs_server')
+ if not tacacs_server_facts:
+ return []
+ return tacacs_server_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+ existing_tacacs_server_facts = self.get_tacacs_server_facts()
+ commands, requests = self.set_config(existing_tacacs_server_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_tacacs_server_facts = self.get_tacacs_server_facts()
+
+ result['before'] = existing_tacacs_server_facts
+ if result['changed']:
+ result['after'] = changed_tacacs_server_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_tacacs_server_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 and want.get('source_interface', None):
+ want['source_interface'] = get_normalize_interface_name(want['source_interface'], self._module)
+
+ have = existing_tacacs_server_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, 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, diff)
+ 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 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 = []
+ command = diff
+ requests = self.get_modify_tacacs_server_requests(command, have)
+ if command and len(requests) > 0:
+ commands = update_states([command], "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 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
+ """
+ # if want is none, then delete all the tacacs_serveri except admin
+ commands = []
+ if not want:
+ command = have
+ else:
+ command = want
+
+ requests = self.get_delete_tacacs_server_requests(command, have)
+
+ if command and len(requests) > 0:
+ commands = update_states([command], "deleted")
+
+ return commands, requests
+
+ def get_tacacs_global_payload(self, conf):
+ payload = {}
+ global_cfg = {}
+
+ if conf.get('auth_type', None):
+ global_cfg['auth-type'] = conf['auth_type']
+ if conf.get('key', None):
+ global_cfg['secret-key'] = conf['key']
+ if conf.get('source_interface', None):
+ global_cfg['source-interface'] = conf['source_interface']
+ if conf.get('timeout', None):
+ global_cfg['timeout'] = conf['timeout']
+
+ if global_cfg:
+ payload = {'openconfig-system:config': global_cfg}
+
+ return payload
+
+ def get_tacacs_server_payload(self, hosts):
+ payload = {}
+ servers_load = []
+ for host in hosts:
+ if host.get('name', None):
+ host_cfg = {'address': host['name']}
+ if host.get('auth_type', None):
+ host_cfg['auth-type'] = host['auth_type']
+ if host.get('priority', None):
+ host_cfg['priority'] = host['priority']
+ if host.get('vrf', None):
+ host_cfg['vrf'] = host['vrf']
+ if host.get('timeout', None):
+ host_cfg['timeout'] = host['timeout']
+
+ tacacs_port_key_cfg = {}
+ if host.get('port', None):
+ tacacs_port_key_cfg['port'] = host['port']
+ if host.get('key', None):
+ tacacs_port_key_cfg['secret-key'] = host['key']
+
+ if tacacs_port_key_cfg:
+ consolidated_load = {'address': host['name']}
+ consolidated_load['config'] = host_cfg
+ consolidated_load['tacacs'] = {'config': tacacs_port_key_cfg}
+ servers_load.append(consolidated_load)
+
+ if servers_load:
+ payload = {'openconfig-system:servers': {'server': servers_load}}
+
+ return payload
+
+ def get_modify_servers_request(self, command):
+ request = None
+
+ hosts = []
+ if command.get('servers', None) and command['servers'].get('host', None):
+ hosts = command['servers']['host']
+ if hosts:
+ url = 'data/openconfig-system:system/aaa/server-groups/server-group=TACACS/servers'
+ payload = self.get_tacacs_server_payload(hosts)
+ if payload:
+ request = {'path': url, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_modify_global_config_request(self, conf):
+ request = None
+
+ url = 'data/openconfig-system:system/aaa/server-groups/server-group=TACACS/config'
+ payload = self.get_tacacs_global_payload(conf)
+ if payload:
+ request = {'path': url, 'method': PATCH, 'data': payload}
+
+ return request
+
+ def get_modify_tacacs_server_requests(self, command, have):
+ requests = []
+ if not command:
+ return requests
+
+ request = self.get_modify_global_config_request(command)
+ if request:
+ requests.append(request)
+
+ request = self.get_modify_servers_request(command)
+ if request:
+ requests.append(request)
+
+ return requests
+
+ def get_delete_global_params(self, conf, match):
+
+ requests = []
+
+ url = 'data/openconfig-system:system/aaa/server-groups/server-group=TACACS/config/'
+ if conf.get('auth_type', None) and match.get('auth_type', None) and match['auth_type'] != 'pap':
+ requests.append({'path': url + 'auth-type', 'method': DELETE})
+ if conf.get('key', None) and match.get('key', None):
+ requests.append({'path': url + 'secret-key', 'method': DELETE})
+ if conf.get('source_interface', None) and match.get('source_interface', None):
+ requests.append({'path': url + 'source-interface', 'method': DELETE})
+ if conf.get('timeout', None) and match.get('timeout', None) and match['timeout'] != 5:
+ requests.append({'path': url + 'timeout', 'method': DELETE})
+
+ return requests
+
+ def get_delete_servers(self, command, have):
+ requests = []
+ url = 'data/openconfig-system:system/aaa/server-groups/server-group=TACACS/servers/server='
+
+ mat_hosts = []
+ if have.get('servers', None) and have['servers'].get('host', None):
+ mat_hosts = have['servers']['host']
+
+ hosts = []
+ if command.get('servers', None):
+ if command['servers'].get('host', None):
+ hosts = command['servers']['host']
+ else:
+ hosts = mat_hosts
+
+ if mat_hosts and hosts:
+ for host in hosts:
+ if next((m_host for m_host in mat_hosts if m_host['name'] == host['name']), None):
+ requests.append({'path': url + host['name'], 'method': DELETE})
+
+ return requests
+
+ def get_delete_tacacs_server_requests(self, command, have):
+ requests = []
+ if not command:
+ return requests
+
+ requests.extend(self.get_delete_global_params(command, have))
+ requests.extend(self.get_delete_servers(command, have))
+
+ return requests
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
new file mode 100644
index 000000000..73398cf74
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/users/users.py
@@ -0,0 +1,299 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic_users 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 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,
+)
+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 (
+ dict_to_set,
+ update_states,
+ get_diff,
+)
+from ansible.module_utils.connection import ConnectionError
+
+PATCH = 'patch'
+DELETE = 'delete'
+
+
+class Users(ConfigBase):
+ """
+ The sonic_users class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'users',
+ ]
+
+ def __init__(self, module):
+ super(Users, self).__init__(module)
+
+ def get_users_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)
+ users_facts = facts['ansible_network_resources'].get('users')
+ if not users_facts:
+ return []
+ return users_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+ existing_users_facts = self.get_users_facts()
+ commands, requests = self.set_config(existing_users_facts)
+ auth_error = False
+ 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:
+ try:
+ json_obj = json.loads(str(exc).replace("'", '"'))
+ if json_obj and type(json_obj) is dict and 401 == json_obj['code']:
+ auth_error = True
+ warnings.append("Unable to get after configs as password got changed for current user")
+ else:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ except Exception as err:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+ result['changed'] = True
+ result['commands'] = commands
+
+ changed_users_facts = []
+ if not auth_error:
+ changed_users_facts = self.get_users_facts()
+
+ result['before'] = existing_users_facts
+ if result['changed']:
+ result['after'] = changed_users_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_users_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_users_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 = []
+
+ 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)
+
+ diff = []
+ for cfg in new_diff:
+ match = next((w_cfg for w_cfg in want if w_cfg['name'] == cfg['name']), None)
+ if match:
+ diff.append(match)
+
+ for cfg in want:
+ if cfg['password'] and cfg['update_password'] == 'always':
+ d_match = next((d_cfg for d_cfg in diff if d_cfg['name'] == cfg['name']), None)
+ if d_match is None:
+ diff.append(cfg)
+
+ 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, diff)
+ 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 obj_in_have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current 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, "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 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
+ """
+ # if want is none, then delete all the usersi except admin
+ if not want:
+ commands = have
+ else:
+ commands = want
+
+ requests = self.get_delete_users_requests(commands, have)
+
+ if commands and len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def get_pwd(self, pw):
+ clear_pwd = hashed_pwd = ""
+ pwd = pw.replace("\\", "")
+ if pwd[:3] == '$6$':
+ hashed_pwd = pwd
+ else:
+ clear_pwd = pwd
+ return clear_pwd, hashed_pwd
+
+ def get_single_user_payload(self, name, role, password, update_pass, match):
+ user_cfg = {'username': name}
+ if not role and match:
+ role = match['role']
+
+ if not password and match:
+ password = match['password']
+
+ if role:
+ user_cfg['role'] = role
+
+ if password:
+ clear_pwd, hashed_pwd = self.get_pwd(password)
+ user_cfg['password'] = clear_pwd
+ user_cfg['password-hashed'] = hashed_pwd
+
+ pay_load = {'openconfig-system:user': [{'username': name, 'config': user_cfg}]}
+ return pay_load
+
+ def get_modify_single_user_request(self, conf, match):
+ request = None
+ name = conf.get('name', None)
+ role = conf.get('role', None)
+ password = conf.get('password', None)
+ update_pass = conf.get('update_password', None)
+ if role or (password and update_pass == 'always'):
+ url = 'data/openconfig-system:system/aaa/authentication/users/user=%s' % (name)
+ payload = self.get_single_user_payload(name, role, password, update_pass, match)
+ request = {'path': url, 'method': PATCH, 'data': payload}
+ return request
+
+ def get_modify_users_requests(self, commands, have):
+ requests = []
+ if not commands:
+ return requests
+
+ for conf in commands:
+ match = next((cfg for cfg in have if cfg['name'] == conf['name']), None)
+ req = self.get_modify_single_user_request(conf, match)
+ if req:
+ requests.append(req)
+ return requests
+
+ def get_new_users(self, want, have):
+ new_users = []
+ for user in want:
+ if not next((h_user for h_user in have if h_user['name'] == user['name']), None):
+ new_users.append(user)
+ return new_users
+
+ def validate_new_users(self, want, have):
+ new_users = self.get_new_users(want, have)
+ invalid_users = []
+ for user in new_users:
+ params = []
+ if not user['role']:
+ params.append('role')
+ if not user['password']:
+ params.append('password')
+ if params:
+ invalid_users.append({user['name']: params})
+ if invalid_users:
+ err_msg = "Missing parameter(s) for new users! " + str(invalid_users)
+ self._module.fail_json(msg=err_msg, code=513)
+
+ def get_delete_users_requests(self, commands, have):
+ requests = []
+ if not commands:
+ return requests
+
+ # Skip the asmin user in 'deleted' state. we cannot delete all users
+ admin_usr = None
+
+ for conf in commands:
+ # Skip the asmin user in 'deleted' state. we cannot delete all users
+ if conf['name'] == 'admin':
+ admin_usr = conf
+ continue
+ match = next((cfg for cfg in have if cfg['name'] == conf['name']), None)
+ if match:
+ url = 'data/openconfig-system:system/aaa/authentication/users/user=%s' % (conf['name'])
+ requests.append({'path': url, 'method': DELETE})
+
+ if admin_usr:
+ commands.remove(admin_usr)
+ return requests
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
new file mode 100644
index 000000000..404051074
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vlans/vlans.py
@@ -0,0 +1,265 @@
+#
+# -*- 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_vlans 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 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,
+)
+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.utils.interfaces_util import (
+ build_interfaces_create_request,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils._text import to_native
+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': ''}},
+]
+
+
+class Vlans(ConfigBase):
+ """
+ The sonic_vlans class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'vlans',
+ ]
+
+ def __init__(self, module):
+ super(Vlans, self).__init__(module)
+
+ def get_vlans_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)
+ vlans_facts = facts['ansible_network_resources'].get('vlans')
+ if not vlans_facts:
+ return []
+ return vlans_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+
+ existing_vlans_facts = self.get_vlans_facts()
+ commands, requests = self.set_config(existing_vlans_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_vlans_facts = self.get_vlans_facts()
+
+ result['before'] = existing_vlans_facts
+ if result['changed']:
+ result['after'] = changed_vlans_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_vlans_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_vlans_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 method works on dict, so creating temp dict
+ 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, diff)
+ 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
+ """
+ return self._state_merged(want, have, diff)
+
+ 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
+ """
+ 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"))
+
+ 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"))
+
+ return commands, ret_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 at position-0
+ Requests necessary to merge to the current configuration
+ at position-1
+ """
+ commands = update_states(diff, "merged")
+ requests = self.get_create_vlans_requests(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 = list()
+ # if want is none, then delete all the vlans
+ if not want:
+ commands = have
+ else: # delete specific vlans
+ commands = get_diff(want, diff, TEST_KEYS)
+
+ requests = self.get_delete_vlans_requests(commands)
+ commands = update_states(commands, "deleted")
+ return commands, requests
+
+ def get_delete_vlans_requests(self, configs):
+ requests = []
+ if not configs:
+ return requests
+ # Create URL and payload
+ url = "data/openconfig-interfaces:interfaces/interface=Vlan{}"
+ method = "DELETE"
+ for vlan in configs:
+ vlan_id = vlan.get("vlan_id")
+ description = vlan.get("description")
+ if description:
+ path = self.get_delete_vlan_config_attr(vlan_id, "description")
+ else:
+ path = url.format(vlan_id)
+
+ request = {"path": path,
+ "method": method,
+ }
+ requests.append(request)
+
+ return requests
+
+ def get_delete_vlan_config_attr(self, vlan_id, attr_name):
+ url = "data/openconfig-interfaces:interfaces/interface=Vlan{}/config/{}"
+ path = url.format(vlan_id, attr_name)
+
+ return path
+
+ def get_create_vlans_requests(self, configs):
+ requests = []
+ if not configs:
+ return requests
+ for vlan in configs:
+ vlan_id = vlan.get("vlan_id")
+ interface_name = "Vlan" + str(vlan_id)
+ description = vlan.get("description", None)
+ request = build_interfaces_create_request(interface_name=interface_name)
+ requests.append(request)
+ if description:
+ requests.append(self.get_modify_vlan_config_attr(interface_name, 'description', description))
+
+ return requests
+
+ def get_modify_vlan_config_attr(self, intf_name, attr_name, attr_value):
+ url = "data/openconfig-interfaces:interfaces/interface={}/config"
+ payload = {"openconfig-interfaces:config": {"name": intf_name, attr_name: attr_value}}
+ method = "PATCH"
+ request = {"path": url.format(intf_name), "method": method, "data": payload}
+
+ return request
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
new file mode 100644
index 000000000..83deb0ecb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vrfs/vrfs.py
@@ -0,0 +1,303 @@
+#
+# -*- 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_vrfs 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,
+ normalize_interface_name
+)
+from ansible.module_utils.connection import ConnectionError
+
+PATCH = 'patch'
+DELETE = 'DELETE'
+TEST_KEYS = [
+ {'interfaces': {'name': ''}},
+]
+
+
+class Vrfs(ConfigBase):
+ """
+ The sonic_vrfs class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'vrfs',
+ ]
+
+ delete_all_flag = False
+
+ def __init__(self, module):
+ super(Vrfs, self).__init__(module)
+
+ def get_vrf_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)
+ vrf_interfaces_facts = facts['ansible_network_resources'].get('vrfs')
+ if not vrf_interfaces_facts:
+ return []
+ return vrf_interfaces_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_vrf_interfaces_facts = self.get_vrf_facts()
+ commands, requests = self.set_config(existing_vrf_interfaces_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_vrf_interfaces_facts = self.get_vrf_facts()
+
+ result['before'] = existing_vrf_interfaces_facts
+ if result['changed']:
+ result['after'] = changed_vrf_interfaces_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_vrf_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']
+ have = existing_vrf_interfaces_facts
+ if want is None:
+ want = []
+
+ for each in want:
+ if each.get("members", None):
+ interfaces = each["members"].get("interfaces", None)
+ if interfaces:
+ interfaces = normalize_interface_name(interfaces, self._module)
+ 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)
+
+ if state == 'deleted':
+ commands, requests = self._state_deleted(want, have)
+ elif state == 'merged':
+ commands, requests = self._state_merged(want, have, diff)
+
+ 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 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 = []
+ if commands:
+ requests = self.get_create_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 obj_in_have: the current configuration as a dictionary
+ :param interface_type: interface type
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ # if want is none, then delete all the vrfs
+ if not want:
+ commands = have
+ self.delete_all_flag = True
+ else:
+ commands = want
+ self.delete_all_flag = False
+
+ requests = []
+ if commands:
+ requests = self.get_delete_vrf_interface_requests(commands, have, want)
+
+ if len(requests) > 0:
+ commands = update_states(commands, "deleted")
+ else:
+ commands = []
+
+ return commands, requests
+
+ def get_delete_vrf_interface_requests(self, configs, have, want):
+ requests = []
+ if not configs:
+ return requests
+
+ # Create URL and payload
+ method = DELETE
+ for conf in configs:
+ name = conf['name']
+ empty_flag = False
+ members = conf.get('members', None)
+ if members:
+ interfaces = members.get('interfaces', None)
+ if members is None:
+ empty_flag = True
+ elif members is not None and interfaces is None:
+ empty_flag = True
+ matched = next((have_cfg for have_cfg in have if have_cfg['name'] == name), None)
+ if not matched:
+ continue
+
+ # if members are not mentioned delet the vrf name
+ if (self._module.params['state'] == 'deleted' and self.delete_all_flag) or 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)
+
+ return requests
+
+ def get_create_requests(self, configs, have):
+ requests = []
+ if not configs:
+ return requests
+
+ requests_vrf = self.get_create_vrf_requests(configs, have)
+ if requests_vrf:
+ requests.extend(requests_vrf)
+
+ requests_vrf_intf = self.get_create_vrf_interface_requests(configs, have)
+ if requests_vrf_intf:
+ requests.extend(requests_vrf_intf)
+ return requests
+
+ def get_create_vrf_requests(self, configs, have):
+ requests = []
+ if not configs:
+ return requests
+ # Create URL and payload
+ method = PATCH
+ for conf in configs:
+ if conf.get("name", None):
+ name = conf["name"]
+ matched = next((have_cfg for have_cfg in have if have_cfg['name'] == name), None)
+ if not matched:
+ url = 'data/openconfig-network-instance:network-instances'
+ payload = self.build_create_vrf_payload(conf)
+ request = {"path": url, "method": method, "data": payload}
+ requests.append(request)
+ return requests
+
+ def get_create_vrf_interface_requests(self, configs, have):
+ requests = []
+ if not configs:
+ return requests
+
+ # Create URL and payload
+ method = PATCH
+ for conf in configs:
+ if conf.get("members", None):
+ if conf["members"].get("interfaces", None):
+ url = 'data/openconfig-network-instance:network-instances/network-instance={0}/interfaces/interface'.format(conf["name"])
+ payload = self.build_create_vrf_interface_payload(conf)
+ if payload:
+ request = {"path": url, "method": method, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def build_create_vrf_payload(self, conf):
+ name = conf['name']
+
+ netw_inst = dict({'name': name})
+ netw_inst['config'] = dict({'name': name})
+ netw_inst['config'].update({'enabled': True})
+ netw_inst['config'].update({'type': 'L3VRF'})
+ netw_inst_arr = [netw_inst]
+
+ return dict({'openconfig-network-instance:network-instances': {'network-instance': netw_inst_arr}})
+
+ def build_create_vrf_interface_payload(self, conf):
+ members = conf["members"].get("interfaces", None)
+ network_inst_payload = dict()
+ if members:
+ network_inst_payload.update({"openconfig-network-instance:interface": []})
+ for member in members:
+ if member["name"]:
+ member_config_payload = dict({"id": member["name"]})
+ member_payload = dict({"id": member["name"], "config": member_config_payload})
+ network_inst_payload["openconfig-network-instance:interface"].append(member_payload)
+
+ return network_inst_payload
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
new file mode 100644
index 000000000..d44adcedf
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/config/vxlans/vxlans.py
@@ -0,0 +1,606 @@
+#
+# -*- 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_vxlans 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
+)
+from ansible.module_utils.connection import ConnectionError
+
+PATCH = 'patch'
+DELETE = 'delete'
+test_keys = [
+ {'vlan_map': {'vlan': '', 'vni': ''}},
+ {'vrf_map': {'vni': '', 'vrf': ''}},
+]
+
+
+class Vxlans(ConfigBase):
+ """
+ The sonic_vxlans class
+ """
+
+ gather_subset = [
+ '!all',
+ '!min',
+ ]
+
+ gather_network_resources = [
+ 'vxlans',
+ ]
+
+ def __init__(self, module):
+ super(Vxlans, self).__init__(module)
+
+ def get_vxlans_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)
+ vxlans_facts = facts['ansible_network_resources'].get('vxlans')
+ if not vxlans_facts:
+ return []
+ return vxlans_facts
+
+ def execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+
+ existing_vxlans_facts = self.get_vxlans_facts()
+ commands, requests = self.set_config(existing_vxlans_facts)
+
+ if commands and requests:
+ 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_vxlans_facts = self.get_vxlans_facts()
+
+ result['before'] = existing_vxlans_facts
+ if result['changed']:
+ result['after'] = changed_vxlans_facts
+
+ result['warnings'] = warnings
+ return result
+
+ def set_config(self, existing_vxlans_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_vxlans_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)
+
+ 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, 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
+ """
+
+ 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)
+
+ 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
+ """
+ requests = []
+ commands = []
+
+ 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)
+
+ 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 at position-0
+ Requests necessary to merge to the current configuration
+ at position-1
+ """
+ commands = diff
+ requests = self.get_create_vxlans_request(commands, have)
+
+ if len(requests) == 0:
+ commands = []
+
+ if commands:
+ commands = update_states(commands, "merged")
+
+ 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
+ """
+
+ requests = []
+ is_delete_all = False
+ # if want is none, then delete all the vxlans
+ if not want or len(have) == 0:
+ commands = have
+ is_delete_all = True
+ else:
+ commands = want
+
+ if is_delete_all:
+ requests = self.get_delete_all_vxlan_request(have)
+ else:
+ requests = self.get_delete_vxlan_request(commands, have)
+
+ if len(requests) == 0:
+ commands = []
+
+ if commands:
+ commands = update_states(commands, "deleted")
+
+ return commands, requests
+
+ def get_create_vxlans_request(self, configs, have):
+ requests = []
+
+ if not configs:
+ return requests
+
+ tunnel_requests = self.get_create_tunnel_request(configs, have)
+ vlan_map_requests = self.get_create_vlan_map_request(configs, have)
+ vrf_map_requests = self.get_create_vrf_map_request(configs, have)
+
+ if tunnel_requests:
+ requests.extend(tunnel_requests)
+ if vlan_map_requests:
+ requests.extend(vlan_map_requests)
+ if vrf_map_requests:
+ requests.extend(vrf_map_requests)
+
+ return requests
+
+ def get_delete_all_vxlan_request(self, have):
+ requests = []
+
+ vrf_map_requests = []
+ vlan_map_requests = []
+ src_ip_requests = []
+ primary_ip_requests = []
+ tunnel_requests = []
+
+ # Need to delete in reverse order of creation.
+ # vrf_map needs to be cleared before vlan_map
+ # vlan_map needs to be cleared before tunnel(source-ip)
+ for conf in have:
+ name = conf['name']
+ vlan_map_list = conf.get('vlan_map', [])
+ vrf_map_list = conf.get('vrf_map', [])
+ src_ip = conf.get('source_ip', None)
+ primary_ip = conf.get('primary_ip', None)
+
+ if vrf_map_list:
+ vrf_map_requests.extend(self.get_delete_vrf_map_request(conf, conf, name, vrf_map_list))
+ if vlan_map_list:
+ vlan_map_requests.extend(self.get_delete_vlan_map_request(conf, conf, name, vlan_map_list))
+ if src_ip:
+ 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))
+ tunnel_requests.extend(self.get_delete_tunnel_request(conf, conf, name))
+
+ if vrf_map_requests:
+ requests.extend(vrf_map_requests)
+ if vlan_map_requests:
+ requests.extend(vlan_map_requests)
+ if src_ip_requests:
+ requests.extend(src_ip_requests)
+ if primary_ip_requests:
+ requests.extend(primary_ip_requests)
+ if tunnel_requests:
+ requests.extend(tunnel_requests)
+
+ return requests
+
+ def get_delete_vxlan_request(self, configs, have):
+ requests = []
+
+ if not configs:
+ return requests
+
+ vrf_map_requests = []
+ vlan_map_requests = []
+ src_ip_requests = []
+ primary_ip_requests = []
+ tunnel_requests = []
+
+ # Need to delete in the reverse order of creation.
+ # vrf_map needs to be cleared before vlan_map
+ # vlan_map needs to be cleared before tunnel(source-ip)
+ for conf in configs:
+
+ name = conf['name']
+ src_ip = conf.get('source_ip', None)
+ primary_ip = conf.get('primary_ip', None)
+ vlan_map_list = conf.get('vlan_map', None)
+ vrf_map_list = conf.get('vrf_map', None)
+
+ have_vlan_map_count = 0
+ have_vrf_map_count = 0
+ matched = next((each_vxlan for each_vxlan in have if each_vxlan['name'] == name), None)
+ if matched:
+ have_vlan_map = matched.get('vlan_map', [])
+ have_vrf_map = matched.get('vrf_map', [])
+ if have_vlan_map:
+ have_vlan_map_count = len(have_vlan_map)
+ if have_vrf_map:
+ have_vrf_map_count = len(have_vrf_map)
+
+ 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):
+ is_delete_full = True
+ vrf_map_list = matched.get("vrf_map", [])
+ vlan_map_list = matched.get("vlan_map", [])
+
+ if vlan_map_list is not None and len(vlan_map_list) == 0 and matched:
+ vlan_map_list = matched.get("vlan_map", [])
+ if vrf_map_list is not None and len(vrf_map_list) == 0 and matched:
+ vrf_map_list = matched.get("vrf_map", [])
+
+ if vrf_map_list:
+ temp_vrf_map_requests = self.get_delete_vrf_map_request(conf, matched, name, vrf_map_list)
+ if temp_vrf_map_requests:
+ vrf_map_requests.extend(temp_vrf_map_requests)
+ have_vrf_map_count -= len(temp_vrf_map_requests)
+ if vlan_map_list:
+ temp_vlan_map_requests = self.get_delete_vlan_map_request(conf, matched, name, vlan_map_list)
+ if temp_vlan_map_requests:
+ vlan_map_requests.extend(temp_vlan_map_requests)
+ 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 primary_ip:
+ primary_ip_requests.extend(self.get_delete_primary_ip_request(conf, matched, name, primary_ip))
+ if is_delete_full:
+ tunnel_requests.extend(self.get_delete_tunnel_request(conf, matched, name))
+
+ if vrf_map_requests:
+ requests.extend(vrf_map_requests)
+ if vlan_map_requests:
+ requests.extend(vlan_map_requests)
+ if src_ip_requests:
+ requests.extend(src_ip_requests)
+ if primary_ip_requests:
+ requests.extend(primary_ip_requests)
+ if tunnel_requests:
+ requests.extend(tunnel_requests)
+
+ return requests
+
+ def get_create_evpn_request(self, conf):
+ # Create URL and payload
+ url = "data/sonic-vxlan:sonic-vxlan/EVPN_NVO/EVPN_NVO_LIST"
+ payload = self.build_create_evpn_payload(conf)
+ request = {"path": url, "method": PATCH, "data": payload}
+
+ return request
+
+ def get_create_tunnel_request(self, configs, have):
+ # Create URL and payload
+ requests = []
+ url = "data/sonic-vxlan:sonic-vxlan/VXLAN_TUNNEL"
+ for conf in configs:
+ payload = self.build_create_tunnel_payload(conf)
+ request = {"path": url, "method": PATCH, "data": payload}
+ requests.append(request)
+ if conf.get('source_ip', None):
+ requests.append(self.get_create_evpn_request(conf))
+
+ return requests
+
+ def build_create_evpn_payload(self, conf):
+
+ evpn_nvo_list = [{'name': conf['evpn_nvo'], 'source_vtep': conf['name']}]
+ evpn_dict = {'sonic-vxlan:EVPN_NVO_LIST': evpn_nvo_list}
+
+ return evpn_dict
+
+ def build_create_tunnel_payload(self, conf):
+ payload_url = dict()
+
+ vtep_ip_dict = dict()
+ vtep_ip_dict['name'] = conf['name']
+ if conf.get('source_ip', None):
+ vtep_ip_dict['src_ip'] = conf['source_ip']
+ if conf.get('primary_ip', None):
+ vtep_ip_dict['primary_ip'] = conf['primary_ip']
+
+ payload_url['sonic-vxlan:VXLAN_TUNNEL'] = {'VXLAN_TUNNEL_LIST': [vtep_ip_dict]}
+
+ return payload_url
+
+ def get_create_vlan_map_request(self, configs, have):
+ # Create URL and payload
+ requests = []
+ for conf in configs:
+ new_vlan_map_list = conf.get('vlan_map', [])
+ if new_vlan_map_list:
+ for each_vlan_map in new_vlan_map_list:
+ name = conf['name']
+ vlan = each_vlan_map.get('vlan')
+ vni = each_vlan_map.get('vni')
+ matched = next((each_vxlan for each_vxlan in have if each_vxlan['name'] == name), None)
+
+ is_change_needed = True
+ if matched:
+ matched_vlan_map_list = matched.get('vlan_map', [])
+ if matched_vlan_map_list:
+ matched_vlan_map = next((e_vlan_map for e_vlan_map in matched_vlan_map_list if e_vlan_map['vni'] == vni), None)
+ if matched_vlan_map:
+ if matched_vlan_map['vlan'] == vlan:
+ is_change_needed = False
+
+ if is_change_needed:
+ map_name = "map_{0}_Vlan{1}".format(vni, vlan)
+ payload = self.build_create_vlan_map_payload(conf, each_vlan_map)
+ url = "data/sonic-vxlan:sonic-vxlan/VXLAN_TUNNEL_MAP"
+ request = {"path": url, "method": PATCH, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def build_create_vlan_map_payload(self, conf, vlan_map):
+ payload_url = dict()
+
+ vlan_map_dict = dict()
+ vlan_map_dict['name'] = conf['name']
+ vlan_map_dict['mapname'] = "map_{vni}_Vlan{vlan}".format(vni=vlan_map['vni'], vlan=vlan_map['vlan'])
+ vlan_map_dict['vlan'] = "Vlan{vlan}".format(vlan=vlan_map['vlan'])
+ vlan_map_dict['vni'] = vlan_map['vni']
+
+ payload_url['sonic-vxlan:VXLAN_TUNNEL_MAP'] = {'VXLAN_TUNNEL_MAP_LIST': [vlan_map_dict]}
+
+ return payload_url
+
+ def get_create_vrf_map_request(self, configs, have):
+ # Create URL and payload
+ requests = []
+ for conf in configs:
+ new_vrf_map_list = conf.get('vrf_map', [])
+ if new_vrf_map_list:
+ for each_vrf_map in new_vrf_map_list:
+ name = conf['name']
+ vrf = each_vrf_map.get('vrf')
+ vni = each_vrf_map.get('vni')
+ matched = next((each_vxlan for each_vxlan in have if each_vxlan['name'] == name), None)
+
+ is_change_needed = True
+ if matched:
+ matched_vrf_map_list = matched.get('vrf_map', [])
+ if matched_vrf_map_list:
+ matched_vrf_map = next((e_vrf_map for e_vrf_map in matched_vrf_map_list if e_vrf_map['vni'] == vni), None)
+ if matched_vrf_map:
+ if matched_vrf_map['vrf'] == vrf:
+ is_change_needed = False
+
+ if is_change_needed:
+ payload = self.build_create_vrf_map_payload(conf, each_vrf_map)
+ url = "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST={vrf}/vni".format(vrf=vrf)
+ request = {"path": url, "method": PATCH, "data": payload}
+ requests.append(request)
+
+ return requests
+
+ def build_create_vrf_map_payload(self, conf, vrf_map):
+
+ payload_url = dict({"sonic-vrf:vni": vrf_map['vni']})
+ return payload_url
+
+ def get_delete_evpn_request(self, conf):
+ # 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}
+
+ return request
+
+ def get_delete_tunnel_request(self, conf, matched, name):
+ # Create URL and payload
+ requests = []
+
+ url = "data/sonic-vxlan:sonic-vxlan/VXLAN_TUNNEL/VXLAN_TUNNEL_LIST={name}".format(name=name)
+ requests.append({"path": url, "method": DELETE})
+
+ return requests
+
+ def get_delete_src_ip_request(self, conf, matched, name, del_source_ip):
+ # Create URL and payload
+ requests = []
+
+ url = "data/sonic-vxlan:sonic-vxlan/VXLAN_TUNNEL/VXLAN_TUNNEL_LIST={name}/src_ip"
+
+ is_change_needed = False
+ if matched:
+ matched_source_ip = matched.get('source_ip', None)
+ 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)
+
+ return requests
+
+ def get_delete_primary_ip_request(self, conf, matched, name, del_primary_ip):
+ # Create URL and payload
+ requests = []
+
+ url = "data/sonic-vxlan:sonic-vxlan/VXLAN_TUNNEL/VXLAN_TUNNEL_LIST={name}/primary_ip"
+
+ is_change_needed = False
+ if matched:
+ matched_primary_ip = matched.get('primary_ip', None)
+ if matched_primary_ip and matched_primary_ip == del_primary_ip:
+ is_change_needed = True
+
+ if is_change_needed:
+ request = {"path": url.format(name=name), "method": DELETE}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_vlan_map_request(self, conf, matched, name, del_vlan_map_list):
+ # Create URL and payload
+ requests = []
+
+ for each_vlan_map in del_vlan_map_list:
+ vlan = each_vlan_map.get('vlan')
+ vni = each_vlan_map.get('vni')
+
+ is_change_needed = False
+ if matched:
+ matched_vlan_map_list = matched.get('vlan_map', None)
+ if matched_vlan_map_list:
+ matched_vlan_map = next((e_vlan_map for e_vlan_map in matched_vlan_map_list if e_vlan_map['vni'] == vni), None)
+ if matched_vlan_map:
+ if matched_vlan_map['vlan'] == vlan:
+ is_change_needed = True
+
+ if is_change_needed:
+ map_name = "map_{0}_Vlan{1}".format(vni, vlan)
+ url = "data/sonic-vxlan:sonic-vxlan/VXLAN_TUNNEL_MAP/VXLAN_TUNNEL_MAP_LIST={name},{map_name}".format(name=name, map_name=map_name)
+ request = {"path": url, "method": DELETE}
+ requests.append(request)
+
+ return requests
+
+ def get_delete_vrf_map_request(self, conf, matched, name, del_vrf_map_list):
+ # Create URL and payload
+ requests = []
+
+ for each_vrf_map in del_vrf_map_list:
+ vrf = each_vrf_map.get('vrf')
+ vni = each_vrf_map.get('vni')
+
+ is_change_needed = False
+ if matched:
+ matched_vrf_map_list = matched.get('vrf_map', None)
+ if matched_vrf_map_list:
+ matched_vrf_map = next((e_vrf_map for e_vrf_map in matched_vrf_map_list if e_vrf_map['vni'] == vni), None)
+ if matched_vrf_map:
+ if matched_vrf_map['vrf'] == vrf:
+ is_change_needed = True
+
+ if is_change_needed:
+ url = "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST={vrf}/vni".format(vrf=vrf)
+ request = {"path": url, "method": DELETE}
+ requests.append(request)
+
+ return requests
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/aaa/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/aaa/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/aaa/__init__.py
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
new file mode 100644
index 000000000..5a7bd05c9
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/aaa/aaa.py
@@ -0,0 +1,111 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 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 aaa 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.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.aaa.aaa import AaaArgs
+
+GET = "get"
+
+
+class AaaFacts(object):
+ """ The sonic aaa fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = AaaArgs.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 get_aaa(self):
+ """Get aaa details available in chassis"""
+ request = [{"path": "data/openconfig-system:system/aaa", "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)
+ data = {}
+ if ('openconfig-system:aaa' in response[0][1]):
+ if ('authentication' in response[0][1]['openconfig-system:aaa']):
+ if ('config' in response[0][1]['openconfig-system:aaa']['authentication']):
+ data = response[0][1]['openconfig-system:aaa']['authentication']['config']
+ return data
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for aaa
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ data = self.get_aaa()
+ objs = []
+ objs = self.render_config(self.generated_spec, data)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['aaa'] = 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 = self.parse_sonic_aaa(spec, conf)
+ return config
+
+ def parse_sonic_aaa(self, spec, conf):
+ config = deepcopy(spec)
+ if conf:
+ temp = {}
+ if ('authentication-method' in conf) and (conf['authentication-method']):
+ if 'local' in conf['authentication-method']:
+ temp['local'] = True
+ choices = ['tacacs+', 'ldap', 'radius']
+ for i, word in enumerate(conf['authentication-method']):
+ if word in choices:
+ temp['group'] = conf['authentication-method'][i]
+ if ('failthrough' in conf):
+ temp['fail_through'] = conf['failthrough']
+ if temp:
+ config['authentication']['data'] = temp
+ return utils.remove_empties(config)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp/__init__.py
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
new file mode 100644
index 000000000..c86b53c2a
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp/bgp.py
@@ -0,0 +1,156 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic bgp 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.utils.utils import (
+ remove_empties_from_list
+)
+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.utils.bgp_utils import (
+ get_bgp_data,
+)
+
+
+class BgpFacts(object):
+ """ The sonic bgp fact class
+ """
+
+ global_params_map = {
+ 'bgp_as': 'as',
+ 'router_id': 'router-id',
+ 'holdtime': 'hold-time',
+ 'keepalive_interval': 'keepalive-interval',
+ 'log_neighbor_changes': ['logging-options', 'log-neighbor-state-changes'],
+ 'as_path_confed': ['route-selection-options', 'compare-confed-as-path'],
+ 'as_path_ignore': ['route-selection-options', 'ignore-as-path-length'],
+ 'as_path_multipath_relax': ['use-multiple-paths', 'ebgp', 'config', 'allow-multiple-as'],
+ 'as_path_multipath_relax_as_set': ['use-multiple-paths', 'ebgp', 'config', 'as-set'],
+ 'compare_routerid': ['route-selection-options', 'external-compare-router-id'],
+ 'med_confed': ['route-selection-options', 'med-confed'],
+ 'med_missing_as_worst': ['route-selection-options', 'med-missing-as-worst'],
+ 'always_compare_med': ['route-selection-options', 'always-compare-med'],
+ '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'],
+ }
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = BgpArgs.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 BGP
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = list()
+ if connection: # just for linting purposes, remove
+ pass
+
+ if not data:
+ data = get_bgp_data(self._module, self.global_params_map)
+ self.normalise_bgp_data(data)
+
+ # operate on a collection of resource x
+ for conf in data:
+ if conf:
+ obj = self.render_config(self.generated_spec, conf)
+ # split the config into instances of the resource
+ if obj:
+ objs.append(obj)
+
+ 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']
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def normalise_bgp_data(self, data):
+ for conf in data:
+ bestpath = {}
+ med = {}
+ timers = {}
+ as_path = {}
+ max_med_on_start_up = {}
+
+ conf['log_neighbor_changes'] = conf.get('log_neighbor_changes', False)
+
+ as_path['confed'] = conf.get('as_path_confed', False)
+ as_path['ignore'] = conf.get('as_path_ignore', False)
+ as_path['multipath_relax'] = conf.get('as_path_multipath_relax', False)
+ as_path['multipath_relax_as_set'] = conf.get('as_path_multipath_relax_as_set', False)
+ bestpath['as_path'] = as_path
+
+ med['confed'] = conf.get('med_confed', False)
+ med['missing_as_worst'] = conf.get('med_missing_as_worst', False)
+ med['always_compare_med'] = conf.get('always_compare_med', False)
+ bestpath['med'] = med
+
+ timers['holdtime'] = conf.get('holdtime', None)
+ timers['keepalive_interval'] = conf.get('keepalive_interval', None)
+ conf['timers'] = timers
+ bestpath['compare_routerid'] = conf.get('compare_routerid', False)
+
+ conf['bestpath'] = bestpath
+
+ max_med_on_start_up["timer"] = conf.get('max_med_on_startup_timer', None)
+ max_med_on_start_up["med_val"] = conf.get('max_med_on_startup_med_val', None)
+
+ conf['max_med'] = {
+ 'on_startup': max_med_on_start_up,
+ }
+
+ keys = [
+ 'as_path_confed', 'as_path_ignore', 'as_path_multipath_relax', 'as_path_multipath_relax_as_set',
+ 'med_confed', 'med_missing_as_worst', 'always_compare_med', 'max_med_val', 'holdtime',
+ 'keepalive_interval', 'compare_routerid', 'admin_max_med', 'max_med_on_startup_timer',
+ 'max_med_on_startup_med_val',
+ ]
+ for key in keys:
+ if key in conf:
+ conf.pop(key)
+
+ 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
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_af/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_af/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_af/__init__.py
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
new file mode 100644
index 000000000..fd37533e4
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_af/bgp_af.py
@@ -0,0 +1,258 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic bgp_af 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.utils.utils import (
+ remove_empties_from_list
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.bgp_af.bgp_af import Bgp_afArgs
+
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import (
+ get_bgp_af_data,
+ get_all_bgp_af_redistribute,
+)
+
+
+class Bgp_afFacts(object):
+ """ The sonic bgp_af fact class
+ """
+
+ afi_safi_types_map = {
+ 'openconfig-bgp-types:IPV4_UNICAST': 'ipv4_unicast',
+ 'openconfig-bgp-types:IPV6_UNICAST': 'ipv6_unicast',
+ 'openconfig-bgp-types:L2VPN_EVPN': 'l2vpn_evpn',
+ }
+
+ af_params_map = {
+ 'afi': 'afi-safi-name',
+ 'route_map': 'policy-name',
+ 'prefix': 'prefix',
+ 'neighbor': 'neighbor-address',
+ 'route_reflector_client': 'route-reflector-client',
+ 'route_server_client': 'route-server-client',
+ 'next_hop_self': ['next-hop-self', 'enabled'],
+ 'remove_private_as': ['remove-private-as', 'enabled'],
+ 'prefix_list_in': ['prefix-list', 'import-policy'],
+ 'prefix_list_out': ['prefix-list', 'export-policy'],
+ 'maximum_prefix': ['prefix-limit', 'max-prefixes'],
+ 'activate': 'enabled',
+ 'advertise_pip': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:config', 'advertise-pip'],
+ 'advertise_pip_ip': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:config', 'advertise-pip-ip'],
+ 'advertise_pip_peer_ip': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:config', 'advertise-pip-peer-ip'],
+ 'advertise_svi_ip': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:config', 'advertise-svi-ip'],
+ 'advertise_all_vni': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:config', 'advertise-all-vni'],
+ 'advertise_default_gw': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:config', 'advertise-default-gw'],
+ 'ebgp': ['use-multiple-paths', 'ebgp', 'maximum-paths'],
+ 'ibgp': ['use-multiple-paths', 'ibgp', 'maximum-paths'],
+ 'network': ['network-config', 'network'],
+ 'dampening': ['route-flap-damping', 'config', 'enabled'],
+ 'route_advertise_list': ['l2vpn-evpn', 'openconfig-bgp-evpn-ext:route-advertise', 'route-advertise-list'],
+ }
+
+ af_redis_params_map = {
+ 'protocol': 'src-protocol',
+ 'afi': 'address-family',
+ 'metric': 'metric',
+ 'route_map': 'import-policy'
+ }
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Bgp_afArgs.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 BGP
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = list()
+ if connection: # just for linting purposes, remove
+ pass
+ if not data:
+ data = get_bgp_af_data(self._module, self.af_params_map)
+ vrf_list = [e_bgp_af['vrf_name'] for e_bgp_af in data]
+ self.update_max_paths(data)
+ self.update_network(data)
+ self.update_route_advertise_list(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)
+
+ # operate on a collection of resource x
+ for conf in data:
+ if conf:
+ obj = self.render_config(self.generated_spec, conf)
+ # split the config into instances of the resource
+ if obj:
+ objs.append(obj)
+
+ 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']
+ 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 check_afi(self, afi, redis_data):
+ afi_rhs = afi
+ afi_lhs = redis_data.get('afi', None)
+ return (afi_lhs and (afi_rhs == afi_lhs))
+
+ def update_redis_data(self, objs, af_redis_data):
+ if not (af_redis_data or objs):
+ return
+
+ for conf in objs:
+ vrf_name = conf['vrf_name']
+ raw_af_redis_data = next((e_af_redis for e_af_redis in af_redis_data if vrf_name in e_af_redis), None)
+ if not raw_af_redis_data:
+ continue
+ norm_af_redis_data = self.normalize_af_redis_params(raw_af_redis_data[vrf_name])
+ if norm_af_redis_data:
+ if 'address_family' in conf:
+ afs = conf['address_family']
+ if not afs:
+ continue
+ for e_af in afs:
+ if 'afi' in e_af:
+ afi = e_af['afi']
+ redis_arr = []
+ for e_redis_data in norm_af_redis_data:
+ if self.check_afi(afi, e_redis_data):
+ e_redis_data.pop('afi')
+ redis_arr.append(e_redis_data)
+ e_af.update({'redistribute': redis_arr})
+ else:
+ addr_fams = []
+ for e_norm_af_redis in norm_af_redis_data:
+ afi = e_norm_af_redis['afi']
+ e_norm_af_redis.pop('afi')
+ mat_addr_fam = next((each_addr_fam for each_addr_fam in addr_fams if each_addr_fam['afi'] == afi), None)
+ if mat_addr_fam:
+ mat_addr_fam['redistribute'].append(e_norm_af_redis)
+ else:
+ addr_fams.append({'redistribute': [e_norm_af_redis], 'afi': afi})
+
+ if addr_fams:
+ conf.update({'address_family': addr_fams})
+
+ def update_max_paths(self, data):
+ for conf in data:
+ afs = conf.get('address_family', [])
+ if afs:
+ for af in afs:
+ max_path = {}
+ ebgp = af.get('ebgp', None)
+ if ebgp:
+ af.pop('ebgp')
+ max_path['ebgp'] = ebgp
+ ibgp = af.get('ibgp', None)
+ if ibgp:
+ af.pop('ibgp')
+ max_path['ibgp'] = ibgp
+ if max_path:
+ af['max_path'] = max_path
+
+ def update_network(self, data):
+ for conf in data:
+ afs = conf.get('address_family', [])
+ if afs:
+ for af in afs:
+ temp = []
+ network = af.get('network', None)
+ if network:
+ for e in network:
+ prefix = e.get('prefix', None)
+ if prefix:
+ temp.append(prefix)
+ af['network'] = temp
+ dampening = af.get('dampening', None)
+ if dampening:
+ af.pop('dampening')
+ af['dampening'] = dampening
+
+ def update_afis(self, data):
+ for conf in data:
+ if 'address_family' in conf:
+ conf['address_family'] = {'afis': conf['address_family']}
+
+ def update_route_advertise_list(self, data):
+ for conf in data:
+ afs = conf.get('address_family', [])
+ if afs:
+ for af in afs:
+ rt_adv_lst = []
+ route_advertise_list = af.get('route_advertise_list', None)
+ if route_advertise_list:
+ for rt in route_advertise_list:
+ rt_adv_dict = {}
+ advertise_afi = rt['advertise-afi-safi'].split(':')[1].split('_')[0].lower()
+ route_map_config = rt['config']
+ route_map = route_map_config.get('route-map', None)
+ if advertise_afi:
+ rt_adv_dict['advertise_afi'] = advertise_afi
+ if route_map:
+ rt_adv_dict['route_map'] = route_map[0]
+ if rt_adv_dict and rt_adv_dict not in rt_adv_lst:
+ rt_adv_lst.append(rt_adv_dict)
+ af['route_advertise_list'] = rt_adv_lst
+
+ def normalize_af_redis_params(self, af):
+ norm_af = list()
+ for e_af in af:
+ temp = e_af.copy()
+ for key, val in e_af.items():
+ if 'afi' == key or 'protocol' == key and val:
+ if ':' in val:
+ temp[key] = val.split(':')[1].lower()
+ if '_' in val:
+ temp[key] = val.split('_')[1].lower()
+ elif 'route_map' == key and val:
+ temp['route_map'] = val[0]
+
+ norm_af.append(temp)
+ return norm_af
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_as_paths/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_as_paths/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_as_paths/__init__.py
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
new file mode 100644
index 000000000..822db22a4
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_as_paths/bgp_as_paths.py
@@ -0,0 +1,129 @@
+#
+# -*- 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 bgp_as_paths 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.bgp_as_paths.bgp_as_paths import Bgp_as_pathsArgs
+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 Bgp_as_pathsFacts(object):
+ """ The sonic bgp_as_paths fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Bgp_as_pathsArgs.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 get_as_path_list(self):
+ url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/as-path-sets"
+ 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), code=exc.code)
+
+ as_path_lists = []
+ if "openconfig-bgp-policy:as-path-sets" in response[0][1]:
+ temp = response[0][1].get("openconfig-bgp-policy:as-path-sets", {})
+ if "as-path-set" in temp:
+ as_path_lists = temp["as-path-set"]
+
+ as_path_list_configs = []
+ for as_path in as_path_lists:
+ result = dict()
+ as_name = as_path["as-path-set-name"]
+ member_config = as_path['config']
+ members = member_config.get("as-path-set-member", [])
+ permit_str = member_config.get("openconfig-bgp-policy-ext:action", None)
+ result['name'] = as_name
+ result['members'] = members
+ if permit_str and permit_str == "PERMIT":
+ result['permit'] = True
+ 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):
+ """ Populate the facts for as_path_list
+ :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:
+ resources = self.get_as_path_list()
+
+ objs = []
+ for resource in resources:
+ if resource:
+ obj = self.render_config(self.generated_spec, resource)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('bgp_as_paths', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['bgp_as_paths'] = 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)
+ try:
+ config['name'] = str(conf['name'])
+ config['members'] = conf['members']
+ config['permit'] = conf['permit']
+ except TypeError:
+ config['name'] = None
+ config['members'] = None
+ config['permit'] = None
+ return utils.remove_empties(config)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_communities/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_communities/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_communities/__init__.py
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
new file mode 100644
index 000000000..ffa294221
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_communities/bgp_communities.py
@@ -0,0 +1,145 @@
+#
+# -*- 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 bgp_communities 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.bgp_communities.bgp_communities import Bgp_communitiesArgs
+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 Bgp_communitiesFacts(object):
+ """ The sonic bgp_communities fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Bgp_communitiesArgs.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 get_bgp_communities(self):
+ url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/community-sets"
+ 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), code=exc.code)
+
+ bgp_communities = []
+ if "openconfig-bgp-policy:community-sets" in response[0][1]:
+ temp = response[0][1].get("openconfig-bgp-policy:community-sets", {})
+ if "community-set" in temp:
+ bgp_communities = temp["community-set"]
+
+ bgp_communities_configs = []
+ for bgp_community in bgp_communities:
+ result = dict()
+ name = bgp_community["community-set-name"]
+ member_config = bgp_community['config']
+ 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['match'] = match
+ 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'
+ 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}
+ 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):
+ """ Populate the facts for bgp_communities
+ :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:
+ resources = self.get_bgp_communities()
+
+ objs = []
+ for resource in resources:
+ if resource:
+ obj = self.render_config(self.generated_spec, resource)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('bgp_communities', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['bgp_communities'] = 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)
+ 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)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_ext_communities/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_ext_communities/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_ext_communities/__init__.py
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
new file mode 100644
index 000000000..b1d7c4ad0
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_ext_communities/bgp_ext_communities.py
@@ -0,0 +1,158 @@
+#
+# -*- 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 bgp_ext_communities 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.bgp_ext_communities.bgp_ext_communities import (
+ Bgp_ext_communitiesArgs,
+)
+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 Bgp_ext_communitiesFacts(object):
+ """ The sonic bgp_ext_communities fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Bgp_ext_communitiesArgs.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 get_bgp_extcommunities(self):
+ url = "data/openconfig-routing-policy:routing-policy/defined-sets/openconfig-bgp-policy:bgp-defined-sets/ext-community-sets"
+ 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), code=exc.code)
+
+ bgp_extcommunities = []
+ if "openconfig-bgp-policy:ext-community-sets" in response[0][1]:
+ temp = response[0][1].get("openconfig-bgp-policy:ext-community-sets", {})
+ if "ext-community-set" in temp:
+ bgp_extcommunities = temp["ext-community-set"]
+
+ bgp_extcommunities_configs = []
+ for bgp_extcommunity in bgp_extcommunities:
+ result = dict()
+ name = bgp_extcommunity["ext-community-set-name"]
+ member_config = bgp_extcommunity['config']
+ 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['match'] = match.lower()
+
+ if permit_str and permit_str == 'PERMIT':
+ result['permit'] = True
+ 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
+
+ bgp_extcommunities_configs.append(result)
+
+ return bgp_extcommunities_configs
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for bgp_ext_communities
+ :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:
+ resources = self.get_bgp_extcommunities()
+
+ objs = []
+ for resource in resources:
+ if resource:
+ obj = self.render_config(self.generated_spec, resource)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('bgp_ext_communities', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['bgp_ext_communities'] = 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)
+ 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)
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors/__init__.py
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
new file mode 100644
index 000000000..903b93de1
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors/bgp_neighbors.py
@@ -0,0 +1,229 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic bgp_neighbors 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.utils.utils import (
+ remove_empties_from_list
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.bgp_neighbors.bgp_neighbors import Bgp_neighborsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import (
+ get_all_bgp_neighbors,
+ get_from_params_map,
+ get_peergroups,
+)
+
+
+class Bgp_neighborsFacts(object):
+ """ The sonic bgp_neighbors fact class
+ """
+
+ neighbor_params_map = {
+ 'neighbor': 'neighbor-address',
+ 'peer_as': 'peer-as',
+ 'peer_type': 'peer-type',
+ 'peer_group': 'peer-group',
+ 'keepalive': 'keepalive-interval',
+ 'holdtime': 'hold-time',
+ 'connect_retry': 'connect-retry',
+ 'advertisement_interval': 'minimum-advertisement-interval',
+ 'bfd_enabled': ['enable-bfd', 'enabled'],
+ 'check_failure': ['enable-bfd', 'check-control-plane-failure'],
+ 'profile': ['enable-bfd', 'bfd-profile'],
+ 'dynamic': 'capability-dynamic',
+ 'extended_nexthop': 'capability-extended-nexthop',
+ 'pwd': ['auth-password', 'password'],
+ 'encrypted': ['auth-password', 'encrypted'],
+ 'nbr_description': 'description',
+ 'disable_connected_check': 'disable-ebgp-connected-route-check',
+ 'dont_negotiate_capability': 'dont-negotiate-capability',
+ 'enforce_first_as': 'enforce-first-as',
+ 'enforce_multihop': 'enforce-multihop',
+ 'local_address': ['transport', 'config', 'local-address'],
+ 'as': 'local-as',
+ 'no_prepend': 'local-as-no-prepend',
+ 'replace_as': 'local-as-replace-as',
+ 'override_capability': 'override-capability',
+ 'port': 'peer-port',
+ 'shutdown_msg': 'shutdown-message',
+ 'solo': 'solo-peer',
+ 'strict_capability_match': 'strict-capability-match',
+ 'ttl_security': 'ttl-security-hops',
+ 'enabled': ['ebgp-multihop', 'enabled'],
+ 'multihop_ttl': ['ebgp-multihop', 'multihop-ttl'],
+ 'v6only': 'openconfig-bgp-ext:v6only',
+ 'passive': ['transport', 'config', 'passive-mode']
+ }
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Bgp_neighborsArgs.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 BGP
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = list()
+
+ if not data:
+ data = get_all_bgp_neighbors(self._module)
+ filtered_data = self.filter_neighbors_data(data)
+ if filtered_data:
+ data = filtered_data
+
+ 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('bgp_neighbors', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': remove_empties_from_list(objs)})
+ facts['bgp_neighbors'] = 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 filter_neighbors_data(self, data):
+ filtered_data = []
+ for conf in data:
+ vrf_name = conf['vrf_name']
+ tmp = {}
+ bgp_as = conf['bgp_as']
+ val = None
+ if 'neighbors' in conf and 'neighbor' in conf['neighbors']:
+ val = conf['neighbors']['neighbor']
+
+ tmp['vrf_name'] = vrf_name
+ tmp['bgp_as'] = bgp_as
+ peergroup = get_peergroups(self._module, vrf_name)
+ if peergroup:
+ tmp['peer_group'] = peergroup
+ fil_neighbors = []
+ if val:
+ for neighbor in val:
+ fil_neighbor = get_from_params_map(self.neighbor_params_map, neighbor)
+ capability = {}
+ capability_dynamic = fil_neighbor.get('dynamic', None)
+ if capability_dynamic is not None:
+ capability['dynamic'] = capability_dynamic
+ fil_neighbor.pop('dynamic')
+ capability_extended_nexthop = fil_neighbor.get('extended_nexthop', None)
+ if capability_extended_nexthop is not None:
+ capability['extended_nexthop'] = capability_extended_nexthop
+ fil_neighbor.pop('extended_nexthop')
+ if capability:
+ fil_neighbor['capability'] = capability
+ remote = {}
+ peer_as = fil_neighbor.get('peer_as', None)
+ if peer_as is not None:
+ remote['peer_as'] = peer_as
+ fil_neighbor.pop('peer_as')
+ peer_type = fil_neighbor.get('peer_type', None)
+ if peer_type is not None:
+ remote['peer_type'] = peer_type.lower()
+ fil_neighbor.pop('peer_type')
+ if remote:
+ fil_neighbor['remote_as'] = remote
+ auth_pwd = {}
+ pwd = fil_neighbor.get('pwd', None)
+ if pwd is not None:
+ auth_pwd['pwd'] = pwd
+ fil_neighbor.pop('pwd')
+ encrypted = fil_neighbor.get('encrypted', None)
+ if encrypted is not None:
+ auth_pwd['encrypted'] = encrypted
+ fil_neighbor.pop('encrypted')
+ ebgp_multihop = {}
+ enabled = fil_neighbor.get('enabled', None)
+ if enabled is not None:
+ ebgp_multihop['enabled'] = enabled
+ fil_neighbor.pop('enabled')
+ multihop_ttl = fil_neighbor.get('multihop_ttl', None)
+ if multihop_ttl is not None:
+ ebgp_multihop['multihop_ttl'] = multihop_ttl
+ fil_neighbor.pop('multihop_ttl')
+ local_as = {}
+ asn = fil_neighbor.get('as', None)
+ if asn is not None:
+ local_as['as'] = asn
+ fil_neighbor.pop('as')
+ no_prepend = fil_neighbor.get('no_prepend', None)
+ if no_prepend is not None:
+ local_as['no_prepend'] = no_prepend
+ fil_neighbor.pop('no_prepend')
+ replace_as = fil_neighbor.get('replace_as', None)
+ if replace_as is not None:
+ local_as['replace_as'] = replace_as
+ fil_neighbor.pop('replace_as')
+ bfd = {}
+ bfd_enabled = fil_neighbor.get('bfd_enabled', None)
+ if bfd_enabled is not None:
+ bfd['enabled'] = bfd_enabled
+ fil_neighbor.pop('bfd_enabled')
+ check_failure = fil_neighbor.get('check_failure', None)
+ if check_failure is not None:
+ bfd['check_failure'] = check_failure
+ fil_neighbor.pop('check_failure')
+ profile = fil_neighbor.get('profile', None)
+ if profile is not None:
+ bfd['profile'] = profile
+ fil_neighbor.pop('profile')
+ if auth_pwd:
+ fil_neighbor['auth_pwd'] = auth_pwd
+ if ebgp_multihop:
+ fil_neighbor['ebgp_multihop'] = ebgp_multihop
+ if local_as:
+ fil_neighbor['local_as'] = local_as
+ if bfd:
+ fil_neighbor['bfd'] = bfd
+ if fil_neighbor:
+ fil_neighbors.append(fil_neighbor)
+ if fil_neighbors:
+ tmp['neighbors'] = fil_neighbors
+ filtered_data.append(tmp)
+ return filtered_data
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors_af/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors_af/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors_af/__init__.py
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
new file mode 100644
index 000000000..26119b61c
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/bgp_neighbors_af/bgp_neighbors_af.py
@@ -0,0 +1,222 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic bgp_neighbors_af 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.utils.utils import (
+ remove_empties_from_list
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.bgp_neighbors_af.bgp_neighbors_af import Bgp_neighbors_afArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.bgp_utils import (
+ get_all_bgp_neighbors,
+ get_from_params_map,
+ update_bgp_nbr_pg_ip_afi_dict,
+ update_bgp_nbr_pg_prefix_limit_dict
+)
+
+
+class Bgp_neighbors_afFacts(object):
+ """ The sonic bgp_neighbors_af fact class
+ """
+
+ neighbor_af_params_map = {
+ 'afi': 'afi-safi-name',
+ 'route_reflector_client': 'route-reflector-client',
+ 'route_server_client': 'route-server-client',
+ 'allowas_in_origin': ['allow-own-as', 'origin'],
+ 'allowas_in_value': ['allow-own-as', 'as-count'],
+ 'in_route_name': ['apply-policy', 'import-policy'],
+ 'out_route_name': ['apply-policy', 'export-policy'],
+ 'activate': 'enabled',
+ 'prefix_list_in': ['prefix-list', 'import-policy'],
+ 'prefix_list_out': ['prefix-list', 'export-policy'],
+ 'ipv4_unicast': 'ipv4-unicast',
+ 'ipv6_unicast': 'ipv6-unicast',
+ 'l2vpn_evpn': ['l2vpn-evpn', 'prefix-limit']
+ }
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Bgp_neighbors_afArgs.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 fill_route_map(self, data):
+ for route_map_key in ['out_route_name', 'in_route_name']:
+ if route_map_key in data:
+ route_map = data['route_map']
+ for e_route in data[route_map_key]:
+ direction = route_map_key.split('_', maxsplit=1)[0]
+ route_map.append({'name': e_route, 'direction': direction})
+ data.pop(route_map_key)
+
+ def normalize_neighbors_af_data(self, neighbors):
+ norm_neighbors = []
+
+ for nei_data in neighbors:
+ norm_neighbor = {}
+
+ neighbor = nei_data.get('neighbor-address', None)
+ if not neighbor:
+ continue
+ norm_neighbor['neighbor'] = neighbor
+ norm_neighbor['address_family'] = []
+ nei_afs = nei_data.get('afi-safis', None)
+ if not nei_afs:
+ if norm_neighbor:
+ norm_neighbors.append(norm_neighbor)
+ continue
+ nei_afs = nei_afs.get('afi-safi', None)
+ if not nei_afs:
+ if norm_neighbor:
+ norm_neighbors.append(norm_neighbor)
+ continue
+ norm_neighbor_afs = []
+ for nei_af in nei_afs:
+ norm_nei_af = get_from_params_map(self.neighbor_af_params_map, nei_af)
+ if norm_nei_af:
+ if 'activate' not in norm_nei_af:
+ norm_nei_af['activate'] = False
+ if 'route_server_client' not in norm_nei_af:
+ norm_nei_af['route_server_client'] = False
+ norm_nei_af['route_map'] = []
+ self.fill_route_map(norm_nei_af)
+
+ allowas_in = {}
+ allowas_in_origin = norm_nei_af.get('allowas_in_origin', None)
+ if allowas_in_origin is not None:
+ allowas_in['origin'] = allowas_in_origin
+ norm_nei_af.pop('allowas_in_origin')
+
+ allowas_in_value = norm_nei_af.get('allowas_in_value', None)
+ if allowas_in_value is not None:
+ allowas_in['value'] = allowas_in_value
+ norm_nei_af.pop('allowas_in_value')
+ if allowas_in:
+ norm_nei_af['allowas_in'] = allowas_in
+
+ 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'])
+ if ip_afi:
+ norm_nei_af['ip_afi'] = ip_afi
+ if 'prefix-limit' in ipv4_unicast and 'config' in ipv4_unicast['prefix-limit']:
+ prefix_limit = update_bgp_nbr_pg_prefix_limit_dict(ipv4_unicast['prefix-limit']['config'])
+ if prefix_limit:
+ norm_nei_af['prefix_limit'] = prefix_limit
+ norm_nei_af.pop('ipv4_unicast')
+ elif ipv6_unicast:
+ if 'config' in ipv6_unicast:
+ ip_afi = update_bgp_nbr_pg_ip_afi_dict(ipv6_unicast['config'])
+ if ip_afi:
+ norm_nei_af['ip_afi'] = ip_afi
+ if 'prefix-limit' in ipv6_unicast and 'config' in ipv6_unicast['prefix-limit']:
+ prefix_limit = update_bgp_nbr_pg_prefix_limit_dict(ipv6_unicast['prefix-limit']['config'])
+ 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:
+ norm_neighbor['address_family'] = norm_neighbor_afs
+ if norm_neighbor:
+ norm_neighbors.append(norm_neighbor)
+ return norm_neighbors
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for BGP
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = list()
+ if connection: # just for linting purposes, remove
+ pass
+
+ if not data:
+ data = get_all_bgp_neighbors(self._module)
+
+ new_data = []
+ for conf in data:
+ if not conf:
+ continue
+ new_item = {}
+ new_item['bgp_as'] = conf['bgp_as']
+ new_item['vrf_name'] = conf['vrf_name']
+ neighbors = conf.get('neighbors', None)
+ if not neighbors:
+ new_data.append(new_item)
+ continue
+ neighbors = neighbors.get('neighbor', None)
+ if not neighbors:
+ new_data.append(new_item)
+ continue
+
+ new_neighbors = self.normalize_neighbors_af_data(neighbors)
+ if new_neighbors:
+ new_item['neighbors'] = new_neighbors
+ if new_item:
+ new_data.append(new_item)
+
+ # operate on a collection of resource x
+ for conf in new_data:
+ if conf:
+ obj = self.render_config(self.generated_spec, conf)
+ # split the config into instances of the resource
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('bgp_neighbors_af', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': remove_empties_from_list(objs)})
+ facts['bgp_neighbors_af'] = 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
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
new file mode 100644
index 000000000..75622632a
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/facts.py
@@ -0,0 +1,101 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 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 facts class for sonic
+this file validates each subset of facts and selectively
+calls the appropriate facts gathering function
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.facts.facts import FactsArgs
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import (
+ FactsBase,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.vlans.vlans import VlansFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.interfaces.interfaces import InterfacesFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.l2_interfaces.l2_interfaces import L2_interfacesFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.l3_interfaces.l3_interfaces import L3_interfacesFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.bgp.bgp import BgpFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.bgp_af.bgp_af import Bgp_afFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.bgp_neighbors.bgp_neighbors import Bgp_neighborsFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.bgp_neighbors_af.bgp_neighbors_af import Bgp_neighbors_afFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.bgp_as_paths.bgp_as_paths import Bgp_as_pathsFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.bgp_communities.bgp_communities import Bgp_communitiesFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.bgp_ext_communities.bgp_ext_communities import (
+ Bgp_ext_communitiesFacts,
+)
+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.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
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.port_breakout.port_breakout import Port_breakoutFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.aaa.aaa import AaaFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.tacacs_server.tacacs_server import Tacacs_serverFacts
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.system.system import SystemFacts
+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
+
+FACT_LEGACY_SUBSETS = {}
+FACT_RESOURCE_SUBSETS = dict(
+ vlans=VlansFacts,
+ interfaces=InterfacesFacts,
+ l2_interfaces=L2_interfacesFacts,
+ l3_interfaces=L3_interfacesFacts,
+ lag_interfaces=Lag_interfacesFacts,
+ bgp=BgpFacts,
+ bgp_af=Bgp_afFacts,
+ bgp_neighbors=Bgp_neighborsFacts,
+ bgp_neighbors_af=Bgp_neighbors_afFacts,
+ bgp_as_paths=Bgp_as_pathsFacts,
+ bgp_communities=Bgp_communitiesFacts,
+ bgp_ext_communities=Bgp_ext_communitiesFacts,
+ mclag=MclagFacts,
+ prefix_lists=Prefix_listsFacts,
+ vrfs=VrfsFacts,
+ vxlans=VxlansFacts,
+ users=UsersFacts,
+ system=SystemFacts,
+ port_breakout=Port_breakoutFacts,
+ aaa=AaaFacts,
+ tacacs_server=Tacacs_serverFacts,
+ radius_server=Radius_serverFacts,
+ static_routes=Static_routesFacts,
+ ntp=NtpFacts,
+)
+
+
+class Facts(FactsBase):
+ """ The fact class for sonic
+ """
+
+ VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys())
+ VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys())
+
+ def __init__(self, module):
+ super(Facts, self).__init__(module)
+
+ def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None):
+ """ Collect the facts for sonic
+
+ :param legacy_facts_type: List of legacy facts types
+ :param resource_facts_type: List of resource fact types
+ :param data: previously collected conf
+ :rtype: dict
+ :return: the facts gathered
+ """
+ netres_choices = FactsArgs.argument_spec['gather_network_resources'].get('choices', [])
+ if self.VALID_RESOURCE_SUBSETS:
+ self.get_network_resources_facts(FACT_RESOURCE_SUBSETS, resource_facts_type, data)
+
+ if self.VALID_LEGACY_GATHER_SUBSETS:
+ self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type)
+
+ return self.ansible_facts, self._warnings
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/interfaces/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/interfaces/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/interfaces/__init__.py
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
new file mode 100644
index 000000000..a36b5d3c0
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/interfaces/interfaces.py
@@ -0,0 +1,147 @@
+#
+# -*- 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 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
+
+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.interfaces.interfaces import InterfacesArgs
+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 InterfacesFacts(object):
+ """ The sonic interfaces fact class
+ """
+ loop_backs = ","
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = 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 get_all_interfaces(self):
+ """Get all the interfaces available in chassis"""
+ all_interfaces = {}
+ request = [{"path": "data/openconfig-interfaces:interfaces", "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)
+
+ 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):
+ """ Populate the facts for interfaces
+ :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:
+ # typically data is populated from the current device configuration
+ # data = connection.get('show running-config | section ^interface')
+ # using mock data instead
+ data = self.get_all_interfaces()
+ # operate on a collection of resource x
+ self.reset_loop_backs()
+
+ for conf in data:
+ if conf:
+ obj = self.render_config(self.generated_spec, conf)
+ obj = self.transform_config(obj)
+ # split the config into instances of the resource
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('interfaces', None)
+ facts = {}
+ if objs:
+ facts['interfaces'] = []
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ if params:
+ facts['interfaces'].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 transform_config(self, conf):
+
+ exist_cfg = conf['config']
+ trans_cfg = None
+
+ is_loop_back = False
+ name = conf['name']
+ if name.startswith('Loopback'):
+ is_loop_back = True
+ pos = name.find('|')
+ 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()
+ 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['mtu'] = exist_cfg['mtu'] if exist_cfg.get('mtu') else 9100
+
+ return trans_cfg
+
+ def reset_loop_backs(self):
+ self.loop_backs = ","
+
+ def update_loop_backs(self, loop_back):
+ self.loop_backs += "{0},".format(loop_back)
+
+ def is_loop_back_already_esist(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/l2_interfaces/l2_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py
new file mode 100644
index 000000000..07d7f97dd
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l2_interfaces/l2_interfaces.py
@@ -0,0 +1,160 @@
+#
+# -*- 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 l2_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
+
+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.l2_interfaces.l2_interfaces import L2_interfacesArgs
+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 L2_interfacesFacts(object):
+ """ The sonic l2_interfaces fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = L2_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 vlan_range_to_list(self, in_range):
+ range_bounds = in_range.split('-')
+ range_bottom = int(range_bounds[0])
+ range_top = int(range_bounds[1]) + 1
+ vlan_list = list(range(range_bottom, range_top))
+ vlan_dict_list = []
+ for vlan in vlan_list:
+ vlan_dict_list.append({'vlan': vlan})
+ return vlan_dict_list
+
+ def get_l2_interfaces_from_interfaces(self, interfaces):
+ l2_interfaces = []
+
+ for intf in interfaces:
+ name = intf['name']
+ key = 'openconfig-if-ethernet:ethernet'
+ if name.startswith('PortChannel'):
+ key = 'openconfig-if-aggregate:aggregation'
+ eth_det = intf.get(key)
+ if eth_det:
+ open_cfg_vlan = eth_det.get('openconfig-vlan:switched-vlan')
+ if open_cfg_vlan:
+ new_det = dict()
+ new_det['name'] = name
+ if name == "eth0":
+ continue
+ if (open_cfg_vlan['config'].get('access-vlan')):
+ new_det['access'] = dict({'vlan': open_cfg_vlan['config'].get('access-vlan')})
+ if (open_cfg_vlan['config'].get('trunk-vlans')):
+ 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.
+ 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))
+ else:
+ new_det['trunk']['allowed_vlans'].append({'vlan': vlan})
+ l2_interfaces.append(new_det)
+
+ return l2_interfaces
+
+ def get_all_l2_interfaces(self):
+ """Get all the l2_interfaces available in chassis"""
+ l2_interfaces = {}
+ request = [{"path": "data/openconfig-interfaces:interfaces", "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)
+
+ if "openconfig-interfaces:interfaces" in response[0][1]:
+ interfaces = response[0][1].get("openconfig-interfaces:interfaces", {})
+ if interfaces.get("interface"):
+ interfaces = interfaces['interface']
+ l2_interfaces = self.get_l2_interfaces_from_interfaces(interfaces)
+ else:
+ l2_interfaces = {}
+
+ return l2_interfaces
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for l2_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:
+ # typically data is populated from the current device configuration
+ # data = connection.get('show running-config | section ^interface')
+ # using mock data instead
+ data = self.get_all_l2_interfaces()
+
+ objs = list()
+ for conf in data:
+ if conf:
+ obj = self.render_config(self.generated_spec, conf)
+ # split the config into instances of the resource
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('l2_interfaces', None)
+ facts = {}
+ if objs:
+ facts['l2_interfaces'] = []
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ for cfg in params['config']:
+ facts['l2_interfaces'].append(utils.remove_empties(cfg))
+ 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
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_interfaces/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_interfaces/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_interfaces/__init__.py
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
new file mode 100644
index 000000000..69a6dcd44
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/l3_interfaces/l3_interfaces.py
@@ -0,0 +1,185 @@
+#
+# -*- 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 l3_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
+
+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.l3_interfaces.l3_interfaces import L3_interfacesArgs
+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 L3_interfacesFacts(object):
+ """ The sonic l3_interfaces fact class
+ """
+
+ loop_backs = ","
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = L3_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 get_l3_interfaces(self):
+ url = "data/openconfig-interfaces:interfaces/interface"
+ 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), code=exc.code)
+
+ l3_lists = []
+ if "openconfig-interfaces:interface" in response[0][1]:
+ l3_lists = response[0][1].get("openconfig-interfaces:interface", [])
+
+ l3_configs = []
+ for l3 in l3_lists:
+ l3_dict = dict()
+ l3_name = l3["name"]
+ if l3_name == "eth0":
+ continue
+
+ l3_dict['name'] = l3_name
+
+ ip = None
+ anycast_addr = list()
+ if l3.get('openconfig-vlan:routed-vlan'):
+ ip = l3['openconfig-vlan:routed-vlan']
+ if ip.get('openconfig-if-ip:ipv4', None) and ip['openconfig-if-ip:ipv4'].get('openconfig-interfaces-ext:sag-ipv4', None):
+ if ip['openconfig-if-ip:ipv4']['openconfig-interfaces-ext:sag-ipv4'].get('config', None):
+ if ip['openconfig-if-ip:ipv4']['openconfig-interfaces-ext:sag-ipv4']['config'].get('static-anycast-gateway', None):
+ anycast_addr = ip['openconfig-if-ip:ipv4']['openconfig-interfaces-ext:sag-ipv4']['config']['static-anycast-gateway']
+ else:
+ ip = l3.get('subinterfaces', {}).get('subinterface', [{}])[0]
+
+ l3_dict['ipv4'] = dict()
+ l3_ipv4 = list()
+ if anycast_addr:
+ l3_dict['ipv4']['anycast_addresses'] = anycast_addr
+ elif 'openconfig-if-ip:ipv4' in ip and 'addresses' in ip['openconfig-if-ip:ipv4'] and 'address' in ip['openconfig-if-ip:ipv4']['addresses']:
+ for ipv4 in ip['openconfig-if-ip:ipv4']['addresses']['address']:
+ if ipv4.get('config') and ipv4.get('config').get('ip'):
+ temp = dict()
+ temp['address'] = str(ipv4['config']['ip']) + '/' + str(ipv4['config']['prefix-length'])
+ temp['secondary'] = ipv4['config']['secondary']
+ l3_ipv4.append(temp)
+ if l3_ipv4:
+ l3_dict['ipv4']['addresses'] = l3_ipv4
+
+ l3_dict['ipv6'] = dict()
+ l3_ipv6 = list()
+ if 'openconfig-if-ip:ipv6' in ip:
+ if 'addresses' in ip['openconfig-if-ip:ipv6'] and 'address' in ip['openconfig-if-ip:ipv6']['addresses']:
+ for ipv6 in ip['openconfig-if-ip:ipv6']['addresses']['address']:
+ if ipv6.get('config') and ipv6.get('config').get('ip'):
+ temp = dict()
+ temp['address'] = str(ipv6['config']['ip']) + '/' + str(ipv6['config']['prefix-length'])
+ l3_ipv6.append(temp)
+ if l3_ipv6:
+ l3_dict['ipv6']['addresses'] = l3_ipv6
+ if 'config' in ip['openconfig-if-ip:ipv6'] and 'enabled' in ip['openconfig-if-ip:ipv6']['config']:
+ l3_dict['ipv6']['enabled'] = ip['openconfig-if-ip:ipv6']['config']['enabled']
+
+ l3_configs.append(l3_dict)
+ return l3_configs
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for l3_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:
+ resources = self.get_l3_interfaces()
+ objs = []
+ for resource in resources:
+ if resource:
+ obj = self.render_config(self.generated_spec, resource)
+ obj = self.transform_config(obj)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('l3_interfaces', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['l3_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
+ """
+ return conf
+
+ def transform_config(self, conf):
+ exist_cfg = conf
+ trans_cfg = None
+
+ is_loop_back = False
+ name = exist_cfg['name']
+ if name.startswith('Loopback'):
+ is_loop_back = True
+ pos = name.find('|')
+ 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()
+ trans_cfg['name'] = name
+ if is_loop_back:
+ self.update_loop_backs(name)
+ trans_cfg['ipv4'] = exist_cfg.get('ipv4', {})
+ trans_cfg['ipv6'] = exist_cfg.get('ipv6', {})
+
+ return trans_cfg
+
+ def reset_loop_backs(self):
+ self.loop_backs = ","
+
+ def update_loop_backs(self, loop_back):
+ self.loop_backs += "{Loopback},".format(Loopback=loop_back)
+
+ def is_loop_back_already_esist(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/lag_interfaces/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lag_interfaces/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lag_interfaces/__init__.py
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
new file mode 100644
index 000000000..728196813
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/lag_interfaces/lag_interfaces.py
@@ -0,0 +1,135 @@
+#
+# -*- 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 lag_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
+
+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.lag_interfaces.lag_interfaces import Lag_interfacesArgs
+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 Lag_interfacesFacts(object):
+ """ The sonic lag_interfaces fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Lag_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 get_all_portchannels(self):
+ """Get all the interfaces available in chassis"""
+ request = [{"path": "data/sonic-portchannel:sonic-portchannel", "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)
+ if response[0][1]:
+ data = response[0][1]['sonic-portchannel:sonic-portchannel']
+ else:
+ data = []
+ if data is not None:
+ if "PORTCHANNEL_MEMBER" in data:
+ portchannel_members_list = data["PORTCHANNEL_MEMBER"]["PORTCHANNEL_MEMBER_LIST"]
+ else:
+ portchannel_members_list = []
+ if "PORTCHANNEL" in data:
+ portchannel_list = data["PORTCHANNEL"]["PORTCHANNEL_LIST"]
+ else:
+ portchannel_list = []
+ if portchannel_list:
+ for i in portchannel_list:
+ if not any(d["name"] == i["name"] for d in portchannel_members_list):
+ portchannel_members_list.append({'ifname': None, 'name': i['name']})
+ if data:
+ return portchannel_members_list
+ else:
+ return []
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for lag_interfaces
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+ if not data:
+ data = self.get_all_portchannels()
+ # operate on a collection of resource x
+ for conf in data:
+ if conf:
+ obj = self.render_config(self.generated_spec, conf)
+ obj = self.transform_config(obj)
+ if obj:
+ self.merge_portchannels(objs, obj)
+ facts = {}
+ if objs:
+ facts['lag_interfaces'] = []
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ for cfg in params['config']:
+ facts['lag_interfaces'].append(cfg)
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ return conf
+
+ def transform_config(self, conf):
+ trans_cfg = dict()
+ trans_cfg['name'] = conf['name']
+ trans_cfg['members'] = dict()
+ if conf['ifname']:
+ interfaces = list()
+ interface = {'member': conf['ifname']}
+ interfaces.append(interface)
+ trans_cfg['members'] = {'interfaces': interfaces}
+ return trans_cfg
+
+ def merge_portchannels(self, configs, conf):
+ if len(configs) == 0:
+ configs.append(conf)
+ else:
+ new_interface = None
+ if conf.get('members') and conf['members'].get('interfaces'):
+ new_interface = conf['members']['interfaces'][0]
+ else:
+ configs.append(conf)
+ if new_interface:
+ matched = next((cfg for cfg in configs if cfg['name'] == conf['name']), None)
+ if matched and matched.get('members'):
+ ext_interfaces = matched.get('members').get('interfaces', [])
+ ext_interfaces.append(new_interface)
+ else:
+ configs.append(conf)
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
new file mode 100644
index 000000000..69864cdf9
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/mclag/mclag.py
@@ -0,0 +1,139 @@
+#
+# -*- 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 mclag 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.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.mclag.mclag import MclagArgs
+from ansible.module_utils.connection import ConnectionError
+
+GET = "get"
+
+
+class MclagFacts(object):
+ """ The sonic mclag fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = MclagArgs.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 get_all_mclag(self):
+ """Get all the mclag available in chassis"""
+ request = [{"path": "data/openconfig-mclag:mclag", "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)
+ if ('openconfig-mclag:mclag' in response[0][1]):
+ data = response[0][1]['openconfig-mclag:mclag']
+ else:
+ data = {}
+ return data
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for mclag
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = None
+ if not data:
+ data = self.get_all_mclag()
+ if data:
+ objs = self.render_config(self.generated_spec, data)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['mclag'] = 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 = self.parse_sonic_mclag(spec, conf)
+ return config
+
+ def parse_sonic_mclag(self, spec, conf):
+ config = {}
+ portchannels_list = []
+ if conf:
+ domain_data = None
+ if conf.get('mclag-domains', None) and conf['mclag-domains'].get('mclag-domain', None):
+ domain_data = conf['mclag-domains']['mclag-domain'][0]
+ if domain_data:
+ domain_id = domain_data['domain-id']
+ config['domain_id'] = domain_id
+ domain_config = domain_data.get('config', None)
+ if domain_config:
+ if domain_config.get('session-timeout', None):
+ config['session_timeout'] = domain_config['session-timeout']
+ if domain_config.get('keepalive-interval', None):
+ config['keepalive'] = domain_config['keepalive-interval']
+ if domain_config.get('source-address', None):
+ config['source_address'] = domain_config['source-address']
+ if domain_config.get('peer-address', None):
+ config['peer_address'] = domain_config['peer-address']
+ if domain_config.get('peer-link', None):
+ config['peer_link'] = domain_config['peer-link']
+ if domain_config.get('mclag-system-mac', None):
+ config['system_mac'] = domain_config['mclag-system-mac']
+
+ if conf.get('vlan-interfaces', None) and conf['vlan-interfaces'].get('vlan-interface', None):
+ vlans_list = []
+ vlan_data = conf['vlan-interfaces']['vlan-interface']
+ for vlan in vlan_data:
+ vlans_list.append({'vlan': vlan['name']})
+ if vlans_list:
+ config['unique_ip'] = {'vlans': vlans_list}
+
+ if conf.get('interfaces', None) and conf['interfaces'].get('interface', None):
+ portchannels_list = []
+ po_data = conf['interfaces']['interface']
+ for po in po_data:
+ if po.get('config', None) and po['config'].get('mclag-domain-id', None) and domain_id == domain_data['domain-id']:
+ portchannels_list.append({'lag': po['name']})
+ if portchannels_list:
+ config['members'] = {'portchannels': portchannels_list}
+
+ return config
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ntp/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ntp/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ntp/__init__.py
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
new file mode 100644
index 000000000..a47142b47
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/ntp/ntp.py
@@ -0,0 +1,153 @@
+#
+# -*- 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 ntp 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.ntp.ntp import NtpArgs
+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 NtpFacts(object):
+ """ The sonic ntp fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = NtpArgs.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 ntp
+ :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_ntp_configuration()
+
+ obj = self.render_config(self.generated_spec, data)
+
+ ansible_facts['ansible_network_resources'].pop('ntp', None)
+ facts = {}
+ if obj:
+ params = utils.validate_config(self.argument_spec, {'config': obj})
+ facts['ntp'] = 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_ntp_configuration(self):
+ """Get all NTP configuration"""
+
+ all_ntp_request = [{"path": "data/openconfig-system:system/ntp", "method": GET}]
+ all_ntp_response = []
+ try:
+ all_ntp_response = edit_config(self._module, to_request(self._module, all_ntp_request))
+ except ConnectionError as exc:
+ self._module.fail_json(msg=str(exc), code=exc.code)
+
+ all_ntp_config = dict()
+ if 'openconfig-system:ntp' in all_ntp_response[0][1]:
+ all_ntp_config = all_ntp_response[0][1].get('openconfig-system:ntp', {})
+
+ ntp_global_config = dict()
+ if 'config' in all_ntp_config:
+ ntp_global_config = all_ntp_config.get('config', {})
+
+ ntp_servers = []
+ if 'servers' in all_ntp_config:
+ ntp_servers = all_ntp_config['servers'].get('server', [])
+
+ ntp_keys = []
+ if 'ntp-keys' in all_ntp_config:
+ ntp_keys = all_ntp_config['ntp-keys'].get('ntp-key', [])
+
+ ntp_config = dict()
+
+ if 'network-instance' in ntp_global_config:
+ 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:
+ ntp_config['source_interfaces'] = ntp_global_config['source-interface']
+
+ if 'trusted-key' in ntp_global_config:
+ ntp_config['trusted_keys'] = ntp_global_config['trusted-key']
+
+ servers = []
+ for ntp_server in ntp_servers:
+ if 'config' in ntp_server:
+ server = {}
+ server['address'] = ntp_server['config'].get('address', None)
+ if 'key-id' in ntp_server['config']:
+ server['key_id'] = ntp_server['config']['key-id']
+ server['minpoll'] = ntp_server['config'].get('minpoll', None)
+ server['maxpoll'] = ntp_server['config'].get('maxpoll', None)
+ servers.append(server)
+ ntp_config['servers'] = servers
+
+ keys = []
+ for ntp_key in ntp_keys:
+ if 'config' in ntp_key:
+ key = {}
+ key['encrypted'] = ntp_key['config'].get('encrypted', None)
+ key['key_id'] = ntp_key['config'].get('key-id', None)
+ key_type_str = ntp_key['config'].get('key-type', None)
+ key_type = key_type_str.split(":", 1)[-1]
+ key['key_type'] = key_type
+ key['key_value'] = ntp_key['config'].get('key-value', None)
+ keys.append(key)
+ ntp_config['ntp_keys'] = keys
+
+ return ntp_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
new file mode 100644
index 000000000..938bd6423
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/port_breakout/port_breakout.py
@@ -0,0 +1,125 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic port breakout 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
+import json
+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_breakout.port_breakout import Port_breakoutArgs
+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_breakout_mode,
+)
+from ansible.module_utils.connection import ConnectionError
+
+GET = "get"
+POST = "post"
+
+
+class Port_breakoutFacts(object):
+ """ The sonic port breakout fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Port_breakoutArgs.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_breakout
+ :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:
+ # typically data is populated from the current device configuration
+ # data = connection.get('show running-config | section ^interface')
+ # using mock data instead
+ data = self.get_all_port_breakout()
+
+ objs = list()
+ 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_breakout', None)
+ facts = {}
+ if objs:
+ facts['port_breakout'] = []
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ if params:
+ facts['port_breakout'].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_all_port_breakout(self):
+ """Get all the port_breakout configured in the device"""
+ request = [{"path": "operations/sonic-port-breakout:breakout_capabilities", "method": POST}]
+ port_breakout_list = []
+ 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('[')]
+ norm_port_breakout = {'name': name, 'mode': mode}
+ mode = get_breakout_mode(self._module, name)
+ if mode:
+ norm_port_breakout['mode'] = mode
+ port_breakout_list.append(norm_port_breakout)
+
+ return port_breakout_list
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/prefix_lists/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/prefix_lists/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/prefix_lists/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/prefix_lists/prefix_lists.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/prefix_lists/prefix_lists.py
new file mode 100644
index 000000000..e246b5720
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/prefix_lists/prefix_lists.py
@@ -0,0 +1,158 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The sonic prefix_lists 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.prefix_lists.prefix_lists import Prefix_listsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+
+
+def prefix_set_cfg_parse(unparsed_prefix_set):
+ '''Parse the raw input configuration JSON representation for the prefix set specified
+ by the input "unparsed_prefix_set" input parameter. Parse the information to
+ convert it to a dictionary matching the "argspec" for the "prefix_lists" resource
+ module.'''
+
+ parsed_prefix_set = dict()
+ if not unparsed_prefix_set.get("config"):
+ return parsed_prefix_set
+ parsed_prefix_set['name'] = unparsed_prefix_set['name']
+ pfx_cfg = unparsed_prefix_set['config']
+ if pfx_cfg.get('mode') and isinstance((pfx_cfg['mode']), str):
+ parsed_prefix_set['afi'] = pfx_cfg['mode'].lower()
+ if unparsed_prefix_set.get('openconfig-routing-policy-ext:extended-prefixes'):
+ prefix_lists_container = \
+ unparsed_prefix_set['openconfig-routing-policy-ext:extended-prefixes']
+ if not prefix_lists_container.get("extended-prefix"):
+ return parsed_prefix_set
+ prefix_lists_unparsed = prefix_lists_container['extended-prefix']
+
+ prefix_lists_parsed = []
+ for prefix_entry_unparsed in prefix_lists_unparsed:
+ if not prefix_entry_unparsed.get('config'):
+ continue
+ if not prefix_entry_unparsed['config'].get('action'):
+ continue
+ prefix_entry_cfg = prefix_entry_unparsed['config']
+ prefix_parsed = dict()
+ prefix_parsed['action'] = prefix_entry_cfg['action'].lower()
+ if not prefix_entry_unparsed.get('ip-prefix'):
+ continue
+ if not prefix_entry_unparsed.get('sequence-number'):
+ continue
+
+ prefix_parsed['prefix'] = prefix_entry_unparsed['ip-prefix']
+ prefix_parsed['sequence'] = prefix_entry_unparsed['sequence-number']
+ if (prefix_entry_unparsed.get('masklength-range') and
+ (not prefix_entry_unparsed['masklength-range'] == 'exact')):
+ mask = int(prefix_parsed['prefix'].split('/')[1])
+ ge_le = prefix_entry_unparsed['masklength-range'].split('..')
+ ge_bound = int(ge_le[0])
+ if ge_bound != mask:
+ prefix_parsed['ge'] = ge_bound
+ pfx_len = 32 if parsed_prefix_set['afi'] == 'ipv4' else 128
+ le_bound = int(ge_le[1])
+ if le_bound != pfx_len:
+ prefix_parsed['le'] = le_bound
+ prefix_lists_parsed.append(prefix_parsed)
+ parsed_prefix_set['prefixes'] = prefix_lists_parsed
+ return parsed_prefix_set
+
+
+class Prefix_listsFacts:
+ """ The sonic prefix_lists fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Prefix_listsArgs.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 get_all_prefix_sets(self):
+ '''Execute a REST "GET" API to fetch all of the current prefix list configuration
+ from the target device.'''
+
+ pfx_fetch_spec = "openconfig-routing-policy:routing-policy/defined-sets/prefix-sets"
+ pfx_resp_key = "openconfig-routing-policy:prefix-sets"
+ pfx_set_key = "prefix-set"
+ # pfx_short_spec = "openconfig-routing-policy:prefix-set"
+ url = "data/%s" % pfx_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))
+
+ prefix_lists_unparsed = []
+ resp_prefix_set = response[0][1].get(pfx_resp_key, None)
+ if resp_prefix_set:
+ prefix_lists_unparsed = resp_prefix_set.get(pfx_set_key, None)
+ return prefix_lists_unparsed
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for prefix_lists
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if connection: # (comment by Ansible): just for linting purposes, remove
+ pass
+
+ if not data:
+ # Fetch data from the current device configuration
+ # (Skip if operating on previously fetched configuration.)
+ data = self.get_all_prefix_sets()
+
+ # split the unparsed prefix configuration list into a list
+ # of parsed prefix set "instances" (dictonary "objects").
+ prefix_sets = list()
+ for prefix_set_cfg in data:
+ prefix_set = prefix_set_cfg_parse(prefix_set_cfg)
+ if prefix_set:
+ prefix_sets.append(prefix_set)
+
+ ansible_facts['ansible_network_resources'].pop('prefix_lists', None)
+ facts = {}
+ if prefix_sets:
+ params = utils.validate_config(self.argument_spec,
+ {'config': remove_empties_from_list(prefix_sets)})
+ facts['prefix_lists'] = 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/radius_server/radius_server.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/radius_server/radius_server.py
new file mode 100644
index 000000000..72593b225
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/radius_server/radius_server.py
@@ -0,0 +1,168 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic tacas server 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
+import json
+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.radius_server.radius_server import Radius_serverArgs
+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 Radius_serverFacts(object):
+ """ The sonic tacas server fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Radius_serverArgs.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 radius_server
+ :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 = None
+ 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_all_radius_server()
+
+ obj = self.render_config(self.generated_spec, data)
+
+ ansible_facts['ansible_network_resources'].pop('radius_server', None)
+ facts = {}
+ if obj:
+ facts['radius_server'] = {}
+ params = utils.validate_config(self.argument_spec, {'config': obj})
+ if params:
+ facts['radius_server'] = 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_radius_server(self):
+ """Get all the radius_server configured in the device"""
+ request = [{"path": "data/openconfig-system:system/aaa/server-groups/server-group=RADIUS/config", "method": GET}]
+ radius_server_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)
+
+ if "openconfig-system:config" in response[0][1]:
+ raw_radius_global_data = response[0][1].get("openconfig-system:config", {})
+
+ 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:
+ 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']
+
+ request = [{"path": "data/openconfig-system:system/aaa/server-groups/server-group=RADIUS/openconfig-aaa-radius-ext:radius/config", "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)
+
+ if "openconfig-aaa-radius-ext:config" in response[0][1]:
+ raw_radius_ext_global_data = response[0][1].get("openconfig-aaa-radius-ext:config", {})
+
+ if 'nas-ip-address' in raw_radius_ext_global_data:
+ radius_server_data['nas_ip'] = raw_radius_ext_global_data['nas-ip-address']
+ if 'retransmit-attempts' in raw_radius_ext_global_data:
+ radius_server_data['retransmit'] = raw_radius_ext_global_data['retransmit-attempts']
+ if 'statistics' in raw_radius_ext_global_data:
+ radius_server_data['statistics'] = raw_radius_ext_global_data['statistics']
+
+ request = [{"path": "data/openconfig-system:system/aaa/server-groups/server-group=RADIUS/servers", "method": GET}]
+ hosts = []
+ 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_radius_server_list = []
+ if "openconfig-system:servers" in response[0][1]:
+ raw_radius_server_list = response[0][1].get("openconfig-system:servers", {}).get('server', [])
+
+ for radius_host in raw_radius_server_list:
+ host_data = {}
+ if 'address' in radius_host:
+ host_data['name'] = radius_host['address']
+ cfg = radius_host.get('config', None)
+ if cfg:
+ if 'auth-type' in cfg:
+ host_data['auth_type'] = cfg['auth-type']
+ if 'priority' in cfg:
+ host_data['priority'] = cfg['priority']
+ if 'vrf' in cfg:
+ host_data['vrf'] = cfg['vrf']
+ if 'timeout' in cfg:
+ host_data['timeout'] = cfg['timeout']
+ if radius_host.get('radius', None) and radius_host['radius'].get('config', None):
+ tacas_cfg = radius_host['radius']['config']
+ if tacas_cfg.get('auth-port', None):
+ host_data['port'] = tacas_cfg['auth-port']
+ if tacas_cfg.get('secret-key', None):
+ host_data['key'] = tacas_cfg['secret-key']
+ if tacas_cfg.get('openconfig-aaa-radius-ext:source-interface', None):
+ host_data['source_interface'] = tacas_cfg['openconfig-aaa-radius-ext:source-interface']
+ if tacas_cfg.get('retransmit-attempts', None):
+ host_data['retransmit'] = tacas_cfg['retransmit-attempts']
+ if host_data:
+ hosts.append(host_data)
+
+ if hosts:
+ radius_server_data['servers'] = {'host': hosts}
+
+ return radius_server_data
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/static_routes/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/static_routes/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/static_routes/__init__.py
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
new file mode 100644
index 000000000..f83566440
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/static_routes/static_routes.py
@@ -0,0 +1,173 @@
+#
+# -*- 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 static_routes 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.utils.utils import (
+ remove_empties_from_list
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.static_routes.static_routes import Static_routesArgs
+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'
+protocol_static_routes_path = 'protocols/protocol=STATIC,static/static-routes'
+
+
+class Static_routesFacts(object):
+ """ The sonic static_routes fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Static_routesArgs.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 static_routes
+ :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:
+ static_routes_config = self.get_static_routes(self._module)
+ data = self.update_static_routes(static_routes_config)
+ # operate on a collection of resource x
+ for conf in data:
+ if conf:
+ obj = self.render_config(self.generated_spec, conf)
+ # split the config into instances of the resource
+ if obj:
+ objs.append(obj)
+
+ ansible_facts['ansible_network_resources'].pop('static_routes', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': remove_empties_from_list(objs)})
+ facts['static_routes'] = 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_static_routes(self, module):
+ all_static_routes = []
+ vrfs = get_all_vrfs(module)
+ for vrf_name in vrfs:
+ get_path = '%s=%s/%s' % (network_instance_path, vrf_name, protocol_static_routes_path)
+ request = {'path': get_path, 'method': 'get'}
+ try:
+ response = edit_config(module, to_request(module, request))
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+ for resp in response:
+ if 'openconfig-network-instance:static-routes' in resp[1]:
+ static_routes_dict = resp[1].get('openconfig-network-instance:static-routes', {})
+ static_routes_dict['vrf'] = vrf_name
+ all_static_routes.append(static_routes_dict)
+ return all_static_routes
+
+ def update_static_routes(self, data):
+ static_vrf_list = []
+ for static_route in data:
+ static_vrf_dict = {}
+ static_route_list = static_route.get('static', [])
+ vrf_name = static_route.get('vrf', None)
+ static_list = []
+ for static in static_route_list:
+ static_dict = {}
+ prefix = static.get('prefix', None)
+ next_hops = static.get('next-hops', None)
+ next_hop_list = next_hops.get('next-hop', [])
+ next_hop_dict_list = []
+ for next_hop in next_hop_list:
+ next_hop_dict = {}
+ index_dict = {}
+ inf_ref = next_hop.get('interface-ref', {})
+ inf_ref_cfg = inf_ref.get('config', {})
+ interface = inf_ref_cfg.get('interface', None)
+ config = next_hop.get('config', {})
+ next_hop_attr = config.get('next-hop', None)
+ metric = config.get('metric', None)
+ nexthop_vrf = config.get('network-instance', None)
+ blackhole = config.get('blackhole', None)
+ track = config.get('track', None)
+ tag = config.get('tag', None)
+ if blackhole:
+ index_dict['blackhole'] = blackhole
+ if interface:
+ index_dict['interface'] = interface
+ if nexthop_vrf:
+ index_dict['nexthop_vrf'] = nexthop_vrf
+ if next_hop_attr:
+ index_dict['next_hop'] = next_hop_attr
+ if index_dict:
+ next_hop_dict['index'] = index_dict
+ if metric:
+ next_hop_dict['metric'] = metric
+ if track:
+ next_hop_dict['track'] = track
+ if tag:
+ next_hop_dict['tag'] = tag
+ if next_hop_dict:
+ next_hop_dict_list.append(next_hop_dict)
+ if prefix:
+ static_dict['prefix'] = prefix
+ if next_hop_dict_list:
+ static_dict['next_hops'] = next_hop_dict_list
+ if static_dict:
+ static_list.append(static_dict)
+ if static_list:
+ static_vrf_dict['static_list'] = static_list
+ if vrf_name:
+ static_vrf_dict['vrf_name'] = vrf_name
+ if static_vrf_dict:
+ static_vrf_list.append(static_vrf_dict)
+
+ return static_vrf_list
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/system/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/system/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/system/__init__.py
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
new file mode 100644
index 000000000..1d7a82d83
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/system/system.py
@@ -0,0 +1,143 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 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 system 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.sonic import (
+ to_request,
+ edit_config
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.system.system import SystemArgs
+
+GET = "get"
+
+
+class SystemFacts(object):
+ """ The sonic system fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = SystemArgs.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 get_system(self):
+ """Get system hostname available in chassis"""
+ request = [{"path": "data/openconfig-system:system/config", "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)
+ if ('openconfig-system:config' in response[0][1]):
+ data = response[0][1]['openconfig-system:config']
+ else:
+ data = {}
+ return data
+
+ def get_naming(self):
+ """Get interface_naming type available in chassis"""
+ request = [{"path": "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost", "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)
+ if ('sonic-device-metadata:DEVICE_METADATA_LIST' in response[0][1]):
+ intf_data = response[0][1]['sonic-device-metadata:DEVICE_METADATA_LIST']
+ if 'intf_naming_mode' in intf_data[0]:
+ data = intf_data[0]
+ else:
+ data = {}
+ return data
+
+ def get_anycast_addr(self):
+ """Get system anycast address available in chassis"""
+ request = [{"path": "data/sonic-sag:sonic-sag/SAG_GLOBAL/SAG_GLOBAL_LIST/", "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)
+ if ('sonic-sag:SAG_GLOBAL_LIST' in response[0][1]):
+ data = response[0][1]['sonic-sag:SAG_GLOBAL_LIST'][0]
+ else:
+ data = {}
+ return data
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for system
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ data = self.get_system()
+ intf_naming = self.get_naming()
+ if intf_naming:
+ data.update(intf_naming)
+ anycast_addr = self.get_anycast_addr()
+ if anycast_addr:
+ data.update(anycast_addr)
+ objs = []
+ objs = self.render_config(self.generated_spec, data)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['system'] = 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 = self.parse_sonic_system(spec, conf)
+ return config
+
+ def parse_sonic_system(self, spec, conf):
+ config = deepcopy(spec)
+ if conf:
+ if ('hostname' in conf) and (conf['hostname']):
+ config['hostname'] = conf['hostname']
+ if ('intf_naming_mode' in conf) and (conf['intf_naming_mode']):
+ config['interface_naming'] = conf['intf_naming_mode']
+ if ('IPv4' in conf) and (conf['IPv4'] == "enable"):
+ config['anycast_address']['ipv4'] = True
+ if ('IPv4' in conf) and (conf['IPv4'] == "disable"):
+ config['anycast_address']['ipv4'] = False
+ if ('IPv6' in conf) and (conf['IPv6'] == "enable"):
+ config['anycast_address']['ipv6'] = True
+ if ('IPv6' in conf) and (conf['IPv6'] == "disable"):
+ config['anycast_address']['ipv6'] = False
+ if ('gwmac' in conf) and (conf['gwmac']):
+ config['anycast_address']['mac_address'] = conf['gwmac']
+ return utils.remove_empties(config)
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
new file mode 100644
index 000000000..a1e79910f
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/tacacs_server/tacacs_server.py
@@ -0,0 +1,150 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic tacas server 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
+import json
+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.tacacs_server.tacacs_server import Tacacs_serverArgs
+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 Tacacs_serverFacts(object):
+ """ The sonic tacas server fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = Tacacs_serverArgs.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 tacacs_server
+ :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 = None
+ 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_all_tacacs_server()
+
+ obj = self.render_config(self.generated_spec, data)
+
+ ansible_facts['ansible_network_resources'].pop('tacacs_server', None)
+ facts = {}
+ if obj:
+ facts['tacacs_server'] = {}
+ params = utils.validate_config(self.argument_spec, {'config': obj})
+ if params:
+ facts['tacacs_server'] = 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_tacacs_server(self):
+ """Get all the tacacs_server configured in the device"""
+ request = [{"path": "data/openconfig-system:system/aaa/server-groups/server-group=TACACS/config", "method": GET}]
+ tacacs_server_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)
+
+ if "openconfig-system:config" in response[0][1]:
+ raw_tacacs_global_data = response[0][1].get("openconfig-system:config", {})
+
+ if 'auth-type' in raw_tacacs_global_data:
+ tacacs_server_data['auth_type'] = raw_tacacs_global_data['auth-type']
+ if 'secret-key' in raw_tacacs_global_data:
+ tacacs_server_data['key'] = raw_tacacs_global_data['secret-key']
+ if 'source-interface' in raw_tacacs_global_data:
+ tacacs_server_data['source_interface'] = raw_tacacs_global_data['source-interface']
+ if 'timeout' in raw_tacacs_global_data:
+ tacacs_server_data['timeout'] = raw_tacacs_global_data['timeout']
+
+ request = [{"path": "data/openconfig-system:system/aaa/server-groups/server-group=TACACS/servers", "method": GET}]
+ hosts = []
+ 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_tacacs_server_list = []
+ if "openconfig-system:servers" in response[0][1]:
+ raw_tacacs_server_list = response[0][1].get("openconfig-system:servers", {}).get('server', [])
+
+ for tacacs_host in raw_tacacs_server_list:
+ host_data = {}
+ if 'address' in tacacs_host:
+ host_data['name'] = tacacs_host['address']
+ cfg = tacacs_host.get('config', None)
+ if cfg:
+ if 'auth-type' in cfg:
+ host_data['auth_type'] = cfg['auth-type']
+ if 'priority' in cfg:
+ host_data['priority'] = cfg['priority']
+ if 'vrf' in cfg:
+ host_data['vrf'] = cfg['vrf']
+ if 'timeout' in cfg:
+ host_data['timeout'] = cfg['timeout']
+ if tacacs_host.get('tacacs', None) and tacacs_host['tacacs'].get('config', None):
+ tacas_cfg = tacacs_host['tacacs']['config']
+ if tacas_cfg.get('port', None):
+ host_data['port'] = tacas_cfg['port']
+ if tacas_cfg.get('secret-key', None):
+ host_data['key'] = tacas_cfg['secret-key']
+ if host_data:
+ hosts.append(host_data)
+
+ if hosts:
+ tacacs_server_data['servers'] = {'host': hosts}
+
+ return tacacs_server_data
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
new file mode 100644
index 000000000..038e97f83
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/users/users.py
@@ -0,0 +1,122 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic users 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.users.users import UsersArgs
+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 UsersFacts(object):
+ """ The sonic users fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = UsersArgs.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 users
+ :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:
+ # typically data is populated from the current device configuration
+ # data = connection.get('show running-config | section ^interface')
+ # using mock data instead
+ data = self.get_all_users()
+
+ objs = list()
+ 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('users', None)
+ facts = {}
+ if objs:
+ facts['users'] = []
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ if params:
+ facts['users'].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_all_users(self):
+ """Get all the users configured in the device"""
+ request = [{"path": "data/sonic-system-aaa:sonic-system-aaa/USER", "method": GET}]
+ users = []
+ 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_users = []
+ if "sonic-system-aaa:USER" in response[0][1]:
+ raw_users = response[0][1].get("sonic-system-aaa:USER", {}).get('USER_LIST', [])
+
+ 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)
+ 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/vlans/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlans/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlans/__init__.py
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
new file mode 100644
index 000000000..7c4af2ea8
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vlans/vlans.py
@@ -0,0 +1,126 @@
+#
+# -*- 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 vlans 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.vlans.vlans import VlansArgs
+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 VlansFacts(object):
+ """ The sonic vlans fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = VlansArgs.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 vlans
+ :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:
+ vlans = self.get_vlans()
+ objs = []
+ for vlan_id, vlan_config in vlans.items():
+ obj = self.render_config(self.generated_spec, vlan_config)
+ if obj:
+ objs.append(obj)
+ ansible_facts['ansible_network_resources'].pop('vlans', None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts['vlans'] = 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)
+ try:
+ config['vlan_id'] = int(conf['vlan_id'])
+ if conf.get('description', None):
+ config['description'] = conf['description']
+ except TypeError:
+ config['vlan_id'] = None
+ config['description'] = None
+ return utils.remove_empties(config)
+
+ def get_vlans(self):
+ """Get all the l2_interfaces available in chassis"""
+ request = [{"path": "data/openconfig-interfaces:interfaces", "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)
+
+ interfaces = {}
+ if "openconfig-interfaces:interfaces" in response[0][1]:
+ interfaces = response[0][1].get("openconfig-interfaces:interfaces", {})
+ if interfaces.get("interface"):
+ interfaces = interfaces['interface']
+
+ ret_vlan_configs = {}
+
+ for interface in interfaces:
+ interface_name = interface.get("config").get("name")
+ description = interface.get("config").get("description", None)
+ if "Vlan" in interface_name:
+ vlan_id = interface_name.split("Vlan")[1]
+ vlan_configs = {"vlan_id": vlan_id,
+ "name": interface_name,
+ }
+ if description:
+ vlan_configs['description'] = description
+
+ ret_vlan_configs.update({vlan_id: vlan_configs})
+
+ return ret_vlan_configs
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
new file mode 100644
index 000000000..797612bc4
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vrfs/vrfs.py
@@ -0,0 +1,120 @@
+#
+# -*- 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 vrfs 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.vrfs.vrfs import VrfsArgs
+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 VrfsFacts(object):
+ """ The sonic vrfs fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = VrfsArgs.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 vrf
+ :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_all_vrf_interfaces()
+
+ objs = list()
+ 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('vrfs', None)
+ facts = {}
+ if objs:
+ facts['vrfs'] = []
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ if params:
+ facts['vrfs'].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_all_vrf_interfaces(self):
+ """Get all the interfaces available in chassis"""
+ all_network_instatnces = {}
+ request = [{"path": "data/openconfig-network-instance:network-instances", "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)
+
+ if "openconfig-network-instance:network-instances" in response[0][1]:
+ all_network_instatnces = response[0][1].get("openconfig-network-instance:network-instances", {})
+ return self.get_vrf_interfaces_from_network_instances(all_network_instatnces['network-instance'])
+
+ def get_vrf_interfaces_from_network_instances(self, network_instances):
+ vrf_interfaces = []
+
+ for each_ins in network_instances:
+ vrf_interface = dict()
+ name = each_ins['name']
+ if name.startswith('Vrf') or name == 'mgmt':
+ vrf_interface['name'] = name
+ if each_ins.get("interfaces"):
+ interfaces = [{"name": intf.get("id")} for intf in each_ins["interfaces"]["interface"]]
+ vrf_interface["members"] = {"interfaces": interfaces}
+
+ vrf_interfaces.append(vrf_interface)
+ return vrf_interfaces
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vxlans/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vxlans/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vxlans/__init__.py
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
new file mode 100644
index 000000000..51aec6561
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/facts/vxlans/vxlans.py
@@ -0,0 +1,207 @@
+#
+# -*- 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 vxlans 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.vxlans.vxlans import VxlansArgs
+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 VxlansFacts(object):
+ """ The sonic vxlans fact class
+ """
+
+ def __init__(self, module, subspec='config', options='options'):
+ self._module = module
+ self.argument_spec = VxlansArgs.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 vxlans
+ :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:
+ # typically data is populated from the current device configuration
+ # data = connection.get('show running-config | section ^interface')
+ # using mock data instead
+ data = self.get_all_vxlans()
+
+ objs = list()
+ 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('vxlans', None)
+ facts = {}
+ if objs:
+ facts['vxlans'] = []
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ if params:
+ facts['vxlans'].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_all_vxlans(self):
+ vxlans = []
+ vxlan_tunnels = []
+ vxlan_vlan_map = []
+
+ vxlans_tunnels_vlan_map = self.get_all_vxlans_tunnels_vlan_map()
+ vxlans_evpn_nvo_list = self.get_all_vxlans_evpn_nvo_list()
+
+ if vxlans_tunnels_vlan_map.get('VXLAN_TUNNEL'):
+ if vxlans_tunnels_vlan_map['VXLAN_TUNNEL'].get('VXLAN_TUNNEL_LIST'):
+ vxlan_tunnels.extend(vxlans_tunnels_vlan_map['VXLAN_TUNNEL']['VXLAN_TUNNEL_LIST'])
+
+ if vxlans_tunnels_vlan_map.get('VXLAN_TUNNEL_MAP'):
+ if vxlans_tunnels_vlan_map['VXLAN_TUNNEL_MAP'].get('VXLAN_TUNNEL_MAP_LIST'):
+ vxlan_vlan_map.extend(vxlans_tunnels_vlan_map['VXLAN_TUNNEL_MAP']['VXLAN_TUNNEL_MAP_LIST'])
+
+ self.fill_tunnel_source_ip(vxlans, vxlan_tunnels, vxlans_evpn_nvo_list)
+ self.fill_vlan_map(vxlans, vxlan_vlan_map)
+
+ vxlan_vrf_list = self.get_all_vxlans_vrf_list()
+ self.fill_vrf_map(vxlans, vxlan_vrf_list)
+
+ return vxlans
+
+ def get_all_vxlans_vrf_list(self):
+ """Get all the vxlan tunnels and vlan map available """
+ request = [{"path": "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST", "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)
+
+ if "sonic-vrf:VRF_LIST" in response[0][1]:
+ vxlan_vrf_list = response[0][1].get("sonic-vrf:VRF_LIST", {})
+
+ return vxlan_vrf_list
+
+ def get_all_vxlans_evpn_nvo_list(self):
+ """Get all the evpn nvo list available """
+ request = [{"path": "data/sonic-vxlan:sonic-vxlan/EVPN_NVO/EVPN_NVO_LIST", "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)
+
+ vxlans_evpn_nvo_list = []
+ if "sonic-vxlan:EVPN_NVO_LIST" in response[0][1]:
+ vxlans_evpn_nvo_list = response[0][1].get("sonic-vxlan:EVPN_NVO_LIST", [])
+
+ return vxlans_evpn_nvo_list
+
+ def get_all_vxlans_tunnels_vlan_map(self):
+ """Get all the vxlan tunnels and vlan map available """
+ request = [{"path": "data/sonic-vxlan:sonic-vxlan", "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)
+
+ vxlans_tunnels_vlan_map = {}
+ if "sonic-vxlan:sonic-vxlan" in response[0][1]:
+ vxlans_tunnels_vlan_map = response[0][1].get("sonic-vxlan:sonic-vxlan", {})
+
+ return vxlans_tunnels_vlan_map
+
+ def fill_tunnel_source_ip(self, vxlans, vxlan_tunnels, vxlans_evpn_nvo_list):
+ for each_tunnel in vxlan_tunnels:
+ vxlan = dict()
+ vxlan['name'] = each_tunnel['name']
+ 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
+ vxlans.append(vxlan)
+
+ def fill_vlan_map(self, vxlans, vxlan_vlan_map):
+ for each_vlan_map in vxlan_vlan_map:
+ name = each_vlan_map['name']
+ matched_vtep = next((each_vxlan for each_vxlan in vxlans if each_vxlan['name'] == name), None)
+ if matched_vtep:
+ vni = int(each_vlan_map['vni'])
+ vlan = int(each_vlan_map['vlan'][4:])
+ vlan_map = matched_vtep.get('vlan_map')
+ if vlan_map:
+ vlan_map.append(dict({'vni': vni, 'vlan': vlan}))
+ else:
+ matched_vtep['vlan_map'] = [dict({'vni': vni, 'vlan': vlan})]
+
+ def fill_vrf_map(self, vxlans, vxlan_vrf_list):
+ for each_vrf in vxlan_vrf_list:
+ vni = each_vrf.get('vni', None)
+ if vni is None:
+ continue
+
+ matched_vtep = None
+ for each_vxlan in vxlans:
+ for each_vlan in each_vxlan.get('vlan_map', []):
+ if vni == each_vlan['vni']:
+ matched_vtep = each_vxlan
+
+ if matched_vtep:
+ vni = int(each_vrf['vni'])
+ vrf = each_vrf['vrf_name']
+ vrf_map = matched_vtep.get('vrf_map')
+ if vrf_map:
+ vrf_map.append(dict({'vni': vni, 'vrf': vrf}))
+ else:
+ matched_vtep['vrf_map'] = [dict({'vni': vni, 'vrf': vrf})]
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
new file mode 100644
index 000000000..77a63d425
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/sonic.py
@@ -0,0 +1,155 @@
+# This code is part of Ansible, but is an independent component.
+# This particular file snippet, and this file snippet only, is BSD licensed.
+# Modules you write using this snippet, which is embedded dynamically by Ansible
+# still belong to the author of the module, and may assign their own license
+# to the complete work.
+#
+# (c) 2016 Red Hat Inc.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+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
+)
+from ansible.module_utils.connection import Connection, ConnectionError
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, ConfigLine
+
+_DEVICE_CONFIGS = {}
+STANDARD_ETH_REGEXP = r"Eth\d+(/\d+)+"
+PATTERN = re.compile(STANDARD_ETH_REGEXP)
+
+
+def get_connection(module):
+ if hasattr(module, "_sonic_connection"):
+ return module._sonic_connection
+
+ capabilities = get_capabilities(module)
+ network_api = capabilities.get("network_api")
+ if network_api in ["cliconf", "sonic_rest"]:
+ module._sonic_connection = Connection(module._socket_path)
+ else:
+ module.fail_json(msg="Invalid connection type %s" % network_api)
+
+ return module._sonic_connection
+
+
+def get_capabilities(module):
+ if hasattr(module, "_sonic_capabilities"):
+ return module._sonic_capabilities
+ try:
+ capabilities = Connection(module._socket_path).get_capabilities()
+ except ConnectionError as exc:
+ module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+ module._sonic_capabilities = json.loads(capabilities)
+ return module._sonic_capabilities
+
+
+def get_config(module, flags=None):
+ flags = to_list(flags)
+ flag_str = " ".join(flags)
+
+ try:
+ return _DEVICE_CONFIGS[flag_str]
+ except KeyError:
+ connection = get_connection(module)
+ try:
+ out = connection.get_config(flags=flags)
+ except ConnectionError as exc:
+ module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+ cfg = to_text(out, errors="surrogate_then_replace").strip()
+ _DEVICE_CONFIGS[flag_str] = cfg
+ return cfg
+
+
+def get_sublevel_config(running_config, module):
+ contents = list()
+ current_config_contents = list()
+ running_config = NetworkConfig(contents=running_config, indent=1)
+ obj = running_config.get_object(module.params['parents'])
+ if obj:
+ contents = obj.children
+ parents = module.params['parents']
+ if parents[2:]:
+ temp = 1
+ for count, item in enumerate(parents[2:], start=2):
+ item = ' ' * temp + item
+ temp = temp + 1
+ parents[count] = item
+ contents[:0] = parents
+ indent = 0
+ for c in contents:
+ if isinstance(c, str):
+ if c in parents:
+ current_config_contents.append(c.rjust(len(c) + indent, ' '))
+ if c not in parents:
+ c = ' ' * (len(parents) - 1) + c
+ current_config_contents.append(c.rjust(len(c) + indent, ' '))
+ if isinstance(c, ConfigLine):
+ current_config_contents.append(c.raw)
+ indent = 1
+ sublevel_config = '\n'.join(current_config_contents)
+ return sublevel_config
+
+
+def run_commands(module, commands, check_rc=True):
+ connection = get_connection(module)
+ try:
+ return connection.run_commands(commands=commands, check_rc=check_rc)
+ except ConnectionError as exc:
+ module.fail_json(msg=to_text(exc))
+
+
+def edit_config(module, commands, skip_code=None):
+ connection = get_connection(module)
+
+ # 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:
+ url = request.get("path", None)
+ if url:
+ request["path"] = update_url(url)
+ # End
+ return connection.edit_config(commands)
+
+
+def update_url(url):
+ match = re.search(STANDARD_ETH_REGEXP, url)
+ ret_url = url
+ if match:
+ interface_name = match.group()
+ interface_name = interface_name.replace("/", "%2f")
+ ret_url = PATTERN.sub(interface_name, url)
+ return ret_url
+
+
+def to_request(module, requests):
+ transform = ComplexList(dict(path=dict(key=True), method=dict(), data=dict(type='dict')), module)
+ return transform(to_list(requests))
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/__init__.py
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
new file mode 100644
index 000000000..7471bcb11
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/bgp_utils.py
@@ -0,0 +1,611 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The sonic bgp 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.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
+)
+from ansible.module_utils.connection import ConnectionError
+
+afi_safi_types_map = {
+ 'openconfig-bgp-types:IPV4_UNICAST': 'ipv4_unicast',
+ 'openconfig-bgp-types:IPV6_UNICAST': 'ipv6_unicast',
+ 'openconfig-bgp-types:L2VPN_EVPN': 'l2vpn_evpn',
+}
+GET = "get"
+network_instance_path = '/data/openconfig-network-instance:network-instances/network-instance'
+protocol_bgp_path = 'protocols/protocol=BGP,bgp/bgp'
+
+
+def get_all_vrfs(module):
+ """Get all VRF configurations available in chassis"""
+ all_vrfs = []
+ ret = []
+ request = {"path": "data/sonic-vrf:sonic-vrf/VRF/VRF_LIST", "method": GET}
+ try:
+ response = edit_config(module, to_request(module, request))
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+
+ if 'sonic-vrf:VRF_LIST' in response[0][1]:
+ all_vrf_data = response[0][1].get('sonic-vrf:VRF_LIST', [])
+ if all_vrf_data:
+ for vrf_data in all_vrf_data:
+ all_vrfs.append(vrf_data['vrf_name'])
+
+ return all_vrfs
+
+
+def get_peergroups(module, vrf_name):
+ peer_groups = []
+ request_path = '%s=%s/protocols/protocol=BGP,bgp/bgp/peer-groups' % (network_instance_path, vrf_name)
+ request = {"path": request_path, "method": GET}
+ try:
+ response = edit_config(module, to_request(module, request))
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+
+ resp = response[0][1]
+ if 'openconfig-network-instance:peer-groups' in resp:
+ data = resp['openconfig-network-instance:peer-groups']
+ if 'peer-group' in data:
+ for peer_group in data['peer-group']:
+ pg = {}
+ if 'config' in peer_group:
+ if 'peer-group-name' in peer_group['config']:
+ pg.update({'name': peer_group['config']['peer-group-name']})
+ if 'description' in peer_group['config']:
+ pg.update({'pg_description': peer_group['config']['description']})
+ if 'disable-ebgp-connected-route-check' in peer_group['config']:
+ pg.update({'disable_connected_check': peer_group['config']['disable-ebgp-connected-route-check']})
+ if 'dont-negotiate-capability' in peer_group['config']:
+ pg.update({'dont_negotiate_capability': peer_group['config']['dont-negotiate-capability']})
+ if 'enforce-first-as' in peer_group['config']:
+ pg.update({'enforce_first_as': peer_group['config']['enforce-first-as']})
+ if 'enforce-multihop' in peer_group['config']:
+ pg.update({'enforce_multihop': peer_group['config']['enforce-multihop']})
+ local_as = {}
+ if 'local-as' in peer_group['config']:
+ local_as.update({'as': peer_group['config']['local-as']})
+ if 'local-as-no-prepend' in peer_group['config']:
+ local_as.update({'no_prepend': peer_group['config']['local-as-no-prepend']})
+ if 'local-as-replace-as' in peer_group['config']:
+ local_as.update({'replace_as': peer_group['config']['local-as-replace-as']})
+ if 'override-capability' in peer_group['config']:
+ pg.update({'override_capability': peer_group['config']['override-capability']})
+ if 'shutdown-message' in peer_group['config']:
+ pg.update({'shutdown_msg': peer_group['config']['shutdown-message']})
+ if 'solo-peer' in peer_group['config']:
+ pg.update({'solo': peer_group['config']['solo-peer']})
+ if 'strict-capability-match' in peer_group['config']:
+ pg.update({'strict_capability_match': peer_group['config']['strict-capability-match']})
+ if 'ttl-security-hops' in peer_group['config']:
+ pg.update({'ttl_security': peer_group['config']['ttl-security-hops']})
+ auth_pwd = {}
+ if 'auth-password' in peer_group and 'config' in peer_group['auth-password']:
+ if 'encrypted' in peer_group['auth-password']['config']:
+ auth_pwd.update({'encrypted': peer_group['auth-password']['config']['encrypted']})
+ if 'password' in peer_group['auth-password']['config']:
+ auth_pwd.update({'pwd': peer_group['auth-password']['config']['password']})
+ bfd = {}
+ if 'enable-bfd' in peer_group and 'config' in peer_group['enable-bfd']:
+ if 'enabled' in peer_group['enable-bfd']['config']:
+ bfd.update({'enabled': peer_group['enable-bfd']['config']['enabled']})
+ if 'check-control-plane-failure' in peer_group['enable-bfd']['config']:
+ bfd.update({'check_failure': peer_group['enable-bfd']['config']['check-control-plane-failure']})
+ if 'bfd-profile' in peer_group['enable-bfd']['config']:
+ bfd.update({'profile': peer_group['enable-bfd']['config']['bfd-profile']})
+ ebgp_multihop = {}
+ if 'ebgp-multihop' in peer_group and 'config' in peer_group['ebgp-multihop']:
+ if 'enabled' in peer_group['ebgp-multihop']['config']:
+ ebgp_multihop.update({'enabled': peer_group['ebgp-multihop']['config']['enabled']})
+ if 'multihop-ttl' in peer_group['ebgp-multihop']['config']:
+ ebgp_multihop.update({'multihop_ttl': peer_group['ebgp-multihop']['config']['multihop-ttl']})
+ if 'transport' in peer_group and 'config' in peer_group['transport']:
+ if 'local-address' in peer_group['transport']['config']:
+ pg.update({'local_address': peer_group['transport']['config']['local-address']})
+ if 'passive-mode' in peer_group['transport']['config']:
+ pg.update({'passive': peer_group['transport']['config']['passive-mode']})
+ if 'timers' in peer_group and 'config' in peer_group['timers']:
+ if 'minimum-advertisement-interval' in peer_group['timers']['config']:
+ pg.update({'advertisement_interval': peer_group['timers']['config']['minimum-advertisement-interval']})
+ timers = {}
+ if 'hold-time' in peer_group['timers']['config']:
+ timers.update({'holdtime': peer_group['timers']['config']['hold-time']})
+ if 'keepalive-interval' in peer_group['timers']['config']:
+ timers.update({'keepalive': peer_group['timers']['config']['keepalive-interval']})
+ if 'connect-retry' in peer_group['timers']['config']:
+ timers.update({'connect_retry': peer_group['timers']['config']['connect-retry']})
+ capability = {}
+ if 'config' in peer_group and 'capability-dynamic' in peer_group['config']:
+ capability.update({'dynamic': peer_group['config']['capability-dynamic']})
+ if 'config' in peer_group and 'capability-extended-nexthop' in peer_group['config']:
+ capability.update({'extended_nexthop': peer_group['config']['capability-extended-nexthop']})
+ remote_as = {}
+ if 'config' in peer_group and 'peer-as' in peer_group['config']:
+ remote_as.update({'peer_as': peer_group['config']['peer-as']})
+ if 'config' in peer_group and 'peer-type' in peer_group['config']:
+ remote_as.update({'peer_type': peer_group['config']['peer-type'].lower()})
+ afis = []
+ if 'afi-safis' in peer_group and 'afi-safi' in peer_group['afi-safis']:
+ for each in peer_group['afi-safis']['afi-safi']:
+ samp = {}
+ if 'afi-safi-name' in each and each['afi-safi-name']:
+ tmp = each['afi-safi-name'].split(':')
+ if tmp:
+ split_tmp = tmp[1].split('_')
+ if split_tmp:
+ afi = split_tmp[0].lower()
+ safi = split_tmp[1].lower()
+ if afi and safi:
+ samp.update({'afi': afi})
+ samp.update({'safi': safi})
+ if 'config' in each and 'enabled' in each['config']:
+ samp.update({'activate': each['config']['enabled']})
+ if 'allow-own-as' in each and 'config' in each['allow-own-as']:
+ allowas_in = {}
+ allowas_conf = each['allow-own-as']['config']
+ if 'origin' in allowas_conf and allowas_conf['origin']:
+ allowas_in.update({'origin': allowas_conf['origin']})
+ elif 'as-count' in allowas_conf and allowas_conf['as-count']:
+ allowas_in.update({'value': allowas_conf['as-count']})
+ if allowas_in:
+ samp.update({'allowas_in': allowas_in})
+ if 'ipv4-unicast' in each:
+ if 'config' in each['ipv4-unicast']:
+ ip_afi_conf = each['ipv4-unicast']['config']
+ ip_afi = update_bgp_nbr_pg_ip_afi_dict(ip_afi_conf)
+ if ip_afi:
+ samp.update({'ip_afi': ip_afi})
+ if 'prefix-limit' in each['ipv4-unicast'] and 'config' in each['ipv4-unicast']['prefix-limit']:
+ pfx_lmt_conf = each['ipv4-unicast']['prefix-limit']['config']
+ prefix_limit = update_bgp_nbr_pg_prefix_limit_dict(pfx_lmt_conf)
+ if prefix_limit:
+ samp.update({'prefix_limit': prefix_limit})
+ elif 'ipv6-unicast' in each:
+ if 'config' in each['ipv6-unicast']:
+ ip_afi_conf = each['ipv6-unicast']['config']
+ ip_afi = update_bgp_nbr_pg_ip_afi_dict(ip_afi_conf)
+ if ip_afi:
+ samp.update({'ip_afi': ip_afi})
+ if 'prefix-limit' in each['ipv6-unicast'] and 'config' in each['ipv6-unicast']['prefix-limit']:
+ pfx_lmt_conf = each['ipv6-unicast']['prefix-limit']['config']
+ 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']:
+ samp.update({'prefix_list_in': pfx_lst_conf['import-policy']})
+ if 'export-policy' in pfx_lst_conf and pfx_lst_conf['export-policy']:
+ samp.update({'prefix_list_out': pfx_lst_conf['export-policy']})
+ if samp:
+ afis.append(samp)
+ if auth_pwd:
+ pg.update({'auth_pwd': auth_pwd})
+ if bfd:
+ pg.update({'bfd': bfd})
+ if ebgp_multihop:
+ pg.update({'ebgp_multihop': ebgp_multihop})
+ if local_as:
+ pg.update({'local_as': local_as})
+ if timers:
+ pg.update({'timers': timers})
+ if capability:
+ pg.update({'capability': capability})
+ if remote_as:
+ pg.update({'remote_as': remote_as})
+ if afis and len(afis) > 0:
+ afis_dict = {}
+ afis_dict.update({'afis': afis})
+ pg.update({'address_family': afis_dict})
+ peer_groups.append(pg)
+
+ return peer_groups
+
+
+def update_bgp_nbr_pg_ip_afi_dict(ip_afi_conf):
+ ip_afi = {}
+ if 'default-policy-name' in ip_afi_conf and ip_afi_conf['default-policy-name']:
+ ip_afi.update({'default_policy_name': ip_afi_conf['default-policy-name']})
+ if 'send-default-route' in ip_afi_conf and ip_afi_conf['send-default-route']:
+ ip_afi.update({'send_default_route': ip_afi_conf['send-default-route']})
+
+ return ip_afi
+
+
+def update_bgp_nbr_pg_prefix_limit_dict(pfx_lmt_conf):
+ prefix_limit = {}
+ if 'max-prefixes' in pfx_lmt_conf and pfx_lmt_conf['max-prefixes']:
+ prefix_limit.update({'max_prefixes': pfx_lmt_conf['max-prefixes']})
+ if 'prevent-teardown' in pfx_lmt_conf and pfx_lmt_conf['prevent-teardown']:
+ prefix_limit.update({'prevent_teardown': pfx_lmt_conf['prevent-teardown']})
+ if 'warning-threshold-pct' in pfx_lmt_conf and pfx_lmt_conf['warning-threshold-pct']:
+ prefix_limit.update({'warning_threshold': pfx_lmt_conf['warning-threshold-pct']})
+ if 'restart-timer' in pfx_lmt_conf and pfx_lmt_conf['restart-timer']:
+ prefix_limit.update({'restart_timer': pfx_lmt_conf['restart-timer']})
+
+ return prefix_limit
+
+
+def get_ip_afi_cfg_payload(ip_afi):
+ ip_afi_cfg = {}
+
+ if ip_afi.get('default_policy_name', None) is not None:
+ default_policy_name = ip_afi['default_policy_name']
+ ip_afi_cfg.update({'default-policy-name': default_policy_name})
+ if ip_afi.get('send_default_route', None) is not None:
+ send_default_route = ip_afi['send_default_route']
+ ip_afi_cfg.update({'send-default-route': send_default_route})
+
+ return ip_afi_cfg
+
+
+def get_prefix_limit_payload(prefix_limit):
+ pfx_lmt_cfg = {}
+
+ if prefix_limit.get('max_prefixes', None) is not None:
+ max_prefixes = prefix_limit['max_prefixes']
+ pfx_lmt_cfg.update({'max-prefixes': max_prefixes})
+ if prefix_limit.get('prevent_teardown', None) is not None:
+ prevent_teardown = prefix_limit['prevent_teardown']
+ pfx_lmt_cfg.update({'prevent-teardown': prevent_teardown})
+ if prefix_limit.get('warning_threshold', None) is not None:
+ warning_threshold = prefix_limit['warning_threshold']
+ pfx_lmt_cfg.update({'warning-threshold-pct': warning_threshold})
+ if prefix_limit.get('restart_timer', None) is not None:
+ restart_timer = prefix_limit['restart_timer']
+ pfx_lmt_cfg.update({'restart-timer': restart_timer})
+
+ return pfx_lmt_cfg
+
+
+def get_all_bgp_af_redistribute(module, vrfs, af_redis_params_map):
+ """Get all BGP Global Address Family Redistribute configurations available in chassis"""
+ all_af_redis_data = []
+ ret_redis_data = []
+ for vrf_name in vrfs:
+ af_redis_data = {}
+ request_path = '%s=%s/table-connections' % (network_instance_path, vrf_name)
+ request = {"path": request_path, "method": GET}
+ try:
+ response = edit_config(module, to_request(module, request))
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+
+ if "openconfig-network-instance:table-connections" in response[0][1]:
+ af_redis_data.update({vrf_name: response[0][1]['openconfig-network-instance:table-connections']})
+
+ if af_redis_data:
+ all_af_redis_data.append(af_redis_data)
+
+ if all_af_redis_data:
+ for vrf_name in vrfs:
+ key = vrf_name
+ val = next((af_redis_data for af_redis_data in all_af_redis_data if vrf_name in af_redis_data), None)
+ if not val:
+ continue
+
+ val = val[vrf_name]
+ redis_data = val.get('table-connection', [])
+ if not redis_data:
+ continue
+ filtered_redis_data = []
+ for e_cfg in redis_data:
+ af_redis_data = get_from_params_map(af_redis_params_map, e_cfg)
+ if af_redis_data:
+ filtered_redis_data.append(af_redis_data)
+
+ if filtered_redis_data:
+ ret_redis_data.append({key: filtered_redis_data})
+
+ return ret_redis_data
+
+
+def get_all_bgp_globals(module, vrfs):
+ """Get all BGP configurations available in chassis"""
+ all_bgp_globals = []
+ for vrf_name in vrfs:
+ get_path = '%s=%s/%s/global' % (network_instance_path, vrf_name, protocol_bgp_path)
+ request = {"path": get_path, "method": GET}
+ try:
+ response = edit_config(module, to_request(module, request))
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+ for resp in response:
+ if "openconfig-network-instance:global" in resp[1]:
+ bgp_data = {'global': resp[1].get("openconfig-network-instance:global", {})}
+ bgp_data.update({'vrf_name': vrf_name})
+ all_bgp_globals.append(bgp_data)
+ return all_bgp_globals
+
+
+def get_bgp_global_af_data(data, af_params_map):
+ ret_af_data = {}
+ for key, val in data.items():
+ if key == 'global':
+ if 'afi-safis' in val and 'afi-safi' in val['afi-safis']:
+ global_af_data = []
+ raw_af_data = val['afi-safis']['afi-safi']
+ for each_af_data in raw_af_data:
+ af_data = get_from_params_map(af_params_map, each_af_data)
+ if af_data:
+ global_af_data.append(af_data)
+ ret_af_data.update({'address_family': global_af_data})
+ if 'config' in val and 'as' in val['config']:
+ as_val = val['config']['as']
+ ret_af_data.update({'bgp_as': as_val})
+ if key == 'vrf_name':
+ ret_af_data.update({'vrf_name': val})
+ return ret_af_data
+
+
+def get_bgp_global_data(data, global_params_map):
+ bgp_data = {}
+ for key, val in data.items():
+ if key == 'global':
+ global_data = get_from_params_map(global_params_map, val)
+ bgp_data.update(global_data)
+ if key == 'vrf_name':
+ bgp_data.update({'vrf_name': val})
+ return bgp_data
+
+
+def get_from_params_map(params_map, data):
+ ret_data = {}
+ for want_key, config_key in params_map.items():
+ tmp_data = {}
+ for key, val in data.items():
+ if key == 'config':
+ for k, v in val.items():
+ if k == config_key:
+ val_data = val[config_key]
+ ret_data.update({want_key: val_data})
+ if config_key == 'afi-safi-name':
+ ret_data.pop(want_key)
+ for type_k, type_val in afi_safi_types_map.items():
+ if type_k == val_data:
+ afi_safi = type_val.split('_')
+ val_data = afi_safi[0]
+ ret_data.update({'safi': afi_safi[1]})
+ ret_data.update({want_key: val_data})
+ break
+ else:
+ if key == 'timers' and ('config' in val or 'state' in val):
+ tmp = {}
+ if key in ret_data:
+ tmp = ret_data[key]
+ cfg = val['config'] if 'config' in val else val['state']
+ for k, v in cfg.items():
+ if k == config_key:
+ if k != 'minimum-advertisement-interval':
+ tmp.update({want_key: cfg[config_key]})
+ else:
+ ret_data.update({want_key: cfg[config_key]})
+ if tmp:
+ ret_data.update({key: tmp})
+
+ elif isinstance(config_key, list):
+ i = 0
+ if key == config_key[0]:
+ if key == 'afi-safi':
+ cfg_data = config_key[1]
+ for itm in afi_safi_types_map:
+ if cfg_data in itm:
+ afi_safi = itm[cfg_data].split('_')
+ cfg_data = afi_safi[0]
+ ret_data.update({'safi': afi_safi[1]})
+ ret_data.update({want_key: cfg_data})
+ break
+ else:
+ cfg_data = {key: val}
+ for cfg_key in config_key:
+ if cfg_key == 'config':
+ continue
+ new_data = None
+
+ if cfg_key in cfg_data:
+ new_data = cfg_data[cfg_key]
+ elif isinstance(cfg_data, dict) and 'config' in cfg_data:
+ if cfg_key in cfg_data['config']:
+ new_data = cfg_data['config'][cfg_key]
+
+ if new_data is not None:
+ cfg_data = new_data
+ else:
+ break
+ else:
+ ret_data.update({want_key: cfg_data})
+ else:
+ if key == config_key and val:
+ if config_key != 'afi-safi-name' and config_key != 'timers':
+ cfg_data = val
+ ret_data.update({want_key: cfg_data})
+
+ return ret_data
+
+
+def get_bgp_data(module, global_params_map):
+ vrf_list = get_all_vrfs(module)
+ data = get_all_bgp_globals(module, vrf_list)
+
+ objs = []
+ # operate on a collection of resource x
+ for conf in data:
+ if conf:
+ obj = get_bgp_global_data(conf, global_params_map)
+ if obj:
+ objs.append(obj)
+ return objs
+
+
+def get_bgp_af_data(module, af_params_map):
+ vrf_list = get_all_vrfs(module)
+ data = get_all_bgp_globals(module, vrf_list)
+
+ objs = []
+ # operate on a collection of resource x
+ for conf in data:
+ if conf:
+ obj = get_bgp_global_af_data(conf, af_params_map)
+ if obj:
+ objs.append(obj)
+
+ return objs
+
+
+def get_bgp_as(module, vrf_name):
+ as_val = None
+ get_path = '%s=%s/%s/global/config' % (network_instance_path, vrf_name, protocol_bgp_path)
+ request = {"path": get_path, "method": GET}
+ try:
+ response = edit_config(module, to_request(module, request))
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+
+ resp = response[0][1]
+ if "openconfig-network-instance:config" in resp and 'as' in resp['openconfig-network-instance:config']:
+ as_val = resp['openconfig-network-instance:config']['as']
+ return as_val
+
+
+def get_bgp_neighbors(module, vrf_name):
+ neighbors_data = None
+ get_path = '%s=%s/%s/neighbors' % (network_instance_path, vrf_name, protocol_bgp_path)
+ request = {"path": get_path, "method": GET}
+ try:
+ response = edit_config(module, to_request(module, request))
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+
+ resp = response[0][1]
+ if "openconfig-network-instance:neighbors" in resp:
+ neighbors_data = resp['openconfig-network-instance:neighbors']
+
+ return neighbors_data
+
+
+def get_all_bgp_neighbors(module):
+ vrf_list = get_all_vrfs(module)
+ """Get all BGP neighbor configurations available in chassis"""
+ all_bgp_neighbors = []
+
+ for vrf_name in vrf_list:
+ neighbors_cfg = {}
+
+ bgp_as = get_bgp_as(module, vrf_name)
+ if bgp_as:
+ neighbors_cfg['bgp_as'] = bgp_as
+ neighbors_cfg['vrf_name'] = vrf_name
+ else:
+ continue
+
+ neighbors = get_bgp_neighbors(module, vrf_name)
+ if neighbors:
+ neighbors_cfg['neighbors'] = neighbors
+
+ if neighbors_cfg:
+ all_bgp_neighbors.append(neighbors_cfg)
+
+ return all_bgp_neighbors
+
+
+def get_undefined_bgps(want, have, check_neighbors=None):
+ if check_neighbors is None:
+ check_neighbors = False
+
+ undefined_resources = []
+
+ if not want:
+ return undefined_resources
+
+ if not have:
+ have = []
+
+ for want_conf in want:
+ undefined = {}
+ want_bgp_as = want_conf['bgp_as']
+ want_vrf = want_conf['vrf_name']
+ have_conf = next((conf for conf in have if (want_bgp_as == conf['bgp_as'] and want_vrf == conf['vrf_name'])), None)
+ if not have_conf:
+ undefined['bgp_as'] = want_bgp_as
+ undefined['vrf_name'] = want_vrf
+ undefined_resources.append(undefined)
+ if check_neighbors and have_conf:
+ want_neighbors = want_conf.get('neighbors', [])
+ have_neighbors = have_conf.get('neighbors', [])
+ undefined_neighbors = get_undefined_neighbors(want_neighbors, have_neighbors)
+ if undefined_neighbors:
+ undefined['bgp_as'] = want_bgp_as
+ undefined['vrf_name'] = want_vrf
+ undefined['neighbors'] = undefined_neighbors
+ undefined_resources.append(undefined)
+
+ return undefined_resources
+
+
+def get_undefined_neighbors(want, have):
+ undefined_neighbors = []
+ if not want:
+ return undefined_neighbors
+
+ if not have:
+ have = []
+
+ for want_neighbor in want:
+ want_neighbor_val = want_neighbor['neighbor']
+ have_neighbor = next((conf for conf in have if want_neighbor_val == conf['neighbor']), None)
+ if not have_neighbor:
+ undefined_neighbors.append({'neighbor': want_neighbor_val})
+
+ return undefined_neighbors
+
+
+def validate_bgps(module, want, have):
+ validate_bgp_resources(module, want, have)
+
+
+def validate_bgp_neighbors(module, want, have):
+ validate_bgp_resources(module, want, have, check_neighbors=True)
+
+
+def validate_bgp_resources(module, want, have, check_neighbors=None):
+ undefined_resources = get_undefined_bgps(want, have, check_neighbors)
+ if undefined_resources:
+ err = "Resource not found! {res}".format(res=undefined_resources)
+ module.fail_json(msg=err, code=404)
+
+
+def normalize_neighbors_interface_name(want, module):
+ if want:
+ for conf in want:
+ neighbors = conf.get('neighbors', None)
+ if neighbors:
+ normalize_interface_name(neighbors, module, 'neighbor')
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
new file mode 100644
index 000000000..a7f6e9063
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/interfaces_util.py
@@ -0,0 +1,55 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 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.
+#
+#############################################
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import traceback
+import json
+
+from ansible.module_utils._text import to_native
+
+try:
+ import jinja2
+ HAS_LIB = True
+except Exception as e:
+ HAS_LIB = False
+ ERR_MSG = to_native(e)
+ LIB_IMP_ERR = traceback.format_exc()
+
+
+# To create Loopback, VLAN interfaces
+def build_interfaces_create_request(interface_name):
+ url = "data/openconfig-interfaces:interfaces"
+ method = "PATCH"
+ payload_template = """{"openconfig-interfaces:interfaces": {"interface": [{"name": "{{interface_name}}", "config": {"name": "{{interface_name}}"}}]}}"""
+ input_data = {"interface_name": interface_name}
+ env = jinja2.Environment(autoescape=False)
+ t = env.from_string(payload_template)
+ intended_payload = t.render(input_data)
+ ret_payload = json.loads(intended_payload)
+ request = {"path": url,
+ "method": method,
+ "data": ret_payload}
+ return request
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
new file mode 100644
index 000000000..0d6e6d1a0
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/module_utils/network/sonic/utils/utils.py
@@ -0,0 +1,511 @@
+#
+# -*- 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)
+
+# utils
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import re
+import json
+import ast
+from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ is_masklen,
+ to_netmask,
+ remove_empties
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import (
+ to_request,
+ edit_config
+)
+from ansible.module_utils.connection import ConnectionError
+
+DEFAULT_TEST_KEY = {'config': {'name': ''}}
+GET = 'get'
+
+intf_naming_mode = ""
+
+
+def get_diff(base_data, compare_with_data, test_keys=None, is_skeleton=None):
+ diff = []
+ if is_skeleton is None:
+ is_skeleton = False
+
+ test_keys = normalize_testkeys(test_keys)
+
+ if isinstance(base_data, list) and isinstance(compare_with_data, list):
+ dict_diff = get_diff_dict({"config": base_data}, {"config": compare_with_data}, test_keys, is_skeleton)
+ diff = dict_diff.get("config", [])
+
+ else:
+ new_base, new_compare = convert_dict_to_single_entry_list(base_data, compare_with_data, test_keys)
+ diff = get_diff_dict(new_base, new_compare, test_keys, is_skeleton)
+ if diff:
+ diff = convert_single_entry_list_to_dict(diff)
+ else:
+ diff = {}
+
+ return diff
+
+
+def get_diff_dict(base_data, compare_with_data, test_keys=None, is_skeleton=None):
+ if is_skeleton is None:
+ is_skeleton = False
+
+ if test_keys is None:
+ test_keys = []
+
+ if not base_data:
+ return base_data
+
+ planned_set = set(base_data.keys())
+ discovered_set = set(compare_with_data.keys())
+ intersect_set = planned_set.intersection(discovered_set)
+ changed_dict = {}
+ has_dict_item = None
+ added_set = planned_set - intersect_set
+ # Keys part of added are new and put into changed_dict
+ if added_set:
+ for key in added_set:
+ if is_skeleton:
+ changed_dict[key] = base_data[key]
+ elif base_data[key] is not None:
+ if isinstance(base_data[key], dict):
+ val_dict = remove_empties(base_data[key])
+ if val_dict:
+ changed_dict[key] = remove_empties(base_data[key])
+ elif isinstance(base_data[key], list):
+ val_list = remove_empties_from_list(base_data[key])
+ if val_list:
+ changed_dict[key] = remove_empties_from_list(base_data[key])
+ else:
+ changed_dict[key] = base_data[key]
+ for key in intersect_set:
+ has_dict_item = False
+ value = base_data[key]
+ if isinstance(value, list):
+ p_list = base_data[key] if key in base_data else []
+ d_list = compare_with_data[key] if key in compare_with_data else []
+ keys_to_compare = next((test_key_item[key] for test_key_item in test_keys if key in test_key_item), None)
+ changed_list = []
+ if p_list and d_list:
+ for p_list_item in p_list:
+ matched = False
+ has_diff = False
+ for d_list_item in d_list:
+ if (isinstance(p_list_item, dict) and isinstance(d_list_item, dict)):
+ if keys_to_compare:
+ key_matched_cnt = 0
+ test_keys_present_cnt = 0
+ common_keys = set(p_list_item).intersection(d_list_item)
+ for test_key in keys_to_compare:
+ if test_key in common_keys:
+ test_keys_present_cnt += 1
+ if p_list_item[test_key] == d_list_item[test_key]:
+ key_matched_cnt += 1
+ if key_matched_cnt and key_matched_cnt == test_keys_present_cnt:
+ remaining_keys = [test_key_item for test_key_item in test_keys if key not in test_key_item]
+ dict_diff = get_diff_dict(p_list_item, d_list_item, remaining_keys, is_skeleton)
+ matched = True
+ if dict_diff:
+ has_diff = True
+ for test_key in keys_to_compare:
+ dict_diff.update({test_key: p_list_item[test_key]})
+ break
+ else:
+ dict_diff = get_diff_dict(p_list_item, d_list_item, test_keys, is_skeleton)
+ if not dict_diff:
+ matched = True
+ break
+ else:
+ if p_list_item == d_list_item:
+ matched = True
+ break
+ if not matched:
+ if is_skeleton:
+ changed_list.append(p_list_item)
+ else:
+ if isinstance(p_list_item, dict):
+ val_dict = remove_empties(p_list_item)
+ if val_dict is not None:
+ changed_list.append(val_dict)
+ elif isinstance(p_list_item, list):
+ val_list = remove_empties_from_list(p_list_item)
+ if val_list is not None:
+ changed_list.append(val_list)
+ else:
+ if p_list_item is not None:
+ changed_list.append(p_list_item)
+ elif has_diff and dict_diff:
+ changed_list.append(dict_diff)
+ if changed_list:
+ changed_dict.update({key: changed_list})
+ elif p_list and (not d_list):
+ changed_dict[key] = p_list
+ elif (isinstance(value, dict) and isinstance(compare_with_data[key], dict)):
+ dict_diff = get_diff_dict(base_data[key], compare_with_data[key], test_keys, is_skeleton)
+ if dict_diff:
+ changed_dict[key] = dict_diff
+ elif value is not None:
+ if not is_skeleton:
+ if compare_with_data[key] != base_data[key]:
+ changed_dict[key] = base_data[key]
+ return changed_dict
+
+
+def convert_dict_to_single_entry_list(base_data, compare_with_data, test_keys):
+ # if it is dict comparision convert dict into single entry list by adding 'config' as key
+ new_base = {'config': [base_data]}
+ new_compare = {'config': [compare_with_data]}
+
+ # get testkey of 'config'
+ config_testkey = None
+ for item in test_keys:
+ for key, val in item.items():
+ if key == 'config':
+ config_testkey = list(val)[0]
+ break
+ if config_testkey:
+ break
+ # if testkey of 'config' is not in base data, introduce single entry list
+ # with 'temp_key' as config testkey and base_data as data.
+ if config_testkey and base_data and config_testkey not in base_data:
+ new_base = {'config': [{config_testkey: 'temp_key', 'data': base_data}]}
+ new_compare = {'config': [{config_testkey: 'temp_key', 'data': compare_with_data}]}
+
+ return new_base, new_compare
+
+
+def convert_single_entry_list_to_dict(diff):
+ diff = diff['config'][0]
+ if 'data' in diff:
+ diff = diff['data']
+ return diff
+
+
+def normalize_testkeys(test_keys):
+ if test_keys is None:
+ test_keys = []
+
+ if not any(test_key_item for test_key_item in test_keys if "config" in test_key_item):
+ test_keys.append(DEFAULT_TEST_KEY)
+
+ return test_keys
+
+
+def update_states(commands, state):
+ ret_list = list()
+ if commands:
+ if isinstance(commands, list):
+ for command in commands:
+ ret = command.copy()
+ ret.update({"state": state})
+ ret_list.append(ret)
+ elif isinstance(commands, dict):
+ ret_list.append(commands.copy())
+ ret_list[0].update({"state": state})
+ return ret_list
+
+
+def dict_to_set(sample_dict):
+ # Generate a set with passed dictionary for comparison
+ test_dict = dict()
+ if isinstance(sample_dict, dict):
+ for k, v in iteritems(sample_dict):
+ if v is not None:
+ if isinstance(v, list):
+ if isinstance(v[0], dict):
+ li = []
+ for each in v:
+ for key, value in iteritems(each):
+ if isinstance(value, list):
+ each[key] = tuple(value)
+ li.append(tuple(iteritems(each)))
+ v = tuple(li)
+ else:
+ v = tuple(v)
+ elif isinstance(v, dict):
+ li = []
+ for key, value in iteritems(v):
+ if isinstance(value, list):
+ v[key] = tuple(value)
+ li.extend(tuple(iteritems(v)))
+ v = tuple(li)
+ test_dict.update({k: v})
+ return_set = set(tuple(iteritems(test_dict)))
+ else:
+ return_set = set(sample_dict)
+ return return_set
+
+
+def validate_ipv4(value, module):
+ if value:
+ address = value.split("/")
+ if len(address) != 2:
+ module.fail_json(
+ msg="address format is <ipv4 address>/<mask>, got invalid format {0}".format(
+ value
+ )
+ )
+
+ if not is_masklen(address[1]):
+ module.fail_json(
+ msg="invalid value for mask: {0}, mask should be in range 0-32".format(
+ address[1]
+ )
+ )
+
+
+def validate_ipv6(value, module):
+ if value:
+ address = value.split("/")
+ if len(address) != 2:
+ module.fail_json(
+ msg="address format is <ipv6 address>/<mask>, got invalid format {0}".format(
+ value
+ )
+ )
+ else:
+ if not 0 <= int(address[1]) <= 128:
+ module.fail_json(
+ msg="invalid value for mask: {0}, mask should be in range 0-128".format(
+ address[1]
+ )
+ )
+
+
+def validate_n_expand_ipv4(module, want):
+ # Check if input IPV4 is valid IP and expand IPV4 with its subnet mask
+ ip_addr_want = want.get("address")
+ if len(ip_addr_want.split(" ")) > 1:
+ return ip_addr_want
+ validate_ipv4(ip_addr_want, module)
+ ip = ip_addr_want.split("/")
+ if len(ip) == 2:
+ ip_addr_want = "{0} {1}".format(ip[0], to_netmask(ip[1]))
+
+ return ip_addr_want
+
+
+def netmask_to_cidr(netmask):
+ bit_range = [128, 64, 32, 16, 8, 4, 2, 1]
+ count = 0
+ cidr = 0
+ netmask_list = netmask.split(".")
+ netmask_calc = [i for i in netmask_list if int(i) != 255 and int(i) != 0]
+ if netmask_calc:
+ netmask_calc_index = netmask_list.index(netmask_calc[0])
+ elif sum(list(map(int, netmask_list))) == 0:
+ return "32"
+ else:
+ return "24"
+ for each in bit_range:
+ if cidr == int(netmask.split(".")[2]):
+ if netmask_calc_index == 1:
+ return str(8 + count)
+ elif netmask_calc_index == 2:
+ return str(8 * 2 + count)
+ elif netmask_calc_index == 3:
+ return str(8 * 3 + count)
+ break
+ cidr += each
+ count += 1
+
+
+def remove_empties_from_list(config_list):
+ ret_config = []
+ if not config_list:
+ return ret_config
+ for config in config_list:
+ ret_config.append(remove_empties(config))
+ return ret_config
+
+
+def get_device_interface_naming_mode(module):
+ intf_naming_mode = ""
+ request = {"path": "data/sonic-device-metadata:sonic-device-metadata/DEVICE_METADATA/DEVICE_METADATA_LIST=localhost", "method": GET}
+ try:
+ response = edit_config(module, to_request(module, request))
+ except ConnectionError as exc:
+ module.fail_json(msg=str(exc), code=exc.code)
+
+ if 'sonic-device-metadata:DEVICE_METADATA_LIST' in response[0][1]:
+ device_meta_data = response[0][1].get('sonic-device-metadata:DEVICE_METADATA_LIST', [])
+ if device_meta_data:
+ intf_naming_mode = device_meta_data[0].get('intf_naming_mode', 'native')
+
+ return intf_naming_mode
+
+
+STANDARD_ETH_REGEXP = r"[e|E]th\s*\d+/\d+"
+NATIVE_ETH_REGEXP = r"[e|E]th*\d+$"
+NATIVE_MODE = "native"
+STANDARD_MODE = "standard"
+
+
+def find_intf_naming_mode(intf_name):
+ ret_intf_naming_mode = NATIVE_MODE
+
+ if re.search(STANDARD_ETH_REGEXP, intf_name):
+ ret_intf_naming_mode = STANDARD_MODE
+
+ return ret_intf_naming_mode
+
+
+def validate_intf_naming_mode(intf_name, module):
+ global intf_naming_mode
+ if intf_naming_mode == "":
+ intf_naming_mode = get_device_interface_naming_mode(module)
+
+ if intf_naming_mode != "":
+ ansible_intf_naming_mode = find_intf_naming_mode(intf_name)
+ if intf_naming_mode != ansible_intf_naming_mode:
+ err = "Interface naming mode configured on switch {naming_mode}, {intf_name} is not valid".format(naming_mode=intf_naming_mode, intf_name=intf_name)
+ module.fail_json(msg=err, code=400)
+
+
+def normalize_interface_name(configs, module, namekey=None):
+ if not namekey:
+ namekey = 'name'
+
+ if configs:
+ for conf in configs:
+ if conf.get(namekey, None):
+ conf[namekey] = get_normalize_interface_name(conf[namekey], module)
+
+
+def normalize_interface_name_list(configs, module):
+ norm_configs = []
+ if configs:
+ for conf in configs:
+ conf = get_normalize_interface_name(conf, module)
+ norm_configs.append(conf)
+
+ return norm_configs
+
+
+def get_normalize_interface_name(intf_name, module):
+ change_flag = False
+ # remove the space in the given string
+ ret_intf_name = re.sub(r"\s+", "", intf_name, flags=re.UNICODE)
+ ret_intf_name = ret_intf_name.capitalize()
+
+ # serach the numeric charecter(digit)
+ match = re.search(r"\d", ret_intf_name)
+ if match:
+ change_flag = True
+ start_pos = match.start()
+ name = ret_intf_name[0:start_pos]
+ intf_id = ret_intf_name[start_pos:]
+
+ # Interface naming mode affects only ethernet ports
+ if name.startswith("Eth"):
+ validate_intf_naming_mode(intf_name, module)
+
+ if ret_intf_name.startswith("Management") or ret_intf_name.startswith("Mgmt"):
+ name = "eth"
+ intf_id = "0"
+ elif re.search(STANDARD_ETH_REGEXP, ret_intf_name):
+ name = "Eth"
+ elif re.search(NATIVE_ETH_REGEXP, ret_intf_name):
+ name = "Ethernet"
+ elif name.startswith("Po"):
+ name = "PortChannel"
+ elif name.startswith("Vlan"):
+ name = "Vlan"
+ elif name.startswith("Lo"):
+ name = "Loopback"
+ else:
+ change_flag = False
+
+ ret_intf_name = name + intf_id
+
+ if not change_flag:
+ ret_intf_name = intf_name
+
+ return ret_intf_name
+
+
+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
+
+
+def get_breakout_mode(module, name):
+ response = None
+ mode = None
+ component_name = name
+ if "/" in name:
+ component_name = name.replace("/", "%2f")
+ url = "data/openconfig-platform:components/component=%s" % (component_name)
+ request = [{"path": url, "method": GET}]
+ try:
+ response = edit_config(module, to_request(module, request))
+ 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']:
+ response = None
+ else:
+ module.fail_json(msg=str(exc), code=exc.code)
+ except Exception as err:
+ module.fail_json(msg=str(exc), code=exc.code)
+
+ if response and "openconfig-platform:component" in response[0][1]:
+ raw_port_breakout = response[0][1]['openconfig-platform:component'][0]
+ port_name = raw_port_breakout.get('name', None)
+ port_data = raw_port_breakout.get('port', None)
+ if port_name and port_data and 'openconfig-platform-port:breakout-mode' in port_data:
+ if 'groups' in port_data['openconfig-platform-port:breakout-mode']:
+ group = port_data['openconfig-platform-port:breakout-mode']['groups']['group'][0]
+ if 'config' in group:
+ cfg = group.get('config', None)
+ breakout_speed = cfg.get('breakout-speed', None)
+ num_breakouts = cfg.get('num-breakouts', None)
+ if breakout_speed and num_breakouts:
+ speed = breakout_speed.replace('openconfig-if-ethernet:SPEED_', '')
+ speed = speed.replace('GB', 'G')
+ mode = str(num_breakouts) + 'x' + speed
+ return mode
+
+
+def command_list_str_to_dict(module, warnings, cmd_list_in, exec_cmd=False):
+ cmd_list_out = []
+ for cmd in cmd_list_in:
+ cmd_out = dict()
+ nested_cmd_is_dict = False
+ if isinstance(cmd, dict):
+ cmd_out = cmd
+ else:
+ try:
+ nest_dict = ast.literal_eval(cmd)
+ nested_cmd_is_dict = isinstance(nest_dict, dict)
+ except Exception:
+ nested_cmd_is_dict = False
+
+ if nested_cmd_is_dict:
+ for key, value in nest_dict.items():
+ cmd_out[key] = value
+ else:
+ cmd_out = cmd
+
+ if exec_cmd and module.check_mode and not cmd_out['command'].startswith('show'):
+ warnings.append(
+ 'Only show commands are supported when using check mode, not '
+ 'executing %s' % cmd_out['command']
+ )
+ else:
+ cmd_list_out.append(cmd_out)
+
+ return cmd_list_out
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_aaa.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_aaa.py
new file mode 100644
index 000000000..ddc71331f
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_aaa.py
@@ -0,0 +1,215 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 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_aaa
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: sonic_aaa
+version_added: 1.1.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Abirami N (@abirami-n)
+short_description: Manage AAA and its parameters
+description:
+ - This module is used for configuration management of aaa parameters on devices running Enterprise SONiC.
+options:
+ config:
+ description:
+ - Specifies the aaa related configurations
+ type: dict
+ suboptions:
+ authentication:
+ description:
+ - Specifies the configurations required for aaa authentication
+ type: dict
+ suboptions:
+ data:
+ description:
+ - Specifies the data required for aaa authentication
+ type: dict
+ suboptions:
+ fail_through:
+ description:
+ - Specifies the state of failthrough
+ type: bool
+ local:
+ description:
+ - Enable or Disable local authentication
+ type: bool
+ group:
+ description:
+ - Specifies the method of aaa authentication
+ type: str
+ choices:
+ - ldap
+ - radius
+ - tacacs+
+
+ state:
+ description:
+ - 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.
+ default: merged
+ choices: ['merged', 'deleted']
+ type: str
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# do show aaa
+# AAA Authentication Information
+# ---------------------------------------------------------
+# failthrough : True
+# login-method : local
+
+- name: Delete aaa configurations
+ dellemc.enterprise_sonic.sonic_aaa:
+ config:
+ authentication:
+ data:
+ local: True
+ state: deleted
+
+# After state:
+# ------------
+#
+# do show aaa
+# AAA Authentication Information
+# ---------------------------------------------------------
+# failthrough : True
+# login-method :
+
+
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# do show aaa
+# AAA Authentication Information
+# ---------------------------------------------------------
+# failthrough : True
+# login-method : local
+
+- name: Delete aaa configurations
+ dellemc.enterprise_sonic.sonic_aaa:
+ config:
+ state: deleted
+
+# After state:
+# ------------
+#
+# do show aaa
+# AAA Authentication Information
+# ---------------------------------------------------------
+# failthrough :
+# login-method :
+
+
+# Using merged
+#
+# Before state:
+# -------------
+#
+# do show aaa
+# AAA Authentication Information
+# ---------------------------------------------------------
+# failthrough : False
+# login-method :
+
+- name: Merge aaa configurations
+ dellemc.enterprise_sonic.sonic_aaa:
+ config:
+ authentication:
+ data:
+ local: true
+ fail_through: true
+ state: merged
+
+# After state:
+# ------------
+#
+# do show aaa
+# AAA Authentication Information
+# ---------------------------------------------------------
+# failthrough : True
+# login-method : local
+
+
+"""
+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.aaa.aaa import AaaArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.aaa.aaa import Aaa
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=AaaArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Aaa(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_api.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_api.py
new file mode 100644
index 000000000..234603a0a
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_api.py
@@ -0,0 +1,158 @@
+#!/usr/bin/python
+#
+# (c) 2015 Peter Sprygada, <psprygada@ansible.com>
+# Copyright (c) 2020 Dell Inc.
+#
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for sonic_vlans
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_api
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Abirami N (@abirami-n)
+short_description: Manages REST operations on devices running Enterprise SONiC
+description:
+ - Manages REST operations on devices running Enterprise SONiC Distribution
+ by Dell Technologies. This module provides an implementation for working
+ with SONiC REST operations in a deterministic way.
+options:
+ url:
+ description:
+ - The HTTP path of the request after 'restconf/'.
+ type: path
+ required: true
+ body:
+ description:
+ - The body of the HTTP request/response to the web service which contains the payload.
+ type: raw
+ method:
+ description:
+ - The HTTP method of the request or response. Must be a valid method
+ accepted by the service that handles the request.
+ type: str
+ required: true
+ choices: ['GET', 'PUT', 'POST', 'PATCH', 'DELETE']
+ status_code:
+ description:
+ - A list of valid, numeric, HTTP status codes that signifies the success of a request.
+ type: list
+ elements: int
+ required: true
+"""
+EXAMPLES = """
+- name: Checks that you can connect (GET) to a page and it returns a status 200
+ dellemc.enterprise_sonic.sonic_api:
+ url: data/openconfig-interfaces:interfaces/interface=Ethernet60
+ method: "GET"
+ status_code: 200
+
+- name: Appends data to an existing interface using PATCH and verifies if it returns status 204
+ dellemc.enterprise_sonic.sonic_api:
+ url: data/openconfig-interfaces:interfaces/interface=Ethernet60/config/description
+ method: "PATCH"
+ body: {"openconfig-interfaces:description": "Eth-60"}
+ status_code: 204
+
+- name: Deletes an associated IP address using DELETE and verifies if it returns status 204
+ dellemc.enterprise_sonic.sonic_api:
+ url: >
+ data/openconfig-interfaces:interfaces/interface=Ethernet64/subinterfaces/subinterface=0/
+ openconfig-if-ip:ipv4/addresses/address=1.1.1.1/config/prefix-length
+ method: "DELETE"
+ status_code: 204
+
+- name: Adds a VLAN network instance using PUT and verifies if it returns status 204
+ dellemc.enterprise_sonic.sonic_api:
+ url: data/openconfig-network-instance:network-instances/network-instance=Vlan100/
+ method: "PUT"
+ body: {"openconfig-network-instance:network-instance": [{"name": "Vlan100","config": {"name": "Vlan100"}}]}
+ status_code: 204
+
+- name: Adds a prefix-set to a routing policy using POST and verifies if it returns 201
+ dellemc.enterprise_sonic.sonic_api:
+ url: data/openconfig-routing-policy:routing-policy/defined-sets/prefix-sets/prefix-set=p1
+ method: "POST"
+ body: {"openconfig-routing-policy:config": {"name": "p1","mode": "IPV4" }}
+ status_code: 201
+
+"""
+RETURN = """
+response:
+ description: The response at the network device end for the REST call which contains the status code.
+ returned: always
+ type: list
+ sample: {"response": [ 204,{""}]}
+msg:
+ description: The HTTP error message from the request.
+ returned: HTTP Error
+ type: str
+"""
+
+from ansible.module_utils.connection import ConnectionError
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import edit_config, to_request
+
+
+def initiate_request(module):
+ """Get all the data available in chassis"""
+ url = module.params['url']
+ body = module.params['body']
+ method = module.params['method']
+ if method == "GET" or method == "DELETE":
+ request = to_request(module, [{"path": url, "method": method}])
+ elif method == "PATCH" or method == "PUT" or method == "POST":
+ request = to_request(module, [{"path": url, "method": method, "data": body}])
+
+ try:
+ response = edit_config(module, request)
+ except ConnectionError as exc:
+ module.fail_json(msg=to_text(exc))
+ return response
+
+
+def main():
+
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ argument_spec = dict(
+ url=dict(type='path', required=True),
+ body=dict(type='raw', required=False),
+ method=dict(type='str', choices=['GET', 'PUT', 'PATCH', 'DELETE', 'POST'], required=True),
+ status_code=dict(type='list', elements='int', required=True),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec,
+ supports_check_mode=True)
+
+ result = dict(
+ changed=False,
+ )
+ response = initiate_request(module)
+ response_code = response[0][0]
+ status_code = module.params['status_code']
+ if response_code == int(status_code[0]) and response_code in (201, 204):
+ result.update({'changed': True})
+
+ result.update({
+ 'response': response,
+ })
+ 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
new file mode 100644
index 000000000..bc53ca40c
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp.py
@@ -0,0 +1,390 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_bgp
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_bgp
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Dhivya P (@dhivayp)
+short_description: Manage global BGP and its parameters
+description:
+ - This module provides configuration management of global BGP parameters on devices running Enterprise SONiC Distribution by Dell Technologies.
+options:
+ config:
+ description:
+ - Specifies the BGP-related configuration.
+ type: list
+ elements: dict
+ suboptions:
+ bgp_as:
+ description:
+ - Specifies the BGP autonomous system (AS) number to configure on the device.
+ type: str
+ required: true
+ vrf_name:
+ description:
+ - Specifies the VRF name.
+ type: str
+ default: 'default'
+ router_id:
+ description:
+ - Configures the BGP routing process router-id value.
+ type: str
+ log_neighbor_changes:
+ description:
+ - Enables/disables logging neighbor up/down and reset reason.
+ type: bool
+ max_med:
+ description:
+ - Configure max med and its parameters
+ type: dict
+ suboptions:
+ on_startup:
+ description:
+ - On startup time and max-med value
+ type: dict
+ suboptions:
+ timer:
+ description:
+ - Configures on startup time
+ type: int
+ med_val:
+ description:
+ - on startup med value
+ type: int
+ timers:
+ description:
+ - Adjust routing timers
+ type: dict
+ suboptions:
+ holdtime:
+ description:
+ - Configures hold-time
+ type: int
+ keepalive_interval:
+ description:
+ - Configures keepalive-interval
+ type: int
+ bestpath:
+ description:
+ - Configures the BGP best-path.
+ type: dict
+ suboptions:
+ as_path:
+ description:
+ - Configures the as-path values.
+ type: dict
+ suboptions:
+ confed:
+ description:
+ - Configures the confed values of as-path.
+ type: bool
+ ignore:
+ description:
+ - Configures the ignore values of as-path.
+ type: bool
+ multipath_relax:
+ description:
+ - Configures the multipath_relax values of as-path.
+ type: bool
+ multipath_relax_as_set:
+ description:
+ - Configures the multipath_relax_as_set values of as-path.
+ type: bool
+ compare_routerid:
+ description:
+ - Configures the compare_routerid.
+ type: bool
+ med:
+ description:
+ - Configures the med values.
+ type: dict
+ suboptions:
+ confed:
+ description:
+ - Configures the confed values of med.
+ type: bool
+ missing_as_worst:
+ description:
+ - Configures the missing_as_worst values of as-path.
+ type: bool
+ always_compare_med:
+ description:
+ - Allows comparing meds from different neighbors if set to true
+ type: bool
+ 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.
+ default: merged
+ choices: ['merged', 'deleted']
+ type: str
+"""
+EXAMPLES = """
+# Using deleted
+#
+# 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
+#!
+#
+- name: Delete BGP Global attributes
+ dellemc.enterprise_sonic.sonic_bgp:
+ config:
+ - bgp_as: 4
+ router_id: 10.2.2.4
+ log_neighbor_changes: False
+ bestpath:
+ as_path:
+ confed: True
+ ignore: True
+ multipath_relax: False
+ multipath_relax_as_set: True
+ compare_routerid: True
+ med:
+ confed: True
+ missing_as_worst: True
+ - bgp_as: 10
+ router_id: 10.2.2.32
+ log_neighbor_changes: True
+ vrf_name: 'VrfCheck1'
+ - bgp_as: 11
+ log_neighbor_changes: True
+ vrf_name: 'VrfCheck2'
+ bestpath:
+ as_path:
+ confed: False
+ ignore: True
+ multipath_relax_as_set: True
+ compare_routerid: True
+ med:
+ confed: True
+ missing_as_worst: True
+ state: deleted
+
+
+# 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
+#!
+
+
+# Using deleted
+#
+# 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
+#!
+
+- name: Deletes all the bgp global configurations
+ dellemc.enterprise_sonic.sonic_bgp:
+ config:
+ state: deleted
+
+# After state:
+# ------------
+#
+#!
+#!
+
+
+# Using merged
+#
+# Before state:
+# -------------
+#
+#!
+#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
+ log_neighbor_changes: False
+ timers:
+ holdtime: 20
+ keepalive_interval: 30
+ bestpath:
+ as_path:
+ confed: True
+ ignore: True
+ multipath_relax: False
+ multipath_relax_as_set: True
+ compare_routerid: True
+ med:
+ confed: True
+ missing_as_worst: True
+ always_compare_med: True
+ max_med:
+ on_startup:
+ timer: 667
+ med_val: 7878
+ - bgp_as: 10
+ router_id: 10.2.2.32
+ log_neighbor_changes: True
+ vrf_name: 'VrfCheck1'
+ - bgp_as: 11
+ log_neighbor_changes: True
+ vrf_name: 'VrfCheck2'
+ bestpath:
+ as_path:
+ confed: False
+ ignore: True
+ multipath_relax_as_set: True
+ compare_routerid: True
+ med:
+ confed: True
+ missing_as_worst: True
+ state: merged
+#
+# After 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
+# always-compare-med
+# max-med on-startup 667 7878
+# timers 20 30
+#
+#!
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned is always 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
+ 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.bgp.bgp import BgpArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.bgp.bgp import Bgp
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=BgpArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Bgp(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
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
new file mode 100644
index 000000000..6d55355c9
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_af.py
@@ -0,0 +1,414 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_bgp_af
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: sonic_bgp_af
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Niraimadaiselvam M (@niraimadaiselvamm)
+short_description: Manage global BGP address-family and its parameters
+description:
+ - This module provides configuration management of global BGP_AF parameters on devices running Enterprise SONiC.
+ - bgp_as and vrf_name must be created in advance on the device.
+options:
+ config:
+ description:
+ - Specifies the BGP_AF related configuration.
+ type: list
+ elements: dict
+ suboptions:
+ bgp_as:
+ description:
+ - Specifies the BGP autonomous system (AS) number which is already configured on the device.
+ type: str
+ required: true
+ vrf_name:
+ description:
+ - Specifies the VRF name which is already configured on the device.
+ type: str
+ default: 'default'
+ address_family:
+ description:
+ - Specifies BGP address family related configurations.
+ type: dict
+ suboptions:
+ afis:
+ description:
+ - List of address families, such as ipv4, ipv6, and l2vpn.
+ - afi and safi are required together.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - Type of address family to configure.
+ type: str
+ choices:
+ - ipv4
+ - ipv6
+ - l2vpn
+ required: True
+ safi:
+ description:
+ - Specifies the type of communication for the address family.
+ type: str
+ choices:
+ - unicast
+ - evpn
+ default: unicast
+ dampening:
+ description:
+ - Enable route flap dampening if set to true
+ type: bool
+ network:
+ description:
+ - Enable routing on an IP network for each prefix provided in the network
+ type: list
+ elements: str
+ redistribute:
+ description:
+ - Specifies the redistribute information from another routing protocol.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - Specifies the protocol for configuring redistribute information.
+ type: str
+ choices: ['ospf', 'static', 'connected']
+ required: True
+ metric:
+ description:
+ - Specifies the metric for redistributed routes.
+ type: str
+ route_map:
+ description:
+ - Specifies the route map reference.
+ type: str
+ advertise_pip:
+ description:
+ - Enables advertise PIP
+ type: bool
+ advertise_pip_ip:
+ description:
+ - PIP IPv4 address
+ type: str
+ advertise_pip_peer_ip:
+ description:
+ - PIP peer IPv4 address
+ type: str
+ advertise_svi_ip:
+ description:
+ - Enables advertise SVI MACIP routes
+ type: bool
+ route_advertise_list:
+ description:
+ - List of advertise routes
+ type: list
+ elements: dict
+ suboptions:
+ advertise_afi:
+ required: True
+ type: str
+ choices:
+ - ipv4
+ - ipv6
+ description:
+ - Specifies the address family
+ route_map:
+ type: str
+ description:
+ - Specifies the route-map reference
+ advertise_default_gw:
+ description:
+ - Specifies the advertise default gateway flag.
+ type: bool
+ advertise_all_vni:
+ description:
+ - Specifies the advertise all vni flag.
+ type: bool
+ max_path:
+ description:
+ - Specifies the maximum paths of ibgp and ebgp count.
+ type: dict
+ suboptions:
+ ibgp:
+ description:
+ - Specifies the count of the ibgp multipaths count.
+ type: int
+ ebgp:
+ description:
+ - Specifies the count of the ebgp multipaths count.
+ type: int
+ 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.
+ default: merged
+ choices: ['merged', 'deleted']
+ type: str
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#do show running-configuration bgp
+#!
+#router bgp 51
+# router-id 111.2.2.41
+# timers 60 180
+# !
+# address-family ipv4 unicast
+# maximum-paths 1
+# maximum-paths ibgp 1
+# dampening
+# !
+# address-family ipv6 unicast
+# redistribute connected route-map bb metric 21
+# redistribute ospf route-map aa metric 27
+# redistribute static route-map bb metric 26
+# maximum-paths 4
+# maximum-paths ibgp 5
+# !
+# address-family l2vpn evpn
+# advertise-svi-ip
+# advertise ipv6 unicast route-map aa
+# advertise-pip ip 1.1.1.1 peer-ip 2.2.2.2
+#!
+#
+- name: Delete BGP Address family configuration from the device
+ dellemc.enterprise_sonic.sonic_bgp_af:
+ config:
+ - bgp_as: 51
+ address_family:
+ afis:
+ - afi: l2vpn
+ safi: evpn
+ advertise_pip: True
+ advertise_pip_ip: "1.1.1.1"
+ advertise_pip_peer_ip: "2.2.2.2"
+ advertise_svi_ip: True
+ advertise_all_vni: False
+ advertise_default_gw: False
+ route_advertise_list:
+ - advertise_afi: ipv6
+ route_map: aa
+ - afi: ipv4
+ safi: unicast
+ - afi: ipv6
+ safi: unicast
+ max_path:
+ ebgp: 2
+ ibgp: 5
+ redistribute:
+ - metric: "21"
+ protocol: connected
+ route_map: bb
+ - metric: "27"
+ protocol: ospf
+ route_map: aa
+ - metric: "26"
+ protocol: static
+ route_map: bb
+ state: deleted
+
+# After state:
+# ------------
+#
+#do show running-configuration bgp
+#!
+#router bgp 51
+# router-id 111.2.2.41
+# timers 60 180
+# !
+# address-family ipv6 unicast
+# !
+# address-family l2vpn evpn
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#do show running-configuration bgp
+#!
+#router bgp 51
+# router-id 111.2.2.41
+# timers 60 180
+# !
+# address-family ipv6 unicast
+# !
+# address-family l2vpn evpn
+#
+- name: Delete All BGP address family configurations
+ dellemc.enterprise_sonic.sonic_bgp_af:
+ config:
+ state: deleted
+
+
+# After state:
+# ------------
+#
+#do show running-configuration bgp
+#!
+#router bgp 51
+# router-id 111.2.2.41
+# timers 60 180
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#do show running-configuration bgp
+#!
+#router bgp 51
+# router-id 111.2.2.41
+# timers 60 180
+# !
+# address-family l2vpn evpn
+#
+- name: Merge provided BGP address family configuration on the device.
+ 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
+ - afi: ipv4
+ safi: unicast
+ network:
+ - 2.2.2.2/16
+ - 192.168.10.1/32
+ dampening: True
+ - afi: ipv6
+ safi: unicast
+ max_path:
+ ebgp: 4
+ ibgp: 5
+ redistribute:
+ - metric: "21"
+ protocol: connected
+ route_map: bb
+ - metric: "27"
+ protocol: ospf
+ route_map: aa
+ - metric: "26"
+ protocol: static
+ route_map: bb
+ state: merged
+# After state:
+# ------------
+#
+#do show running-configuration bgp
+#!
+#router bgp 51
+# router-id 111.2.2.41
+# timers 60 180
+# !
+# address-family ipv4 unicast
+# network 2.2.2.2/16
+# network 192.168.10.1/32
+# dampening
+# !
+# address-family ipv6 unicast
+# redistribute connected route-map bb metric 21
+# redistribute ospf route-map aa metric 27
+# redistribute static route-map bb metric 26
+# maximum-paths 4
+# maximum-paths ibgp 5
+# !
+# address-family l2vpn evpn
+# advertise-svi-ip
+# advertise ipv4 unicast route-map bb
+# advertise-pip ip 3.3.3.3 peer-ip 4.4.4.4
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned is always in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned always 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.bgp_af.bgp_af import Bgp_afArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.bgp_af.bgp_af import Bgp_af
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Bgp_afArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Bgp_af(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
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
new file mode 100644
index 000000000..bd2ff74a1
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_as_paths.py
@@ -0,0 +1,224 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_bgp_as_paths
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_bgp_as_paths
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+short_description: Manage BGP autonomous system path (or as-path-list) and its parameters
+description:
+ - This module provides configuration management of BGP bgp_as_paths for devices
+ running Enterprise SONiC Distribution by Dell Technologies.
+author: Kumaraguru Narayanan (@nkumaraguru)
+options:
+ config:
+ description: A list of 'bgp_as_paths' configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ required: True
+ type: str
+ description:
+ - Name of as-path-list.
+ members:
+ required: False
+ type: list
+ elements: str
+ description:
+ - Members of this BGP as-path; regular expression string can be provided.
+ permit:
+ required: False
+ type: bool
+ description:
+ - Permits or denies this as path.
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using deleted
+
+# Before state:
+# -------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+# 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
+
+# After state:
+# ------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+# action:
+# members: 808.*
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+# action: permit
+# members: 808.*,909.*
+# AS path list test1:
+# action: deny
+# members: 608.*,709.*
+
+- name: Deletes BGP as-path list
+ dellemc.enterprise_sonic.sonic_bgp_as_paths:
+ config:
+ - name: test
+ members:
+ state: deleted
+
+# After state:
+# ------------
+#
+# show bgp as-path-access-list
+# AS path list test1:
+# action: deny
+# members: 608.*,709.*
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+# action: permit
+# members: 808.*,909.*
+
+- name: Deletes BGP as-path list
+ dellemc.enterprise_sonic.sonic_bgp_as_paths:
+ config:
+ state: deleted
+
+# After state:
+# ------------
+#
+# show bgp as-path-access-list
+# (No bgp as-path-access-list configuration present)
+
+
+# Using merged
+
+# Before state:
+# -------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+
+- name: Adds 909.* to test as-path list
+ dellemc.enterprise_sonic.sonic_bgp_as_paths:
+ config:
+ - name: test
+ members:
+ - 909.*
+ permit: true
+ state: merged
+
+# After state:
+# ------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+# action: permit
+# members: 909.*
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned is always 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
+ 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.bgp_as_paths.bgp_as_paths import Bgp_as_pathsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.bgp_as_paths.bgp_as_paths import Bgp_as_paths
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Bgp_as_pathsArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Bgp_as_paths(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
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
new file mode 100644
index 000000000..08c8dcc7f
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_communities.py
@@ -0,0 +1,301 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_bgp_communities
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: sonic_bgp_communities
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+short_description: Manage BGP community and its parameters
+description:
+ - This module provides configuration management of BGP bgp_communities for device
+ running Enterprise SONiC Distribution by Dell Technologies.
+author: Kumaraguru Narayanan (@nkumaraguru)
+options:
+ config:
+ description: A list of 'bgp_communities' configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ required: True
+ type: str
+ description:
+ - Name of the BGP communitylist.
+ type:
+ type: str
+ description:
+ - Whether it is a standard or expanded community-list entry.
+ required: False
+ choices:
+ - standard
+ - expanded
+ default: standard
+ permit:
+ required: False
+ type: bool
+ description:
+ - Permits or denies this community.
+ aann:
+ required: False
+ type: str
+ description:
+ - Community number aa:nn format 0..65535:0..65535; applicable for standard BGP community type.
+ local_as:
+ required: False
+ type: bool
+ description:
+ - Do not send outside local AS (well-known community); applicable for standard BGP community type.
+ no_advertise:
+ required: False
+ type: bool
+ description:
+ - Do not advertise to any peer (well-known community); applicable for standard BGP community type.
+ no_export:
+ required: False
+ type: bool
+ description:
+ - Do not export to next AS (well-known community); applicable for standard BGP community type.
+ no_peer:
+ required: False
+ type: bool
+ description:
+ - Do not export to next AS (well-known community); applicable for standard BGP community type.
+ members:
+ required: False
+ type: dict
+ suboptions:
+ regex:
+ type: list
+ elements: str
+ required: False
+ description:
+ - Members of this BGP community list. Regular expression string can be given here. Applicable for expanded BGP community type.
+ description:
+ - Members of this BGP community list.
+ match:
+ required: False
+ type: str
+ description:
+ - Matches any/all of the members.
+ choices:
+ - ALL
+ - ANY
+ default: ANY
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using deleted
+
+# Before state:
+# -------------
+#
+# show bgp community-list
+# Standard community list test: match: ANY
+# 101
+# 201
+# Standard community list test1: match: ANY
+# 301
+
+- name: Deletes BGP community member
+ dellemc.enterprise_sonic.sonic_bgp_communities:
+ config:
+ - name: test
+ members:
+ regex:
+ - 201
+ state: deleted
+
+# After state:
+# ------------
+#
+# show bgp community-list
+# Standard community list test: match: ANY
+# 101
+# Standard community list test1: match: ANY
+# 301
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# show bgp community-list
+# Standard community list test: match: ANY
+# 101
+# Expanded community list test1: match: ANY
+# 201
+
+- name: Deletes a single BGP community
+ dellemc.enterprise_sonic.sonic_bgp_communities:
+ config:
+ - name: test
+ members:
+ state: deleted
+
+# After state:
+# ------------
+#
+# show bgp community-list
+# Expanded community list test1: match: ANY
+# 201
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# show bgp community-list
+# Standard community list test: match: ANY
+# 101
+# Expanded community list test1: match: ANY
+# 201
+
+- name: Delete All BGP communities
+ dellemc.enterprise_sonic.sonic_bgp_communities:
+ config:
+ state: deleted
+
+# After state:
+# ------------
+#
+# show bgp community-list
+#
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# show bgp community-list
+# Standard community list test: match: ANY
+# 101
+# Expanded community list test1: match: ANY
+# 201
+
+- name: Deletes all members in a single BGP community
+ dellemc.enterprise_sonic.sonic_bgp_communities:
+ config:
+ - name: test
+ members:
+ regex:
+ state: deleted
+
+# After state:
+# ------------
+#
+# show bgp community-list
+# Expanded community list test: match: ANY
+# Expanded community list test1: match: ANY
+# 201
+
+
+# Using merged
+
+# Before state:
+# -------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+
+- name: Adds 909.* to test as-path list
+ dellemc.enterprise_sonic.sonic_bgp_as_paths:
+ config:
+ - name: test
+ members:
+ - 909.*
+ state: merged
+
+# After state:
+# ------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+# members: 909.*
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration that is returned is always in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration that is returned is always in the same format
+ of the parameters above.
+commands:
+ description: The set of commands that are 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.bgp_communities.bgp_communities import Bgp_communitiesArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.bgp_communities.bgp_communities import Bgp_communities
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Bgp_communitiesArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Bgp_communities(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
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
new file mode 100644
index 000000000..c2af0c488
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_ext_communities.py
@@ -0,0 +1,288 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_bgp_ext_communities
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_bgp_ext_communities
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+short_description: Manage BGP extended community-list and its parameters
+description:
+ - This module provides configuration management of BGP extcommunity-list for devices running
+ Enterprise SONiC Distribution by Dell Technologies.
+author: Kumaraguru Narayanan (@nkumaraguru)
+options:
+ config:
+ description: A list of 'bgp_extcommunity_list' configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ required: True
+ type: str
+ description:
+ - Name of the BGP ext communitylist.
+ type:
+ type: str
+ description:
+ - Whether it is a standard or expanded ext community_list entry.
+ required: False
+ choices:
+ - standard
+ - expanded
+ default: standard
+ permit:
+ required: False
+ type: bool
+ description:
+ - Permits or denies this community.
+ members:
+ required: False
+ type: dict
+ suboptions:
+ regex:
+ type: list
+ elements: str
+ required: False
+ description:
+ - Members of this BGP ext community list. Regular expression string can be given here. Applicable for expanded ext BGP community type.
+ route_target:
+ type: list
+ elements: str
+ required: False
+ description:
+ - Members of this BGP ext community list. The format of route_target is in either 0..65535:0..65535 or A.B.C.D:[1..65535] format.
+ route_origin:
+ type: list
+ elements: str
+ required: False
+ description:
+ - Members of this BGP ext community list. The format of route_origin is in either 0..65535:0..65535 or A.B.C.D:[1..65535] format.
+ description:
+ - Members of this BGP ext community list.
+ match:
+ required: False
+ type: str
+ description:
+ - Matches any/all of the the members.
+ choices:
+ - all
+ - any
+ default: any
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using deleted
+
+# Before state:
+# -------------
+#
+# show bgp ext-community-list
+# Standard extended community list test: match: ANY
+# rt:101:101
+# rt:201:201
+
+- name: Deletes a BGP ext community member
+ dellemc.enterprise_sonic.sonic_bgp_ext_communities:
+ config:
+ - name: test
+ members:
+ regex:
+ - 201:201
+ state: deleted
+
+# After state:
+# ------------
+#
+# show bgp ext-community-list
+# Standard extended community list test: match: ANY
+# rt:101:101
+#
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# show bgp ext-community-list
+# Standard extended community list test: match: ANY
+# 101
+# Expanded extended community list test1: match: ANY
+# 201
+
+- name: Deletes a single BGP extended community
+ dellemc.enterprise_sonic.sonic_bgp_ext_communities:
+ config:
+ - name: test1
+ members:
+ state: deleted
+
+# After state:
+# ------------
+#
+# show bgp ext-community-list
+# Standard extended community list test: match: ANY
+# 101
+#
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# show bgp ext-community-list
+# Standard extended community list test: match: ANY
+# 101
+# Expanded extended community list test1: match: ANY
+# 201
+
+- name: Deletes all BGP extended communities
+ dellemc.enterprise_sonic.sonic_bgp_ext_communities:
+ config:
+ state: deleted
+
+# After state:
+# ------------
+#
+# show bgp ext-community-list
+#
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# show bgp ext-community-list
+# Standard extended community list test: match: ANY
+# 101
+# Expanded extended community list test1: match: ANY
+# 201
+
+- name: Deletes all members in a single BGP extended community
+ dellemc.enterprise_sonic.sonic_bgp_ext_communities:
+ config:
+ - name: test1
+ members:
+ regex:
+ state: deleted
+
+# After state:
+# ------------
+#
+# show bgp ext-community-list
+# Standard extended community list test: match: ANY
+# 101
+# Expanded extended community list test1: match: ANY
+#
+
+
+# Using merged
+
+# Before state:
+# -------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+
+- name: Adds 909.* to test as-path list
+ dellemc.enterprise_sonic.sonic_bgp_as_paths:
+ config:
+ - name: test
+ members:
+ - 909.*
+ state: merged
+
+# After state:
+# ------------
+#
+# show bgp as-path-access-list
+# AS path list test:
+# members: 909.*
+
+
+"""
+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.bgp_ext_communities.bgp_ext_communities import (
+ Bgp_ext_communitiesArgs,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.bgp_ext_communities.bgp_ext_communities import Bgp_ext_communities
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Bgp_ext_communitiesArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Bgp_ext_communities(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
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
new file mode 100644
index 000000000..19aeb6fc9
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors.py
@@ -0,0 +1,1112 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_bgp_neighbors
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_bgp_neighbors
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+short_description: Manage a BGP neighbor and its parameters
+description:
+ - This module provides configuration management of global BGP_NEIGHBORS parameters on devices running Enterprise SONiC.
+ - bgp_as and vrf_name must be created on the device in advance.
+author: Abirami N (@abirami-n)
+options:
+ config:
+ description: Specifies the BGP neighbors related configuration.
+ type: list
+ elements: dict
+ suboptions:
+ bgp_as:
+ description:
+ - Specifies the BGP autonomous system (AS) number which is already configured on the device.
+ type: str
+ required: True
+ vrf_name:
+ description:
+ - Specifies the VRF name which is already configured on the device.
+ default: default
+ type: str
+ peer_group:
+ description: Specifies the list of peer groups.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the peer group.
+ type: str
+ required: True
+ remote_as:
+ description:
+ - Remote AS of the BGP peer group to configure.
+ - peer_as and peer_type are mutually exclusive.
+ type: dict
+ suboptions:
+ peer_as:
+ description:
+ - Specifies remote AS number.
+ - The range is from 1 to 4294967295.
+ type: int
+ peer_type:
+ description:
+ - Specifies the type of BGP peer.
+ type: str
+ choices:
+ - internal
+ - external
+ bfd:
+ description:
+ - Enables or disables BFD.
+ type: dict
+ suboptions:
+ enabled:
+ description:
+ - Enables BFD liveliness check for a BGP peer.
+ type: bool
+ check_failure:
+ description:
+ - Link dataplane status with control plane.
+ type: bool
+ profile:
+ description:
+ - BFD Profile name.
+ type: str
+ advertisement_interval:
+ description:
+ - Specifies the minimum interval between sending BGP routing updates.
+ - The range is from 0 to 600.
+ type: int
+ timers:
+ description:
+ - Specifies BGP peer group timer related configurations.
+ type: dict
+ suboptions:
+ keepalive:
+ description:
+ - Frequency with which the device sends keepalive messages to its peer, in seconds.
+ - The range is from 0 to 65535.
+ type: int
+ holdtime:
+ description:
+ - Interval after not receiving a keepalive message that Enterprise SONiC declares a peer dead, in seconds.
+ - The range is from 0 to 65535.
+ type: int
+ connect_retry:
+ description:
+ - Time interval in seconds between attempts to establish a session with the peer.
+ - The range is from 1 to 65535.
+ type: int
+ capability:
+ description:
+ - Specifies capability attributes to this peer group.
+ type: dict
+ suboptions:
+ dynamic:
+ description:
+ - Enables or disables dynamic capability to this peer group.
+ type: bool
+ extended_nexthop:
+ description:
+ - Enables or disables advertise extended next-hop capability to the peer.
+ type: bool
+ auth_pwd:
+ description:
+ - Configuration for peer group authentication password.
+ type: dict
+ suboptions:
+ pwd:
+ description:
+ - Authentication password for the peer group.
+ type: str
+ required: True
+ encrypted:
+ description:
+ - Indicates whether the password is encrypted text.
+ type: bool
+ default: False
+ pg_description:
+ description:
+ - A textual description of the peer group.
+ type: str
+ disable_connected_check:
+ description:
+ - Disables EBGP conntected route check.
+ type: bool
+ dont_negotiate_capability:
+ description:
+ - Disables capability negotiation.
+ type: bool
+ ebgp_multihop:
+ description:
+ - Allow EBGP peers not on directly connected networks.
+ type: dict
+ suboptions:
+ enabled:
+ description:
+ - Enables the referenced group or peers to be indirectly connected.
+ type: bool
+ default: False
+ multihop_ttl:
+ description:
+ - Time-to-live value to use when packets are sent to the referenced group or peers and ebgp-multihop is enabled.
+ type: int
+ enforce_first_as:
+ description:
+ - Enforces the first AS for EBGP routes.
+ type: bool
+ enforce_multihop:
+ description:
+ - Enforces EBGP multihop performance for peer.
+ type: bool
+ local_address:
+ description:
+ - Set the local IP address to use for the session when sending BGP update messages.
+ type: str
+ local_as:
+ description:
+ - Specifies local autonomous system number.
+ type: dict
+ suboptions:
+ as:
+ description:
+ - Local autonomous system number.
+ type: int
+ required: True
+ no_prepend:
+ description:
+ - Do not prepend the local-as number in AS-Path advertisements.
+ type: bool
+ replace_as:
+ description:
+ - Replace the configured AS Number with the local-as number in AS-Path advertisements.
+ type: bool
+ override_capability:
+ description:
+ - Override capability negotiation result.
+ type: bool
+ passive:
+ description:
+ - Do not send open messages to this peer.
+ type: bool
+ default: False
+ shutdown_msg:
+ description:
+ - Add a shutdown message.
+ type: str
+ solo:
+ description:
+ - Indicates that routes advertised by the peer should not be reflected back to the peer.
+ type: bool
+ strict_capability_match:
+ description:
+ - Enables strict capability negotiation match.
+ type: bool
+ ttl_security:
+ description:
+ - Enforces only the peers that are specified number of hops away will be allowed to become peers.
+ type: int
+ address_family:
+ description:
+ - Holds of list of address families associated to the peergroup.
+ type: dict
+ suboptions:
+ afis:
+ description:
+ - List of address families with afi, safi, activate and allowas-in parameters.
+ - afi and safi are required together.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - Holds afi mode.
+ type: str
+ choices:
+ - ipv4
+ - ipv6
+ - l2vpn
+ safi:
+ description:
+ - Holds safi mode.
+ type: str
+ choices:
+ - unicast
+ - evpn
+ activate:
+ description:
+ - Enable or disable activate.
+ type: bool
+ allowas_in:
+ description:
+ - Holds AS value.
+ - The origin and value are mutually exclusive.
+ type: dict
+ suboptions:
+ origin:
+ description:
+ - Set AS as the origin.
+ type: bool
+ value:
+ description:
+ - Holds AS number in the range 1-10.
+ type: int
+ ip_afi:
+ description:
+ - Common configuration attributes for IPv4 and IPv6 unicast address families.
+ type: dict
+ suboptions:
+ default_policy_name:
+ description:
+ - Specifies routing policy definition.
+ type: str
+ send_default_route:
+ description:
+ - Enable or disable sending of default-route to the peer.
+ type: bool
+ default: False
+ prefix_limit:
+ description:
+ - Specifies prefix limit attributes.
+ type: dict
+ suboptions:
+ max_prefixes:
+ description:
+ - Maximum number of prefixes that will be accepted from the peer.
+ type: int
+ prevent_teardown:
+ description:
+ - Enable or disable teardown of BGP session when maximum prefix limit is exceeded.
+ type: bool
+ default: False
+ warning_threshold:
+ description:
+ - Threshold on number of prefixes that can be received from a peer before generation of warning messages.
+ - Expressed as a percentage of max-prefixes.
+ type: int
+ restart_timer:
+ description:
+ - Time interval in seconds after which the BGP session is re-established after being torn down.
+ type: int
+ prefix_list_in:
+ description:
+ - Inbound route filtering policy for a peer.
+ type: str
+ prefix_list_out:
+ description:
+ - Outbound route filtering policy for a peer.
+ type: str
+ neighbors:
+ description: Specifies BGP neighbor-related configurations.
+ type: list
+ elements: dict
+ suboptions:
+ neighbor:
+ description:
+ - Neighbor router address.
+ type: str
+ required: True
+ remote_as:
+ description:
+ - Remote AS of the BGP neighbor to configure.
+ - peer_as and peer_type are mutually exclusive.
+ type: dict
+ suboptions:
+ peer_as:
+ description:
+ - Specifies remote AS number.
+ - The range is from 1 to 4294967295.
+ type: int
+ peer_type:
+ description:
+ - Specifies the type of BGP peer.
+ type: str
+ choices:
+ - internal
+ - external
+ bfd:
+ description:
+ - Enables or disables BFD.
+ type: dict
+ suboptions:
+ enabled:
+ description:
+ - Enables BFD liveliness check for a BGP neighbor.
+ type: bool
+ check_failure:
+ description:
+ - Link dataplane status with control plane.
+ type: bool
+ profile:
+ description:
+ - BFD Profile name.
+ type: str
+ advertisement_interval:
+ description:
+ - Specifies the minimum interval between sending BGP routing updates.
+ - The range is from 0 to 600.
+ type: int
+ peer_group:
+ description:
+ - The name of the peer group that the neighbor is a member of.
+ type: str
+ timers:
+ description:
+ - Specifies BGP neighbor timer-related configurations.
+ type: dict
+ suboptions:
+ keepalive:
+ description:
+ - Frequency with which the device sends keepalive messages to its peer, in seconds.
+ - The range is from 0 to 65535.
+ type: int
+ holdtime:
+ description:
+ - Interval after not receiving a keepalive message that SONiC declares a peer dead, in seconds.
+ - The range is from 0 to 65535.
+ type: int
+ connect_retry:
+ description:
+ - Time interval in seconds between attempts to establish a session with the peer.
+ - The range is from 1 to 65535.
+ type: int
+ capability:
+ description:
+ - Specifies capability attributes to this neighbor.
+ type: dict
+ suboptions:
+ dynamic:
+ description:
+ - Enables or disables dynamic capability to this neighbor.
+ type: bool
+ extended_nexthop:
+ description:
+ - Enables or disables advertise extended next-hop capability to the peer.
+ type: bool
+ auth_pwd:
+ description:
+ - Configuration for neighbor group authentication password.
+ type: dict
+ suboptions:
+ pwd:
+ description:
+ - Authentication password for the neighbor group.
+ type: str
+ required: True
+ encrypted:
+ description:
+ - Indicates whether the password is encrypted text.
+ type: bool
+ default: False
+ nbr_description:
+ description:
+ - A textual description of the neighbor.
+ type: str
+ disable_connected_check:
+ description:
+ - Disables EBGP conntected route check.
+ type: bool
+ dont_negotiate_capability:
+ description:
+ - Disables capability negotiation.
+ type: bool
+ ebgp_multihop:
+ description:
+ - Allow EBGP neighbors not on directly connected networks.
+ type: dict
+ suboptions:
+ enabled:
+ description:
+ - Enables the referenced group or neighbors to be indirectly connected.
+ type: bool
+ default: False
+ multihop_ttl:
+ description:
+ - Time-to-live value to use when packets are sent to the referenced group or neighbors and ebgp-multihop is enabled.
+ type: int
+ enforce_first_as:
+ description:
+ - Enforces the first AS for EBGP routes.
+ type: bool
+ enforce_multihop:
+ description:
+ - Enforces EBGP multihop performance for neighbor.
+ type: bool
+ local_address:
+ description:
+ - Set the local IP address to use for the session when sending BGP update messages.
+ type: str
+ local_as:
+ description:
+ - Specifies local autonomous system number.
+ type: dict
+ suboptions:
+ as:
+ description:
+ - Local autonomous system number.
+ type: int
+ required: True
+ no_prepend:
+ description:
+ - Do not prepend the local-as number in AS-Path advertisements.
+ type: bool
+ replace_as:
+ description:
+ - Replace the configured AS Number with the local-as number in AS-Path advertisements.
+ type: bool
+ override_capability:
+ description:
+ - Override capability negotiation result.
+ type: bool
+ passive:
+ description:
+ - Do not send open messages to this neighbor.
+ type: bool
+ default: False
+ port:
+ description:
+ - Neighbor's BGP port.
+ type: int
+ shutdown_msg:
+ description:
+ - Add a shutdown message.
+ type: str
+ solo:
+ description:
+ - Indicates that routes advertised by the peer should not be reflected back to the peer.
+ type: bool
+ strict_capability_match:
+ description:
+ - Enables strict capability negotiation match.
+ type: bool
+ ttl_security:
+ description:
+ - Enforces only the neighbors that are specified number of hops away will be allowed to become neighbors.
+ type: int
+ v6only:
+ description:
+ - Enables BGP with v6 link-local only.
+ type: bool
+
+ 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.
+ default: merged
+ type: str
+ choices:
+ - merged
+ - deleted
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before state:
+# -------------
+#router bgp 11 vrf VrfCheck2
+# network import-check
+# timers 60 180
+#!
+#router bgp 51 vrf VrfReg1
+# network import-check
+# timers 60 180
+# !
+# neighbor interface Eth1/3
+#!
+#router bgp 11
+# network import-check
+# timers 60 180
+# !
+# neighbor 192.168.1.4
+# !
+# peer-group SP1
+# bfd
+# capability dynamic
+# !
+# peer-group SP2
+# !
+#
+- name: Deletes all BGP neighbors
+ dellemc.enterprise_sonic.sonic_bgp_neighbors:
+ config:
+ state: deleted
+
+#
+# After state:
+# -------------
+#router bgp 11 vrf VrfCheck2
+# network import-check
+# timers 60 180
+#!
+#router bgp 51 vrf VrfReg1
+# network import-check
+# timers 60 180
+#!
+#router bgp 11
+# network import-check
+# timers 60 180
+# !
+#
+# Using merged
+#
+# Before state:
+# ------------
+#router bgp 11 vrf VrfCheck2
+# network import-check
+# timers 60 180
+#!
+#router bgp 51 vrf VrfReg1
+# network import-check
+# timers 60 180
+#!
+#router bgp 11
+# network import-check
+# timers 60 180
+# !
+
+- name: "Adds sonic_bgp_neighbors"
+ dellemc.enterprise_sonic.sonic_bgp_neighbors:
+ config:
+ - bgp_as: 51
+ neighbors:
+ - neighbor: Eth1/2
+ auth_pwd:
+ pwd: 'pw123'
+ encrypted: false
+ dont_negotiate_capability: true
+ ebgp_multihop:
+ enabled: true
+ multihop_ttl: 1
+ enforce_first_as: true
+ enforce_multihop: true
+ local_address: 'Ethernet4'
+ local_as:
+ as: 2
+ no_prepend: true
+ replace_as: true
+ nbr_description: "description 1"
+ override_capability: true
+ passive: true
+ port: 3
+ shutdown_msg: 'msg1'
+ solo: true
+ - neighbor: 1.1.1.1
+ disable_connected_check: true
+ ttl_security: 5
+ - bgp_as: 51
+ vrf_name: VrfReg1
+ peer_group:
+ - name: SPINE
+ bfd:
+ check_failure: true
+ enabled: true
+ profile: 'profile 1'
+ capability:
+ dynamic: true
+ extended_nexthop: true
+ auth_pwd:
+ pwd: 'U2FsdGVkX1/4sRsZ624wbAJfDmagPLq2LsGDOcW/47M='
+ encrypted: true
+ dont_negotiate_capability: true
+ ebgp_multihop:
+ enabled: true
+ multihop_ttl: 1
+ enforce_first_as: true
+ enforce_multihop: true
+ local_address: 'Ethernet4'
+ local_as:
+ as: 2
+ no_prepend: true
+ replace_as: true
+ pg_description: 'description 1'
+ override_capability: true
+ passive: true
+ solo: true
+ remote_as:
+ peer_as: 4
+ - name: SPINE1
+ disable_connected_check: true
+ shutdown_msg: "msg1"
+ strict_capability_match: true
+ timers:
+ keepalive: 30
+ holdtime: 15
+ connect_retry: 25
+ ttl_security: 5
+ address_family:
+ afis:
+ - afi: ipv4
+ safi: unicast
+ activate: true
+ allowas_in:
+ origin: true
+ - afi: ipv6
+ safi: unicast
+ activate: true
+ allowas_in:
+ value: 5
+ neighbors:
+ - neighbor: Eth1/3
+ remote_as:
+ peer_as: 10
+ peer_group: SPINE
+ advertisement_interval: 15
+ timers:
+ keepalive: 30
+ holdtime: 15
+ connect_retry: 25
+ bfd:
+ check_failure: true
+ enabled: true
+ profile: 'profile 1'
+ capability:
+ dynamic: true
+ extended_nexthop: true
+ auth_pwd:
+ pwd: 'U2FsdGVkX199MZ7YOPkOR9O6wEZmtGSgiDfnlcN9hBg='
+ encrypted: true
+ nbr_description: 'description 2'
+ strict_capability_match: true
+ v6only: true
+ - neighbor: 192.168.1.4
+ state: merged
+#
+# After state:
+# ------------
+#!
+#router bgp 11 vrf VrfCheck2
+# network import-check
+# timers 60 180
+#!
+#router bgp 51 vrf VrfReg1
+# network import-check
+# timers 60 180
+# !
+# peer-group SPINE1
+# timers 15 30
+# timers connect 25
+# shutdown message msg1
+# disable-connected-check
+# strict-capability-match
+# ttl-security hops 5
+# !
+# peer-group SPINE
+# description "description 1"
+# ebgp-multihop 1
+# remote-as 4
+# bfd check-control-plane-failure profile "profile 1"
+# update-source interface Ethernet4
+# capability dynamic
+# capability extended-nexthop
+# dont-capability-negotiate
+# enforce-first-as
+# enforce-multihop
+# local-as 2 no-prepend replace-as
+# override-capability
+# passive
+# password U2FsdGVkX1/4sRsZ624wbAJfDmagPLq2LsGDOcW/47M= encrypted
+# solo
+# address-family ipv4 unicast
+# activate
+# allowas-in origin
+# send-community both
+# !
+# address-family ipv6 unicast
+# activate
+# allowas-in 5
+# send-community both
+# !
+# neighbor interface Eth1/3
+# description "description 2"
+# peer-group SPINE
+# remote-as 10
+# timers 15 30
+# timers connect 25
+# bfd check-control-plane-failure profile "profile 1"
+# advertisement-interval 15
+# capability extended-nexthop
+# capability dynamic
+# v6only
+# password U2FsdGVkX199MZ7YOPkOR9O6wEZmtGSgiDfnlcN9hBg= encrypted
+# strict-capability-match
+# !
+# neighbor 192.168.1.4
+#!
+# router bgp 51
+# timers 60 180
+# neighbor interface Eth1/2
+# description "description 1"
+# shutdown message msg1
+# ebgp-multihop 1
+# remote-as external
+# update-source interface Ethernet4
+# dont-capability-negotiate
+# enforce-first-as
+# enforce-multihop
+# local-as 2 no-prepend replace-as
+# override-capability
+# passive
+# password U2FsdGVkX1+bxMf9TKOhaXRNNaHmywiEVDF2lJ2c000= encrypted
+# port 3
+# solo
+# neighbor 1.1.1.1
+# disable-connected-check
+# ttl-security hops 5
+#router bgp 11
+# network import-check
+# timers 60 180
+#
+# Using deleted
+#
+# Before state:
+# ------------
+#!
+#router bgp 11 vrf VrfCheck2
+# network import-check
+# timers 60 180
+#!
+#router bgp 51 vrf VrfReg1
+# network import-check
+# timers 60 180
+# !
+# peer-group SPINE
+# bfd
+# remote-as 4
+# !
+# neighbor interface Eth1/3
+# peer-group SPINE
+# remote-as 10
+# timers 15 30
+# advertisement-interval 15
+# bfd
+# capability extended-nexthop
+# capability dynamic
+# !
+# neighbor 192.168.1.4
+#!
+#router bgp 11
+# network import-check
+# timers 60 18
+# !
+# peer-group SP
+# !
+# neighbor interface Eth1/3
+#
+- name: "Deletes sonic_bgp_neighbors and peer-groups specific to vrfname"
+ dellemc.enterprise_sonic.sonic_bgp_neighbors:
+ config:
+ - bgp_as: 51
+ vrf_name: VrfReg1
+ state: deleted
+
+# After state:
+# ------------
+#!
+#router bgp 11 vrf VrfCheck2
+# network import-check
+# timers 60 180
+#!
+#router bgp 51 vrf VrfReg1
+# network import-check
+# timers 60 180
+# !
+#router bgp 11
+# network import-check
+# timers 60 18
+# !
+# peer-group SP
+# !
+# neighbor interface Eth1/3
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#router bgp 51 vrf VrfReg1
+# network import-check
+# timers 60 180
+# !
+# peer-group SPINE
+# bfd
+# remote-as 4
+# !
+# neighbor interface Eth1/3
+# peer-group SPINE
+# remote-as 10
+# timers 15 30
+# advertisement-interval 15
+# bfd
+# capability extended-nexthop
+# capability dynamic
+# !
+# neighbor 192.168.1.4
+# !
+
+- name: "Deletes specific sonic_bgp_neighbors"
+ dellemc.enterprise_sonic.sonic_bgp_neighbors:
+ config:
+ - bgp_as: 51
+ neighbors:
+ - neighbor: Eth1/2
+ auth_pwd:
+ pwd: 'pw123'
+ encrypted: false
+ dont_negotiate_capability: true
+ ebgp_multihop:
+ enabled: true
+ multihop_ttl: 1
+ enforce_first_as: true
+ enforce_multihop: true
+ local_address: 'Ethernet4'
+ local_as:
+ as: 2
+ no_prepend: true
+ replace_as: true
+ nbr_description: 'description 1'
+ override_capability: true
+ passive: true
+ port: 3
+ shutdown_msg: 'msg1'
+ solo: true
+ - neighbor: 1.1.1.1
+ disable_connected_check: true
+ ttl_security: 5
+ - bgp_as: 51
+ vrf_name: VrfReg1
+ peer_group:
+ - name: SPINE
+ bfd:
+ check_failure: true
+ enabled: true
+ profile: 'profile 1'
+ capability:
+ dynamic: true
+ extended_nexthop: true
+ auth_pwd:
+ pwd: 'U2FsdGVkX1/4sRsZ624wbAJfDmagPLq2LsGDOcW/47M='
+ encrypted: true
+ dont_negotiate_capability: true
+ ebgp_multihop:
+ enabled: true
+ multihop_ttl: 1
+ enforce_first_as: true
+ enforce_multihop: true
+ local_address: 'Ethernet4'
+ local_as:
+ as: 2
+ no_prepend: true
+ replace_as: true
+ pg_description: 'description 1'
+ override_capability: true
+ passive: true
+ solo: true
+ remote_as:
+ peer_as: 4
+ - name: SPINE1
+ disable_connected_check: true
+ shutdown_msg: "msg1"
+ strict_capability_match: true
+ timers:
+ keepalive: 30
+ holdtime: 15
+ connect_retry: 25
+ ttl_security: 5
+ neighbors:
+ - neighbor: Eth1/3
+ remote_as:
+ peer_as: 10
+ peer_group: SPINE
+ advertisement_interval: 15
+ timers:
+ keepalive: 30
+ holdtime: 15
+ connect_retry: 25
+ bfd:
+ check_failure: true
+ enabled: true
+ profile: 'profile 1'
+ capability:
+ dynamic: true
+ extended_nexthop: true
+ auth_pwd:
+ pwd: 'U2FsdGVkX199MZ7YOPkOR9O6wEZmtGSgiDfnlcN9hBg='
+ encrypted: true
+ nbr_description: 'description 2'
+ strict_capability_match: true
+ v6only: true
+ - neighbor: 192.168.1.4
+ state: deleted
+#
+# After state:
+# -------------
+#
+#router bgp 51 vrf VrfReg1
+# network import-check
+# timers 60 180
+# !
+# peer-group SPINE1
+# !
+# peer-group SPINE
+# !
+# neighbor interface Eth1/3
+# !
+# neighbor interface Eth1/2
+# neighbor 1.1.1.1
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+# sonic# show running-configuration bgp peer-group vrf default
+# (No bgp peer-group configuration present)
+
+- name: "Configure BGP peer-group prefix-list attributes"
+ dellemc.enterprise_sonic.sonic_bgp_neighbors:
+ config:
+ - bgp_as: 51
+ peer_group:
+ - name: SPINE
+ address_family:
+ afis:
+ - afi: ipv4
+ safi: unicast
+ ip_afi:
+ default_policy_name: rmap_reg1
+ send_default_route: true
+ prefix_limit:
+ max_prefixes: 1
+ prevent_teardown: true
+ warning_threshold: 80
+ prefix_list_in: p1
+ prefix_list_out: p2
+ state: merged
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration bgp peer-group vrf default
+# !
+# peer-group SPINE
+# !
+# address-family ipv4 unicast
+# default-originate route-map rmap_reg1
+# prefix-list p1 in
+# prefix-list p2 out
+# send-community both
+# maximum-prefix 1 80 warning-only
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# sonic# show running-configuration bgp peer-group vrf default
+# !
+# peer-group SPINE
+# !
+# address-family ipv6 unicast
+# default-originate route-map rmap_reg2
+# prefix-list p1 in
+# prefix-list p2 out
+# send-community both
+# maximum-prefix 5 90 restart 2
+
+- name: "Delete BGP peer-group prefix-list attributes"
+ dellemc.enterprise_sonic.sonic_bgp_neighbors:
+ config:
+ - bgp_as: 51
+ peer_group:
+ - name: SPINE
+ address_family:
+ afis:
+ - afi: ipv6
+ safi: unicast
+ ip_afi:
+ default_policy_name: rmap_reg2
+ send_default_route: true
+ prefix_limit:
+ max_prefixes: 5
+ warning_threshold: 90
+ restart-timer: 2
+ prefix_list_in: p1
+ prefix_list_out: p2
+ state: deleted
+
+# sonic# show running-configuration bgp peer-group vrf default
+# (No bgp peer-group configuration present)
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned is always 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
+ 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.bgp_neighbors.bgp_neighbors import Bgp_neighborsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.bgp_neighbors.bgp_neighbors import Bgp_neighbors
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Bgp_neighborsArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Bgp_neighbors(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
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
new file mode 100644
index 000000000..10400cfe2
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_bgp_neighbors_af.py
@@ -0,0 +1,451 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# 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_bgp_neighbors_af
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: sonic_bgp_neighbors_af
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Niraimadaiselvam M (@niraimadaiselvamm)
+short_description: Manage the BGP neighbor address-family and its parameters
+description:
+ - This module provides configuration management of BGP neighbors address-family parameters on devices running Enterprise SONiC.
+ - bgp_as, vrf_name and neighbors need be created in advance on the device.
+options:
+ config:
+ description:
+ - Specifies the BGP neighbors address-family related configuration.
+ type: list
+ elements: dict
+ suboptions:
+ bgp_as:
+ description:
+ - Specifies the BGP autonomous system (AS) number which is already configured on the device.
+ type: str
+ required: true
+ vrf_name:
+ description:
+ - Specifies the VRF name which is already configured on the device.
+ type: str
+ default: 'default'
+ neighbors:
+ description:
+ - Specifies BGP neighbor related configurations in address-family configuration mode.
+ type: list
+ elements: dict
+ suboptions:
+ neighbor:
+ description:
+ - Neighbor router address which is already configured on the device.
+ type: str
+ required: True
+ address_family:
+ description:
+ - Specifies BGP address-family related configurations.
+ - afi and safi are required together.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - Type of address-family to configure.
+ type: str
+ choices:
+ - ipv4
+ - ipv6
+ - l2vpn
+ required: True
+ safi:
+ description:
+ - Specifies the type of cast for the address-family.
+ type: str
+ choices:
+ - unicast
+ - evpn
+ default: unicast
+ activate:
+ description:
+ - Enables the address-family for this neighbor.
+ type: bool
+ allowas_in:
+ description:
+ - Specifies the allowas in values.
+ type: dict
+ suboptions:
+ value:
+ description:
+ - Specifies the value of the allowas in.
+ type: int
+ origin:
+ description:
+ - Specifies the origin value.
+ type: bool
+ ip_afi:
+ description:
+ - Common configuration attributes for IPv4 and IPv6 unicast address families.
+ type: dict
+ suboptions:
+ default_policy_name:
+ description:
+ - Specifies routing policy definition.
+ type: str
+ send_default_route:
+ description:
+ - Enable or disable sending of default-route to the neighbor.
+ type: bool
+ default: False
+ prefix_limit:
+ description:
+ - Specifies prefix limit attributes.
+ type: dict
+ suboptions:
+ max_prefixes:
+ description:
+ - Maximum number of prefixes that will be accepted from the neighbor.
+ type: int
+ prevent_teardown:
+ description:
+ - Enable or disable teardown of BGP session when maximum prefix limit is exceeded.
+ type: bool
+ default: False
+ warning_threshold:
+ description:
+ - Threshold on number of prefixes that can be received from a neighbor before generation of warning messages.
+ - Expressed as a percentage of max-prefixes.
+ type: int
+ restart_timer:
+ description:
+ - Time interval in seconds after which the BGP session is re-established after being torn down.
+ type: int
+ prefix_list_in:
+ description:
+ - Inbound route filtering policy for a neighbor.
+ type: str
+ prefix_list_out:
+ description:
+ - Outbound route filtering policy for a neighbor.
+ type: str
+ route_map:
+ description:
+ - Specifies the route-map.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Specifies the name of the route-map.
+ type: str
+ direction:
+ description:
+ - Specifies the direction of the route-map.
+ type: str
+ route_reflector_client:
+ description:
+ - Specifies a neighbor as a route-reflector client.
+ type: bool
+ route_server_client:
+ description:
+ - Specifies a neighbor as a route-server client.
+ type: bool
+ 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.
+ default: merged
+ type: str
+ choices: ['merged', 'deleted']
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#!
+#router bgp 4
+# !
+# neighbor interface Eth1/3
+# !
+# address-family ipv4 unicast
+# activate
+# allowas-in 4
+# route-map aa in
+# route-map aa out
+# route-reflector-client
+# route-server-client
+# send-community both
+#!
+#
+- name: Deletes neighbors address-family with specific values
+ dellemc.enterprise_sonic.sonic_bgp_neighbors_af:
+ config:
+ - bgp_as: 4
+ neighbors:
+ - neighbor: Eth1/3
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ allowas_in:
+ value: 4
+ route_map:
+ - name: aa
+ direction: in
+ - name: aa
+ direction: out
+ route_reflector_client: true
+ route_server_client: true
+ state: deleted
+
+# After state:
+# ------------
+#!
+#router bgp 4
+# !
+# neighbor interface Eth1/3
+# !
+# address-family ipv4 unicast
+# send-community both
+#!
+
+
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#!
+#router bgp 4
+# !
+# neighbor interface Eth1/3
+# !
+# address-family ipv4 unicast
+# activate
+# allowas-in 4
+# route-map aa in
+# route-map aa out
+# route-reflector-client
+# route-server-client
+# send-community both
+#!
+# neighbor interface Eth1/5
+# !
+# address-family ipv4 unicast
+# activate
+# allowas-in origin
+# send-community both
+#!
+#
+- name: Deletes neighbors address-family with specific values
+ dellemc.enterprise_sonic.sonic_bgp_neighbors_af:
+ config:
+ state: deleted
+
+# After state:
+# ------------
+#!
+#router bgp 4
+#!
+
+
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#!
+#router bgp 4
+# !
+# neighbor interface Eth1/3
+#!
+#
+- name: Merges neighbors address-family with specific values
+ dellemc.enterprise_sonic.sonic_bgp_neighbors_af:
+ config:
+ - bgp_as: 4
+ neighbors:
+ - neighbor: Eth1/3
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ allowas_in:
+ value: 4
+ route_map:
+ - name: aa
+ direction: in
+ - name: aa
+ direction: out
+ route_reflector_client: true
+ route_server_client: true
+ state: merged
+
+# After state:
+# ------------
+#!
+#router bgp 4
+# !
+# neighbor interface Eth1/3
+# !
+# address-family ipv4 unicast
+# activate
+# allowas-in 4
+# route-map aa in
+# route-map aa out
+# route-reflector-client
+# route-server-client
+# send-community both
+#!
+
+
+# Using merged
+#
+# Before state:
+# -------------
+#
+# sonic# show running-configuration bgp neighbor vrf default 1.1.1.1
+# (No bgp neighbor configuration present)
+- name: "Configure BGP neighbor prefix-list attributes"
+ dellemc.enterprise_sonic.sonic_bgp_neighbors_af:
+ config:
+ - bgp_as: 51
+ neighbors:
+ - neighbor: 1.1.1.1
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ ip_afi:
+ default_policy_name: rmap_reg1
+ send_default_route: true
+ prefix_limit:
+ max_prefixes: 1
+ prevent_teardown: true
+ warning_threshold: 80
+ prefix_list_in: p1
+ prefix_list_out: p2
+ state: merged
+# After state:
+# ------------
+#
+# sonic# show running-configuration bgp neighbor vrf default 1.1.1.1
+# !
+# neighbor 1.1.1.1
+# !
+# address-family ipv4 unicast
+# default-originate route-map rmap_reg1
+# prefix-list p1 in
+# prefix-list p2 out
+# send-community both
+# maximum-prefix 1 80 warning-only
+
+
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# sonic# show running-configuration bgp neighbor vrf default 1.1.1.1
+# !
+# neighbor 1.1.1.1
+# !
+# address-family ipv6 unicast
+# default-originate route-map rmap_reg2
+# prefix-list p1 in
+# prefix-list p2 out
+# send-community both
+# maximum-prefix 5 90 restart 2
+- name: "Delete BGP neighbor prefix-list attributes"
+ dellemc.enterprise_sonic.sonic_bgp_neighbors_af:
+ config:
+ - bgp_as: 51
+ neighbors:
+ - neighbor: 1.1.1.1
+ address_family:
+ - afi: ipv6
+ safi: unicast
+ ip_afi:
+ default_policy_name: rmap_reg2
+ send_default_route: true
+ prefix_limit:
+ max_prefixes: 5
+ warning_threshold: 90
+ restart-timer: 2
+ prefix_list_in: p1
+ prefix_list_out: p2
+ state: deleted
+# sonic# show running-configuration bgp neighbor vrf default 1.1.1.1
+# (No bgp neighbor configuration present)
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned is always 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
+ 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.bgp_neighbors_af.bgp_neighbors_af import Bgp_neighbors_afArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.bgp_neighbors_af.bgp_neighbors_af import Bgp_neighbors_af
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Bgp_neighbors_afArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Bgp_neighbors_af(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_command.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_command.py
new file mode 100644
index 000000000..b0ce87811
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_command.py
@@ -0,0 +1,235 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Peter Sprygada <psprygada@ansible.com>
+# Copyright: (c) 2020, Dell Inc.
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_command
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Dhivya P (@dhivayp)
+short_description: Runs commands on devices running Enterprise SONiC
+description:
+ - Runs commands on remote devices running Enterprise SONiC Distribution
+ by Dell Technologies. Sends arbitrary commands to an Enterprise SONiC node and
+ returns the results that are read from the device. This module includes an
+ argument that causes the module to wait for a specific condition
+ before returning or time out if the condition is not met.
+ - This module does not support running commands in configuration mode.
+ To configure SONiC devices, use M(dellemc.enterprise_sonic.sonic_config).
+options:
+ commands:
+ description:
+ - List of commands to send to the remote Enterprise SONiC devices over the
+ configured provider. The resulting output from the command
+ is returned. If the I(wait_for) argument is provided, the
+ module is not returned until the condition is satisfied or
+ the number of retries has expired. If a command sent to the
+ device requires answering a prompt, it is possible to pass
+ a dict containing I(command), I(answer) and I(prompt).
+ Common answers are 'yes' or "\\r" (carriage return, must be
+ double quotes). See examples.
+ type: list
+ elements: str
+ required: true
+ wait_for:
+ description:
+ - List of conditions to evaluate against the output of the
+ command. The task waits for each condition to be true
+ before moving forward. If the conditional is not true
+ within the configured number of retries, the task fails.
+ See examples.
+ type: list
+ elements: str
+ match:
+ description:
+ - The I(match) argument is used in conjunction with the
+ I(wait_for) argument to specify the match policy. Valid
+ values are C(all) or C(any). If the value is set to C(all)
+ then all conditionals in the wait_for must be satisfied. If
+ the value is set to C(any) then only one of the values must be
+ satisfied.
+ type: str
+ default: all
+ choices: [ 'all', 'any' ]
+ retries:
+ description:
+ - Specifies the number of retries a command should be run
+ before it is considered failed. The command is run on the
+ target device every retry and evaluated against the
+ I(wait_for) conditions.
+ type: int
+ default: 10
+ interval:
+ description:
+ - Configures the interval in seconds to wait between retries
+ of the command. If the command does not pass the specified
+ conditions, the interval indicates how long to wait before
+ trying the command again.
+ type: int
+ default: 1
+"""
+
+EXAMPLES = """
+ - name: Runs show version on remote devices
+ dellemc.enterprise_sonic.sonic_command:
+ commands: show version
+
+ - name: Runs show version and checks to see if output contains 'Dell'
+ dellemc.enterprise_sonic.sonic_command:
+ commands: show version
+ wait_for: result[0] contains Dell
+
+ - name: Runs multiple commands on remote nodes
+ dellemc.enterprise_sonic.sonic_command:
+ commands:
+ - show version
+ - show interface
+
+ - name: Runs multiple commands and evaluate the output
+ dellemc.enterprise_sonic.sonic_command:
+ commands:
+ - 'show version'
+ - 'show system'
+ wait_for:
+ - result[0] contains Dell
+ - result[1] contains Hostname
+
+ - name: Runs commands that require answering a prompt
+ dellemc.enterprise_sonic.sonic_command:
+ commands:
+ - command: 'reload'
+ prompt: '[confirm yes/no]: ?$'
+ answer: 'no'
+"""
+
+RETURN = """
+stdout:
+ description: The set of responses from the commands.
+ returned: always apart from low level errors (such as action plugin)
+ type: list
+ sample: ['...', '...']
+stdout_lines:
+ description: The value of stdout split into a list.
+ returned: always apart from low level errors (such as action plugin)
+ type: list
+ sample: [['...', '...'], ['...'], ['...']]
+failed_conditions:
+ description: The list of conditionals that have failed.
+ returned: failed
+ type: list
+ sample: ['...', '...']
+warnings:
+ description: The list of warnings (if any) generated by module based on arguments.
+ returned: always
+ type: list
+ sample: ['...', '...']
+"""
+import time
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import (
+ Conditional,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ EntityCollection,
+ to_lines,
+)
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import run_commands
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import command_list_str_to_dict
+
+
+def transform_commands_dict(module, commands_dict):
+ transform = EntityCollection(
+ module,
+ dict(
+ command=dict(key=True),
+ output=dict(),
+ prompt=dict(type="list"),
+ answer=dict(type="list"),
+ newline=dict(type="bool", default=True),
+ sendonly=dict(type="bool", default=False),
+ check_all=dict(type="bool", default=False),
+ ),
+ )
+
+ return transform(commands_dict)
+
+
+def parse_commands(module, warnings):
+ commands_dict = command_list_str_to_dict(module, warnings, module.params["commands"])
+ commands = transform_commands_dict(module, commands_dict)
+ return commands
+
+
+def main():
+ """main entry point for module execution
+ """
+ argument_spec = dict(
+ # { command: <str>, prompt: <str>, response: <str> }
+ commands=dict(type='list', required=True, elements="str"),
+
+ wait_for=dict(type='list', elements="str"),
+ match=dict(default='all', choices=['all', 'any']),
+
+ retries=dict(default=10, type='int'),
+ interval=dict(default=1, type='int')
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec,
+ supports_check_mode=True)
+ result = {'changed': False}
+
+ warnings = list()
+# check_args(module, warnings)
+ commands = parse_commands(module, warnings)
+ result['warnings'] = warnings
+
+ wait_for = module.params['wait_for'] or list()
+ try:
+ conditionals = [Conditional(c) for c in wait_for]
+ except AttributeError as exc:
+ module.fail_json(msg=to_text(exc))
+ retries = module.params['retries']
+ interval = module.params['interval']
+ match = module.params['match']
+
+ while retries > 0:
+ responses = run_commands(module, commands)
+ for item in list(conditionals):
+ if item(responses):
+ if match == 'any':
+ conditionals = list()
+ break
+ conditionals.remove(item)
+
+ if not conditionals:
+ break
+
+ time.sleep(interval)
+ retries -= 1
+
+ if conditionals:
+ failed_conditions = [item.raw for item in conditionals]
+ msg = 'One or more conditional statements have not been satisfied.'
+ module.fail_json(msg=msg, failed_conditions=failed_conditions)
+
+ result.update({
+ 'stdout': responses,
+ 'stdout_lines': list(to_lines(responses))
+ })
+
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_config.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_config.py
new file mode 100644
index 000000000..dd054419f
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_config.py
@@ -0,0 +1,329 @@
+#!/usr/bin/python
+#
+# (c) 2015 Peter Sprygada, <psprygada@ansible.com>
+# Copyright (c) 2020 Dell Inc.
+#
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_config
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Abirami N (@abirami-n)
+short_description: Manages configuration sections on devices running Enterprise SONiC
+description:
+ - Manages configuration sections of Enterprise SONiC Distribution
+ by Dell Technologies. SONiC configurations use a simple block indent
+ file syntax for segmenting configuration into sections. This module
+ provides an implementation for working with SONiC configuration
+ sections in a deterministic way.
+options:
+ lines:
+ description:
+ - The ordered set of commands that should be configured in the
+ section. The commands must be the exact same commands as found
+ in the device running-configuration. Be sure to note the configuration
+ command syntax as some commands are automatically modified by the
+ device configuration parser. This argument is mutually exclusive
+ with I(src).
+ type: list
+ elements: str
+ aliases: ['commands']
+ parents:
+ description:
+ - The ordered set of parents that uniquely identify the section or hierarchy
+ the commands should be checked against. If the parents argument
+ is omitted, the commands are checked against the set of top
+ level or global commands.
+ type: list
+ elements: str
+ src:
+ description:
+ - Specifies the source path to the file that contains the configuration
+ or configuration template to load. The path to the source file can
+ either be the full path on the Ansible control host, or a relative
+ path from the playbook or role root directory. This argument is
+ mutually exclusive with I(lines).
+ type: path
+ before:
+ description:
+ - The ordered set of commands to push on to the command stack if
+ a change needs to be made. This allows the playbook designer
+ the opportunity to perform configuration commands prior to pushing
+ any changes without affecting how the set of commands are matched
+ against the system.
+ type: list
+ elements: str
+ after:
+ description:
+ - The ordered set of commands to append to the end of the command
+ stack if a change needs to be made. Just like with I(before), this
+ allows the playbook designer to append a set of commands to be
+ executed after the command set.
+ type: list
+ elements: str
+ save:
+ description:
+ - The C(save) argument instructs the module to save the running-
+ configuration to the startup-configuration at the conclusion of
+ the module running. If check mode is specified, this argument
+ is ignored.
+ type: bool
+ default: 'no'
+ match:
+ description:
+ - Instructs the module on the way to perform the matching of
+ the set of commands against the current device configuration.
+ If match is set to I(line), commands are matched line by line.
+ If match is set to I(strict), command lines are matched with respect
+ to position. If match is set to I(exact), command lines
+ must be an equal match. If match is set to I(none), the
+ module does not attempt to compare the source configuration with
+ the running-configuration on the remote device.
+ type: str
+ default: line
+ choices: ['line', 'strict', 'exact', 'none']
+ replace:
+ description:
+ - Instructs the module how to perform a configuration
+ on the device. If the replace argument is set to I(line), then
+ the modified lines are pushed to the device in configuration
+ mode. If the replace argument is set to I(block), then the entire
+ command block is pushed to the device in configuration mode if any
+ line is not correct.
+ type: str
+ default: line
+ choices: ['line', 'block']
+ update:
+ description:
+ - The I(update) argument controls how the configuration statements
+ are processed on the remote device. Valid choices for the I(update)
+ argument are I(merge) and I(check). When you set this argument to
+ I(merge), the configuration changes merge with the current
+ device running-configuration. When you set this argument to I(check),
+ the configuration updates are determined but not configured
+ on the remote device.
+ type: str
+ default: merge
+ choices: ['merge', 'check']
+ config:
+ description:
+ - The module, by default, connects to the remote device and
+ retrieves the current running-configuration to use as a base for
+ comparing against the contents of source. There are times when
+ it is not desirable to have the task get the current
+ running-configuration for every task in a playbook. The I(config)
+ argument allows the implementer to pass in the configuration to
+ use as the base configuration for comparison.
+ type: str
+ backup:
+ description:
+ - This argument causes the module to create a full backup of
+ the current C(running-configuration) from the remote device before any
+ changes are made. If the C(backup_options) value is not given,
+ the backup file is written to the C(backup) folder in the playbook
+ root directory. If the directory does not exist, it is created.
+ type: bool
+ default: 'no'
+ backup_options:
+ description:
+ - This is a dictionary object containing configurable options related to backup file path.
+ The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set
+ to I(no) this option is ignored.
+ suboptions:
+ filename:
+ description:
+ - The filename to be used to store the backup configuration. If the filename
+ is not given, it is generated based on the hostname, current time, and date
+ in the format defined by <hostname>_config.<current-date>@<current-time>.
+ type: str
+ dir_path:
+ description:
+ - This option provides the path ending with directory name in which the backup
+ configuration file is stored. If the directory does not exist it is first
+ created, and the filename is either the value of C(filename) or default filename
+ as described in C(filename) options description. If the path value is not given,
+ an I(backup) directory is created in the current working directory
+ and backup configuration is copied in C(filename) within the I(backup) directory.
+ type: path
+ type: dict
+"""
+
+EXAMPLES = """
+- dellemc.enterprise_sonic.sonic_config:
+ lines: ['username {{ user_name }} password {{ user_password }} role {{ user_role }}']
+
+- dellemc.enterprise_sonic.sonic_config:
+ lines:
+ - description 'SONiC'
+ parents: ['interface Eth1/10']
+
+- dellemc.enterprise_sonic.sonic_config:
+ lines:
+ - seq 2 permit udp any any
+ - seq 3 deny icmp any any
+ parents: ['ip access-list test']
+ before: ['no ip access-list test']
+
+"""
+
+RETURN = """
+updates:
+ description: The set of commands that is pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['username foo password foo role admin', 'router bgp 1', 'router-id 1.1.1.1']
+commands:
+ description: The set of commands that is pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['username foo password foo role admin', 'router bgp 1', 'router-id 1.1.1.1']
+saved:
+ description: Returns whether the configuration is saved to the startup
+ configuration or not.
+ returned: When not check_mode.
+ type: bool
+ sample: True
+"""
+
+from ansible.module_utils.connection import ConnectionError
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import get_config, get_sublevel_config
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.sonic import edit_config, run_commands
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.utils.utils import command_list_str_to_dict
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import NetworkConfig, dumps
+
+
+def get_candidate(module):
+ candidate = NetworkConfig(indent=1)
+ if module.params['src']:
+ candidate.load(module.params['src'])
+ elif module.params['lines']:
+ parents = module.params['parents'] or list()
+ commands = module.params['lines'][0]
+ if (isinstance(commands, dict)) and (isinstance((commands['command']), list)):
+ candidate.add(commands['command'], parents=parents)
+ elif (isinstance(commands, dict)) and (isinstance((commands['command']), str)):
+ candidate.add([commands['command']], parents=parents)
+ else:
+ candidate.add(module.params['lines'], parents=parents)
+ return candidate
+
+
+def get_running_config(module):
+ contents = module.params['config']
+ if not contents:
+ contents = get_config(module)
+ return contents
+
+
+def main():
+
+ backup_spec = dict(
+ filename=dict(),
+ dir_path=dict(type='path')
+ )
+
+ argument_spec = dict(
+ lines=dict(aliases=['commands'], type='list', elements="str"),
+ parents=dict(type='list', elements="str"),
+
+ src=dict(type='path'),
+
+ before=dict(type='list', elements="str"),
+ after=dict(type='list', elements="str"),
+ save=dict(type='bool', default=False),
+ match=dict(default='line',
+ choices=['line', 'strict', 'exact', 'none']),
+ replace=dict(default='line', choices=['line', 'block']),
+
+ update=dict(choices=['merge', 'check'], default='merge'),
+ config=dict(),
+ backup=dict(type='bool', default=False),
+ backup_options=dict(type='dict', options=backup_spec)
+
+ )
+
+ mutually_exclusive = [('lines', 'src')]
+
+ module = AnsibleModule(argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True)
+ parents = module.params['parents'] or list()
+ match = module.params['match']
+ replace = module.params['replace']
+
+ warnings = list()
+# check_args(module, warnings)
+
+ result = dict(changed=False, saved=False, warnings=warnings)
+ if module.params['backup']:
+ if not module.check_mode:
+ result['__backup__'] = get_config(module)
+
+ commands = list()
+ candidate = get_candidate(module)
+ if any((module.params['lines'], module.params['src'])):
+ if match != 'none':
+ config = get_running_config(module)
+ if parents:
+ contents = get_sublevel_config(config, module)
+ config = NetworkConfig(contents=contents, indent=1)
+ else:
+ config = NetworkConfig(contents=config, indent=1)
+ configobjs = candidate.difference(config, match=match, replace=replace)
+ else:
+
+ configobjs = candidate.items
+ if configobjs:
+ commands = dumps(configobjs, 'commands')
+ if ((isinstance((module.params['lines']), list)) and
+ (isinstance((module.params['lines'][0]), dict)) and
+ (set(['prompt', 'answer']).issubset(module.params['lines'][0]))):
+
+ cmd = {'command': commands,
+ 'prompt': module.params['lines'][0]['prompt'],
+ 'answer': module.params['lines'][0]['answer']}
+ commands = [cmd]
+ else:
+ commands = commands.split('\n')
+ cmd_list_out = command_list_str_to_dict(module, warnings, commands)
+ if cmd_list_out and cmd_list_out != []:
+ commands = cmd_list_out
+
+ if module.params['before']:
+ commands[:0] = module.params['before']
+
+ if module.params['after']:
+ commands.extend(module.params['after'])
+
+ if not module.check_mode and module.params['update'] == 'merge':
+ try:
+ edit_config(module, commands)
+ except ConnectionError as exc:
+ module.fail_json(msg=to_text(exc))
+
+ result['changed'] = True
+ result['commands'] = commands
+ result['updates'] = commands
+
+ if module.params['save']:
+ result['changed'] = True
+ if not module.check_mode:
+ cmd = {r'command': ' write memory'}
+ run_commands(module, [cmd])
+ result['saved'] = True
+
+ 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
new file mode 100644
index 000000000..f13e9defd
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_facts.py
@@ -0,0 +1,136 @@
+#!/usr/bin/python
+# -*- 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 module file for sonic_facts
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_facts
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+short_description: Collects facts on devices running Enterprise SONiC
+description:
+ - Collects facts from devices running Enterprise SONiC Distribution by
+ Dell Technologies. This module places the facts gathered in the fact tree
+ keyed by the respective resource name. The facts module always collects
+ a base set of facts from the device and can enable or disable collection
+ of additional facts.
+author:
+- Mohamed Javeed (@javeedf)
+- Abirami N (@abirami-n)
+options:
+ gather_subset:
+ description:
+ - When supplied, this argument restricts the facts collected
+ to a given subset. Possible values for this argument include
+ all, min, hardware, config, legacy, and interfaces. Can specify a
+ list of values to include a larger subset. Values can also be used
+ with an initial '!' to specify that a specific subset should
+ not be collected.
+ required: false
+ type: list
+ elements: str
+ default: '!config'
+ gather_network_resources:
+ description:
+ - When supplied, this argument restricts the facts collected
+ to a given subset. Possible values for this argument include
+ all and the resources like 'all', 'interfaces', 'vlans', 'lag_interfaces', 'l2_interfaces', 'l3_interfaces'.
+ Can specify a list of values to include a larger subset. Values
+ can also be used with an initial '!' to specify that a
+ specific subset should not be collected.
+ required: false
+ type: list
+ elements: str
+ choices:
+ - all
+ - vlans
+ - interfaces
+ - l2_interfaces
+ - l3_interfaces
+ - lag_interfaces
+ - bgp
+ - bgp_af
+ - bgp_neighbors
+ - bgp_neighbors_af
+ - bgp_as_paths
+ - bgp_communities
+ - bgp_ext_communities
+ - mclag
+ - prefix_lists
+ - vrfs
+ - vxlans
+ - users
+ - system
+ - port_breakout
+ - aaa
+ - tacacs_server
+ - radius_server
+ - static_routes
+ - ntp
+"""
+
+EXAMPLES = """
+- name: Gather all facts
+ dellemc.enterprise_sonic.sonic_facts:
+ gather_subset: all
+ gather_network_resources: all
+- name: Collects VLAN and interfaces facts
+ dellemc.enterprise_sonic.sonic_facts:
+ gather_subset:
+ - min
+ gather_network_resources:
+ - vlans
+ - interfaces
+- name: Do not collects VLAN and interfaces facts
+ dellemc.enterprise_sonic.sonic_facts:
+ gather_network_resources:
+ - "!vlans"
+ - "!interfaces"
+- name: Collects VLAN and minimal default facts
+ dellemc.enterprise_sonic.sonic_facts:
+ gather_subset: min
+ gather_network_resources: vlans
+- name: Collect lag_interfaces and minimal default facts
+ dellemc.enterprise_sonic.sonic_facts:
+ gather_subset: min
+ gather_network_resources: lag_interfaces
+"""
+
+RETURN = """
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.facts.facts import FactsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.facts.facts import Facts
+
+
+def main():
+ """
+ Main entry point for module execution
+ :returns: ansible_facts
+ """
+ module = AnsibleModule(argument_spec=FactsArgs.argument_spec,
+ supports_check_mode=True)
+ warnings = ['default value for `gather_subset` '
+ 'will be changed to `min` from `!config` v2.11 onwards']
+
+ result = Facts(module).get_facts()
+
+ ansible_facts, additional_warnings = result
+ warnings.extend(additional_warnings)
+
+ module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_interfaces.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_interfaces.py
new file mode 100644
index 000000000..0cd6a1896
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_interfaces.py
@@ -0,0 +1,230 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_interfaces
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+short_description: Configure Interface attributes on interfaces such as, Eth, LAG, VLAN, and loopback.
+ (create a loopback interface if it does not exist.)
+description: Configure Interface attributes such as, MTU, admin statu, and so on, on interfaces
+ such as, Eth, LAG, VLAN, and loopback. (create a loopback interface if it does not exist.)
+author: Niraimadaiselvam M(@niraimadaiselvamm)
+options:
+ config:
+ description: A list of interface configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ type: str
+ description: The name of the interface, for example, 'Eth1/15'.
+ required: true
+ description:
+ type: str
+ description:
+ - Description about the interface.
+ enabled:
+ description:
+ - Administrative state of the interface.
+ type: bool
+ mtu:
+ description:
+ - MTU of the interface.
+ type: int
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# show interface status | no-more
+#------------------------------------------------------------------------------------------
+#Name Description Admin Oper 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
+#
+- name: Configures interfaces
+ dellemc.enterprise_sonic.sonic_interfaces:
+ config:
+ name: Eth1/3
+ state: deleted
+#
+# After state:
+# -------------
+#
+# show interface status | no-more
+#------------------------------------------------------------------------------------------
+#Name Description Admin Oper 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
+#
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# show interface status | no-more
+#------------------------------------------------------------------------------------------
+#Name Description Admin Oper 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
+#
+
+- name: Configures interfaces
+ dellemc.enterprise_sonic.sonic_interfaces:
+ config:
+ state: deleted
+
+#
+# After state:
+# -------------
+#
+# show interface status | no-more
+#------------------------------------------------------------------------------------------
+#Name Description Admin Oper 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
+#
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+# show interface status | no-more
+#------------------------------------------------------------------------------------------
+#Name Description Admin Oper Speed MTU
+#------------------------------------------------------------------------------------------
+#Eth1/1 - up 100000 9100
+#Eth1/2 - up 100000 9100
+#Eth1/3 - down 100000 9100
+#Eth1/3 - down 1000 9100
+#
+- name: Configures interfaces
+ dellemc.enterprise_sonic.sonic_interfaces:
+ config:
+ - name: Eth1/3
+ description: 'Ethernet Twelve'
+ - name: Eth1/5
+ description: 'Ethernet Sixteen'
+ enable: True
+ mtu: 3500
+ state: merged
+#
+#
+# After state:
+# ------------
+#
+# show interface status | no-more
+#------------------------------------------------------------------------------------------
+#Name Description Admin Oper 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
+#
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned is always 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
+ 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.interfaces.interfaces import InterfacesArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.interfaces.interfaces import Interfaces
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=InterfacesArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Interfaces(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
new file mode 100644
index 000000000..34a8ff720
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l2_interfaces.py
@@ -0,0 +1,296 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_l2_interfaces
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+short_description: Configure interface-to-VLAN association that is based on access or trunk mode
+description: Manages Layer 2 interface attributes of Enterprise SONiC Distribution by Dell Technologies.
+author: Niraimadaiselvam M(@niraimadaiselvamm)
+options:
+ config:
+ description: A list of Layer 2 interface configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ type: str
+ description: Full name of the interface, for example, 'Eth1/26'.
+ required: true
+ trunk:
+ type: dict
+ description: Configures trunking parameters on an interface.
+ suboptions:
+ allowed_vlans:
+ description: Specifies list of allowed VLANs of trunk mode on the interface.
+ type: list
+ elements: dict
+ suboptions:
+ vlan:
+ type: int
+ description: Configures the specified VLAN in trunk mode.
+ access:
+ type: dict
+ description: Configures access mode characteristics of the interface.
+ suboptions:
+ vlan:
+ type: int
+ description: Configures the specified VLAN in access mode.
+ state:
+ type: str
+ description: The state that the configuration should be left in.
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive A Eth1/3
+#11 Inactive T Eth1/3
+#12 Inactive A Eth1/4
+#13 Inactive T Eth1/4
+#14 Inactive A Eth1/5
+#15 Inactive T Eth1/5
+#
+- name: Configures switch port of interfaces
+ dellemc.enterprise_sonic.sonic_l2_interfaces:
+ config:
+ - name: Eth1/3
+ - name: Eth1/4
+ state: deleted
+#
+# After state:
+# ------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#11 Inactive
+#12 Inactive
+#13 Inactive
+#14 Inactive A Eth1/5
+#15 Inactive T Eth1/5
+#
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive A Eth1/3
+#11 Inactive T Eth1/3
+#12 Inactive A Eth1/4
+#13 Inactive T Eth1/4
+#14 Inactive A Eth1/5
+#15 Inactive T Eth1/5
+#
+- name: Configures switch port of interfaces
+ dellemc.enterprise_sonic.sonic_l2_interfaces:
+ config:
+ state: deleted
+#
+# After state:
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#11 Inactive
+#12 Inactive
+#13 Inactive
+#14 Inactive
+#15 Inactive
+#
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#11 Inactive T Eth1/7
+#12 Inactive T Eth1/7
+#
+- name: Configures switch port of interfaces
+ dellemc.enterprise_sonic.sonic_l2_interfaces:
+ config:
+ - name: Eth1/3
+ access:
+ vlan: 10
+ state: merged
+#
+# After state:
+# ------------
+#
+#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
+#
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive A Eth1/3
+#
+- name: Configures switch port of interfaces
+ dellemc.enterprise_sonic.sonic_l2_interfaces:
+ config:
+ - name: Eth1/3
+ trunk:
+ allowed_vlans:
+ - vlan: 11
+ - vlan: 12
+ state: merged
+#
+# After state:
+# ------------
+#
+#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
+#
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#11 Inactive
+#12 Inactive A Eth1/4
+#13 Inactive T Eth1/4
+#14 Inactive A Eth1/5
+#15 Inactive T Eth1/5
+#
+- name: Configures switch port of interfaces
+ dellemc.enterprise_sonic.sonic_l2_interfaces:
+ config:
+ - name: Eth1/3
+ access:
+ vlan: 12
+ trunk:
+ allowed_vlans:
+ - vlan: 13
+ - vlan: 14
+ state: merged
+#
+# After state:
+# ------------
+#
+#do show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#11 Inactive
+#12 Inactive A Eth1/3
+# A Eth1/4
+#13 Inactive T Eth1/3
+# T Eth1/4
+#14 Inactive A Eth1/3
+# A Eth1/5
+#15 Inactive T Eth1/5
+#
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned always 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
+ 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_interfaces.l2_interfaces import L2_interfacesArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.l2_interfaces.l2_interfaces import L2_interfaces
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=L2_interfacesArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = L2_interfaces(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
new file mode 100644
index 000000000..e796897a5
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_l3_interfaces.py
@@ -0,0 +1,375 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_l3_interfaces
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+short_description: Configure the IPv4 and IPv6 parameters on Interfaces such as, Eth, LAG, VLAN, and loopback
+description:
+ - Configures Layer 3 interface settings on devices running Enterprise SONiC
+ Distribution by Dell Technologies. This module provides configuration management
+ of IPv4 and IPv6 parameters on Ethernet interfaces of devices running Enterprise SONiC.
+author: Kumaraguru Narayanan (@nkumaraguru)
+options:
+ config:
+ description: A list of l3_interfaces configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ required: True
+ type: str
+ description:
+ - Full name of the interface, for example, Eth1/3.
+ ipv4:
+ description:
+ - ipv4 configurations to be set for the Layer 3 interface mentioned in name option.
+ type: dict
+ suboptions:
+ addresses:
+ description:
+ - List of IPv4 addresses to be set.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPv4 address to be set in the format <ipv4 address>/<mask>
+ for example, 192.0.2.1/24.
+ type: str
+ secondary:
+ description:
+ - secondary flag of the ip address.
+ type: bool
+ default: 'False'
+ anycast_addresses:
+ description:
+ - List of IPv4 addresses to be set for anycast.
+ type: list
+ elements: str
+ ipv6:
+ description:
+ - ipv6 configurations to be set for the Layer 3 interface mentioned in name option.
+ type: dict
+ suboptions:
+ addresses:
+ description:
+ - List of IPv6 addresses to be set.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPv6 address to be set in the address format is <ipv6 address>/<mask>
+ for example, 2001:db8:2201:1::1/64.
+ type: str
+ enabled:
+ description:
+ - enabled flag of the ipv6.
+ type: bool
+ state:
+ description:
+ - The state that the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+
+# Using deleted
+#
+# 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
+# ip address 92.1.1.1/16 secondary
+# ipv6 address 90::1/16
+# ipv6 address 91::1/16
+# ipv6 address 92::1/16
+# ipv6 address 93::1/16
+#!
+#interface Vlan501
+# ip anycast-address 11.12.13.14/12
+# ip anycast-address 1.2.3.4/22
+#!
+#
+#
+- name: delete one l3 interface.
+ dellemc.enterprise_sonic.sonic_l3_interfaces:
+ config:
+ - name: Ethernet20
+ ipv4:
+ addresses:
+ - address: 83.1.1.1/16
+ - address: 84.1.1.1/16
+ - name: Ethernet24
+ ipv6:
+ enabled: true
+ addresses:
+ - address: 91::1/16
+ - name: Vlan501
+ ipv4:
+ anycast_addresses:
+ - 11.12.13.14/12
+ state: deleted
+
+# After state:
+# ------------
+#
+#rno-dctor-1ar01c01sw02# show running-configuration interface
+#!
+#interface Ethernet20
+# mtu 9100
+# speed 100000
+# shutdown
+# 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
+# ip address 92.1.1.1/16 secondary
+# ipv6 address 90::1/16
+# ipv6 address 92::1/16
+# ipv6 address 93::1/16
+#!
+#interface Vlan501
+# ip anycast-address 1.2.3.4/22
+#!
+#
+# Using deleted
+#
+# 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
+#!
+#interface Vlan501
+# ip anycast-address 11.12.13.14/12
+# ip anycast-address 1.2.3.4/22
+#!
+#
+#
+- name: delete all l3 interface
+ dellemc.enterprise_sonic.sonic_l3_interfaces:
+ config:
+ state: deleted
+#
+# After state:
+# ------------
+#
+#rno-dctor-1ar01c01sw02# show running-configuration interface
+#!
+#interface Ethernet20
+# mtu 9100
+# speed 100000
+# shutdown
+#!
+#interface Ethernet24
+# mtu 9100
+# speed 100000
+# shutdown
+#!
+#interface Vlan501
+#!
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#rno-dctor-1ar01c01sw02# show running-configuration interface
+#!
+#interface Ethernet20
+# mtu 9100
+# speed 100000
+# shutdown
+#!
+#interface Ethernet24
+# mtu 9100
+# speed 100000
+# shutdown
+#!
+#interface Vlan501
+# ip anycast-address 1.2.3.4/22
+#!
+#
+- name: Add l3 interface configurations
+ dellemc.enterprise_sonic.sonic_l3_interfaces:
+ config:
+ - name: Ethernet20
+ ipv4:
+ addresses:
+ - address: 83.1.1.1/16
+ - address: 84.1.1.1/16
+ secondary: True
+ ipv6:
+ enabled: true
+ addresses:
+ - address: 83::1/16
+ - address: 84::1/16
+ secondary: True
+ - name: Ethernet24
+ ipv4:
+ addresses:
+ - address: 91.1.1.1/16
+ ipv6:
+ addresses:
+ - address: 90::1/16
+ - address: 91::1/16
+ - address: 92::1/16
+ - address: 93::1/16
+ - name: Vlan501
+ ipv4:
+ anycast_addresses:
+ - 11.12.13.14/12
+ state: merged
+#
+# After 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
+#!
+#interface Vlan501
+# ip anycast-address 1.2.3.4/22
+# ip anycast-address 11.12.13.14/12
+#!
+#
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned is always 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
+ 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_interfaces.l3_interfaces import L3_interfacesArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.l3_interfaces.l3_interfaces import L3_interfaces
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=L3_interfacesArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = L3_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
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
new file mode 100644
index 000000000..630db7985
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_lag_interfaces.py
@@ -0,0 +1,238 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_lag_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_lag_interfaces
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+short_description: Manage link aggregation group (LAG) interface parameters
+description:
+ - This module manages attributes of link aggregation group (LAG) interfaces of
+ devices running Enterprise SONiC Distribution by Dell Technologies.
+author: Abirami N (@abirami-n)
+
+options:
+ config:
+ description: A list of LAG configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - ID of the LAG.
+ type: str
+ required: True
+ members:
+ description:
+ - The list of interfaces that are part of the group.
+ type: dict
+ suboptions:
+ interfaces:
+ description: The list of interfaces that are part of the group.
+ type: list
+ elements: dict
+ suboptions:
+ member:
+ description:
+ - The interface name.
+ type: str
+ mode:
+ description:
+ - Specifies mode of the port-channel while creation.
+ type: str
+ choices:
+ - static
+ - lacp
+ state:
+ description:
+ - The state that the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+#
+# Before state:
+# -------------
+#
+# interface Eth1/10
+# mtu 9100
+# speed 100000
+# no shutdown
+# !
+# interface Eth1/15
+# channel-group 12
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+- name: Merges provided configuration with device configuration
+ dellemc.enterprise_sonic.sonic_lag_interfaces:
+ config:
+ - name: PortChannel10
+ members:
+ interfaces:
+ - member: Eth1/10
+ state: merged
+#
+# After state:
+# ------------
+#
+# interface Eth1/10
+# channel-group 10
+# mtu 9100
+# speed 100000
+# no shutdown
+# !
+# interface Eth1/15
+# channel-group 12
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+# Using deleted
+#
+# Before state:
+# -------------
+# interface PortChannel10
+# !
+# interface Eth1/10
+# channel-group 10
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+- name: Deletes LAG attributes of a given interface, This does not delete the port-channel itself
+ dellemc.enterprise_sonic.sonic_lag_interfaces:
+ config:
+ - name: PortChannel10
+ members:
+ interfaces:
+ state: deleted
+#
+# After state:
+# ------------
+# interface PortChannel10
+# !
+# interface Eth1/10
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+# Using deleted
+#
+# Before state:
+# -------------
+# interface PortChannel 10
+# !
+# interface PortChannel 12
+# !
+# interface Eth1/10
+# channel-group 10
+# mtu 9100
+# speed 100000
+# no shutdown
+# !
+# interface Eth1/15
+# channel-group 12
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+- name: Deletes all LAGs and LAG attributes of all interfaces
+ dellemc.enterprise_sonic.sonic_lag_interfaces:
+ config:
+ state: deleted
+#
+# After state:
+# -------------
+#
+# interface Eth1/10
+# mtu 9100
+# speed 100000
+# no shutdown
+# !
+# interface Eth1/15
+# mtu 9100
+# speed 100000
+# no shutdown
+#
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration that is returned is always 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
+ 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.lag_interfaces.lag_interfaces import Lag_interfacesArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.lag_interfaces.lag_interfaces import Lag_interfaces
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Lag_interfacesArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Lag_interfaces(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
new file mode 100644
index 000000000..28d3dbb5b
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_mclag.py
@@ -0,0 +1,516 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_mclag
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_mclag
+version_added: 1.0.0
+notes:
+- 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
+author: Abirami N (@abirami-n)
+
+options:
+ config:
+ description: Dict of mclag domain configurations.
+ type: dict
+ suboptions:
+ domain_id:
+ description:
+ - ID of the mclag domain (MCLAG domain).
+ type: int
+ required: True
+ peer_address:
+ description:
+ - The IPV4 peer-ip for corresponding MCLAG.
+ type: str
+ source_address:
+ description:
+ - The IPV4 source-ip for corresponding MCLAG.
+ type: str
+ peer_link:
+ description:
+ - Peer-link for corresponding MCLAG.
+ type: str
+ system_mac:
+ description:
+ - Mac address of MCLAG.
+ type: str
+ keepalive:
+ description:
+ - MCLAG session keepalive-interval in secs.
+ type: int
+ session_timeout:
+ description:
+ - MCLAG session timeout value in secs.
+ type: int
+ unique_ip:
+ description: Holds Vlan dictionary for mclag unique ip.
+ suboptions:
+ vlans:
+ description:
+ - Holds list of VLANs for which a separate IP addresses is enabled for Layer 3 protocol support over MCLAG.
+ type: list
+ elements: dict
+ suboptions:
+ vlan:
+ description: Holds a VLAN ID.
+ type: str
+ type: dict
+ members:
+ description: Holds portchannels dictionary for an MCLAG domain.
+ suboptions:
+ portchannels:
+ description:
+ - Holds a list of portchannels for configuring as an MCLAG interface.
+ type: list
+ elements: dict
+ suboptions:
+ lag:
+ description: Holds a PortChannel ID.
+ type: str
+ type: dict
+ state:
+ description:
+ - The state that the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+#
+# Before state:
+# -------------
+#
+# sonic# show mclag brief
+# MCLAG Not Configured
+#
+- name: Merge provided configuration with device configuration
+ dellemc.enterprise_sonic.sonic_mclag:
+ config:
+ domain_id: 1
+ peer_address: 1.1.1.1
+ source_address: 2.2.2.2
+ peer_link: 'Portchannel1'
+ keepalive: 1
+ session_timeout: 3
+ unique_ip:
+ vlans:
+ - vlan: Vlan4
+ members:
+ portchannles:
+ - lag: PortChannel10
+ state: merged
+#
+# 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
+# System Mac : 20:04:0f:37:bd:c9
+#
+#
+# Number of MLAG Interfaces:1
+#-----------------------------------------------------------
+# MLAG Interface Local/Remote Status
+#-----------------------------------------------------------
+# PortChannel10 down/down
+#
+# admin@sonic:~$ show runningconfiguration all
+# {
+# ...
+# "MCLAG_UNIQUE_IP": {
+# "Vlan4": {
+# "unique_ip": "enable"
+# }
+# },
+# ...
+# }
+#
+#
+# Using merged
+#
+# 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
+# System Mac : 20:04:0f:37:bd:c9
+#
+#
+# Number of MLAG Interfaces:1
+#-----------------------------------------------------------
+# MLAG Interface Local/Remote Status
+#-----------------------------------------------------------
+# PortChannel10 down/down
+#
+# admin@sonic:~$ show runningconfiguration all
+# {
+# ...
+# "MCLAG_UNIQUE_IP": {
+# "Vlan4": {
+# "unique_ip": "enable"
+# }
+# },
+# ...
+# }
+#
+#
+- name: Merge device configuration with the provided configuration
+ dellemc.enterprise_sonic.sonic_mclag:
+ config:
+ domain_id: 1
+ source_address: 3.3.3.3
+ keepalive: 10
+ session_timeout: 30
+ unique_ip:
+ vlans:
+ - vlan: Vlan5
+ members:
+ portchannels:
+ - lag: PortChannel12
+ state: merged
+#
+# 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 : 10 secs
+# Session Timeout : 30 secs
+# System Mac : 20:04:0f:37:bd:c9
+#
+#
+# Number of MLAG Interfaces:2
+#-----------------------------------------------------------
+# MLAG Interface Local/Remote Status
+#-----------------------------------------------------------
+# PortChannel10 down/down
+# PortChannel12 down/down
+#
+# admin@sonic:~$ show runningconfiguration all
+# {
+# ...
+# "MCLAG_UNIQUE_IP": {
+# "Vlan4": {
+# "unique_ip": "enable"
+# },
+# "Vlan5": {
+# "unique_ip": "enable"
+# }
+# },
+# ...
+# }
+#
+#
+# Using deleted
+#
+# Before 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 : 10 secs
+# Session Timeout : 30 secs
+# System Mac : 20:04:0f:37:bd:c9
+#
+#
+# Number of MLAG Interfaces:1
+#-----------------------------------------------------------
+# MLAG Interface Local/Remote Status
+#-----------------------------------------------------------
+# PortChannel10 down/down
+#
+# admin@sonic:~$ show runningconfiguration all
+# {
+# ...
+# "MCLAG_UNIQUE_IP": {
+# "Vlan4": {
+# "unique_ip": "enable"
+# }
+# },
+# ...
+# }
+#
+- 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
+#
+# After state:
+# ------------
+#
+# sonic# show mclag brief
+#
+# Domain ID : 1
+# Role : standby
+# Session Status : down
+# Peer Link Status : down
+# Source Address :
+# Peer Address : 1.1.1.1
+# Peer Link : PortChannel1
+# Keepalive Interval : 1 secs
+# Session Timeout : 15 secs
+# System Mac : 20:04:0f:37:bd:c9
+#
+#
+# Number of MLAG Interfaces:0
+#
+# admin@sonic:~$ show runningconfiguration all
+# {
+# ...
+# "MCLAG_UNIQUE_IP": {
+# "Vlan4": {
+# "unique_ip": "enable"
+# }
+# },
+# ...
+# }
+#
+#
+#
+# Using deleted
+#
+# Before 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 : 10 secs
+# Session Timeout : 30 secs
+# System Mac : 20:04:0f:37:bd:c9
+#
+#
+# Number of MLAG Interfaces:1
+#-----------------------------------------------------------
+# MLAG Interface Local/Remote Status
+#-----------------------------------------------------------
+# PortChannel10 down/down
+#
+# admin@sonic:~$ show runningconfiguration all
+# {
+# ...
+# "MCLAG_UNIQUE_IP": {
+# "Vlan4": {
+# "unique_ip": "enable"
+# }
+# },
+# ...
+# }
+#
+- 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:~$
+#
+#
+# Using deleted
+#
+# Before 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 : 10 secs
+# Session Timeout : 30 secs
+# System Mac : 20:04:0f:37:bd:c9
+#
+#
+# Number of MLAG Interfaces:2
+#-----------------------------------------------------------
+# MLAG Interface Local/Remote Status
+#-----------------------------------------------------------
+# PortChannel10 down/down
+# PortChannel12 down/sown
+#
+# admin@sonic:~$ show runningconfiguration all
+# {
+# ...
+# "MCLAG_UNIQUE_IP": {
+# "Vlan4": {
+# "unique_ip": "enable"
+# }
+# },
+# ...
+# }
+- 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
+#
+# After state:
+# ------------
+#
+# sonic# show mclag brief
+#
+# Domain ID : 1
+# Role : standby
+# Session Status : down
+# Peer Link Status : down
+# Source Address :
+# Peer Address : 1.1.1.1
+# Peer Link : PortChannel1
+# Keepalive Interval : 1 secs
+# Session Timeout : 15 secs
+# System Mac : 20:04:0f:37:bd:c9
+#
+#
+# Number of MLAG Interfaces:0
+#
+# admin@sonic:~$ show runningconfiguration all
+# {
+# ...
+# "MCLAG_UNIQUE_IP": {
+# "Vlan4": {
+# "unique_ip": "enable"
+# }
+# },
+# ...
+# }
+#
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned always in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned always 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.mclag.mclag import MclagArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.mclag.mclag import Mclag
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=MclagArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Mclag(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ntp.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ntp.py
new file mode 100644
index 000000000..87db8bb06
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_ntp.py
@@ -0,0 +1,360 @@
+#!/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_ntp
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: sonic_ntp
+version_added: 2.0.0
+short_description: Manage NTP configuration on SONiC.
+description:
+ - This module provides configuration management of NTP for devices running SONiC.
+author: "M. Zhang (@mingjunzhang2019)"
+options:
+ config:
+ description:
+ - Specifies NTP related configurations.
+ type: dict
+ suboptions:
+ source_interfaces:
+ type: list
+ elements: str
+ description:
+ - List of names of NTP source interfaces.
+ enable_ntp_auth:
+ type: bool
+ description:
+ - Enable or disable NTP authentication.
+ trusted_keys:
+ type: list
+ elements: int
+ description:
+ - List of trusted NTP authentication keys.
+ vrf:
+ type: str
+ description:
+ - VRF name on which NTP is enabled.
+ servers:
+ type: list
+ elements: dict
+ description:
+ - List of NTP servers.
+ suboptions:
+ address:
+ type: str
+ description:
+ - IPv4/IPv6 address or host name of NTP server.
+ required: true
+ key_id:
+ type: int
+ description:
+ - NTP authentication key used by server.
+ - Key_id can not be deleted.
+ minpoll:
+ type: int
+ description:
+ - Minimum poll interval to poll NTP server.
+ - minpoll can not be deleted.
+ maxpoll:
+ type: int
+ description:
+ - Maximum poll interval to poll NTP server.
+ - maxpoll can not be deleted.
+ ntp_keys:
+ type: list
+ elements: dict
+ description:
+ - List of NTP authentication keys.
+ suboptions:
+ key_id:
+ type: int
+ description:
+ - NTP authentication key identifier.
+ required: true
+ key_type:
+ type: str
+ description:
+ - NTP authentication key type.
+ - key_type can not be deleted.
+ - When "state" is "merged", "key_type" is required.
+ choices:
+ - NTP_AUTH_SHA1
+ - NTP_AUTH_MD5
+ - NTP_AUTH_SHA2_256
+ key_value:
+ type: str
+ description:
+ - NTP authentication key value.
+ - key_value can not be deleted.
+ - When "state" is "merged", "key_value" is required.
+ encrypted:
+ type: bool
+ description:
+ - NTP authentication key_value is encrypted.
+ - encrypted can not be deleted.
+ - When "state" is "merged", "encrypted" is required.
+
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before 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
+#
+- name: Delete NTP server configuration
+ ntp:
+ config:
+ servers:
+ - address: 10.11.0.2
+ - address: dell.org
+ state: deleted
+
+# After state:
+# ------------
+#
+#sonic# show ntp server
+#----------------------------------------------------------------------
+#NTP Servers minpoll maxpoll Authentication key ID
+#----------------------------------------------------------------------
+#10.11.0.1 6 10
+#dell.com 6 9
+#
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#sonic# show ntp global
+#----------------------------------------------
+#NTP Global Configuration
+#----------------------------------------------
+#NTP source-interfaces: Ethernet0, Ethernet4, Ethernet8, Ethernet16
+#
+- name: Delete NTP source-interface configuration
+ ntp:
+ config:
+ source_interfaces:
+ - Ethernet8
+ - Ethernet16
+ state: deleted
+
+# After state:
+# ------------
+#
+#sonic# show ntp global
+#----------------------------------------------
+#NTP Global Configuration
+#----------------------------------------------
+#NTP source-interfaces: Ethernet0, Ethernet4
+#
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#sonic# show running-configuration | grep ntp
+#ntp authentication-key 8 sha1 U2FsdGVkX1/NpJrdOeyMeUHEkSohY6azY9VwbAqXRTY= encrypted
+#ntp authentication-key 10 md5 U2FsdGVkX1/Gxds/5pscCvIKbVngGaKka4SQineS51Y= encrypted
+#ntp authentication-key 20 sha2-256 U2FsdGVkX1/eAzKj1teKhYWD7tnzOsYOijGeFAT0rKM= encrypted
+#
+- name: Delete NTP key configuration
+ ntp:
+ config:
+ ntp_keys:
+ - key_id: 10
+ - key_id: 20
+ state: deleted
+#
+# After state:
+# ------------
+#
+#sonic# show running-configuration | grep ntp
+#ntp authentication-key 8 sha1 U2FsdGVkX1/NpJrdOeyMeUHEkSohY6azY9VwbAqXRTY= encrypted
+#
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#sonic# show ntp server
+#----------------------------------------------------------------------
+#NTP Servers minpoll maxpoll Authentication key ID
+#----------------------------------------------------------------------
+#10.11.0.1 6 10
+#dell.com 6 9
+#
+- name: Merge NTP server configuration
+ ntp:
+ config:
+ servers:
+ - address: 10.11.0.2
+ minpoll: 5
+ - address: dell.org
+ minpoll: 7
+ maxpoll: 10
+ 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
+#
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#sonic# show ntp global
+#----------------------------------------------
+#NTP Global Configuration
+#----------------------------------------------
+#NTP source-interfaces: Ethernet0, Ethernet4
+#
+- name: Merge NTP source-interface configuration
+ ntp:
+ config:
+ source_interfaces:
+ - Ethernet8
+ - Ethernet16
+ state: merged
+
+# After state:
+# ------------
+#
+#sonic# show ntp global
+#----------------------------------------------
+#NTP Global Configuration
+#----------------------------------------------
+#NTP source-interfaces: Ethernet0, Ethernet4, Ethernet8, Ethernet16
+#
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#sonic# show running-configuration | grep ntp
+#ntp authentication-key 8 sha1 U2FsdGVkX1/NpJrdOeyMeUHEkSohY6azY9VwbAqXRTY= encrypted
+#
+- name: Merge NTP key configuration
+ ntp:
+ config:
+ ntp_keys:
+ - key_id: 10
+ key_type: NTP_AUTH_MD5
+ key_value: dellemc10
+ encrypted: false
+ - key_id: 20
+ key_type: NTP_AUTH_SHA2_256
+ key_value: dellemc20
+ encrypted: false
+ state: merged
+#
+# After state:
+# ------------
+#
+#sonic# show running-configuration | grep ntp
+#ntp authentication-key 8 sha1 U2FsdGVkX1/NpJrdOeyMeUHEkSohY6azY9VwbAqXRTY= encrypted
+#ntp authentication-key 10 md5 U2FsdGVkX1/Gxds/5pscCvIKbVngGaKka4SQineS51Y= encrypted
+#ntp authentication-key 20 sha2-256 U2FsdGVkX1/eAzKj1teKhYWD7tnzOsYOijGeFAT0rKM= encrypted
+#
+"""
+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.ntp.ntp import NtpArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.ntp.ntp import Ntp
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=NtpArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Ntp(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
new file mode 100644
index 000000000..66ea00476
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_port_breakout.py
@@ -0,0 +1,228 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_breakout
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_port_breakout
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Niraimadaiselvam M (@niraimadaiselvamm)
+short_description: Configure port breakout settings on physical interfaces
+description:
+ - This module provides configuration management of port breakout parameters on devices running Enterprise SONiC.
+options:
+ config:
+ description:
+ - Specifies the port breakout related configuration.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Specifies the name of the port breakout.
+ type: str
+ required: true
+ mode:
+ description:
+ - Specifies the mode of the port breakout.
+ type: str
+ choices:
+ - 1x100G
+ - 1x400G
+ - 1x40G
+ - 2x100G
+ - 2x200G
+ - 2x50G
+ - 4x100G
+ - 4x10G
+ - 4x25G
+ - 4x50G
+ 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.
+ default: merged
+ choices: ['merged', 'deleted']
+ type: str
+"""
+EXAMPLES = """
+# Using deleted
+#
+# 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
+ dellemc.enterprise_sonic.sonic_port_breakout:
+ config:
+ - name: 1/11
+ mode: 1x100G
+ state: deleted
+
+# 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
+
+
+# Using deleted
+#
+# 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
+ 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
+
+
+# Using merged
+#
+# 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
+#
+- name: Merge users configurations
+ dellemc.enterprise_sonic.sonic_port_breakout:
+ config:
+ - name: 1/11
+ mode: 1x100G
+ state: merged
+
+
+# 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
+
+
+"""
+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.port_breakout.port_breakout import Port_breakoutArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.port_breakout.port_breakout import Port_breakout
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Port_breakoutArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Port_breakout(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
new file mode 100644
index 000000000..5a734e8b2
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_prefix_lists.py
@@ -0,0 +1,423 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# 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_prefix_lists
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_prefix_lists
+version_added: "2.0.0"
+author: Kerry Meyer (@kerry-meyer)
+short_description: prefix list configuration handling for SONiC
+description:
+ - This module provides configuration management for prefix list parameters on devices running SONiC.
+options:
+ config:
+ description:
+ - Specifies a list of prefix set configuration dictionaries
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of a prefix set (a list of prefix entries)
+ type: str
+ required: true
+ afi:
+ description:
+ - Specifies the Address Family for addresses in the prefix list entries
+ type: str
+ choices: ["ipv4", "ipv6"]
+ default: "ipv4"
+ prefixes:
+ description:
+ - A list of prefix entries
+ type: list
+ elements: dict
+ suboptions:
+ sequence:
+ description:
+ - Precedence for this prefix entry (unique within the prefix list)
+ type: int
+ required: true
+ action:
+ description:
+ - Action to be taken for addresses matching this prefix entry
+ type: str
+ required: true
+ choices: ["permit", "deny"]
+ prefix:
+ description:
+ - IPv4 or IPv6 prefix in A.B.C.D/LEN or A:B::C:D/LEN format
+ type: str
+ required: true
+ ge:
+ description: Minimum prefix length to be matched
+ type: int
+ le:
+ description: Maximum prefix length to be matched
+ type: int
+ state:
+ 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.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using "merged" state to create initial configuration
+#
+# Before state:
+# -------------
+#
+# sonic# show running-configuration ip prefix-list
+# sonic#
+# (No configuration present)
+#
+# -------------
+#
+- name: Merge initial prefix-list configuration
+ dellemc.enterprise_sonic.sonic_prefix_lists:
+ config:
+ - name: pfx1
+ afi: "ipv4"
+ prefixes:
+ - sequence: 10
+ prefix: "1.2.3.4/24"
+ action: "permit"
+ ge: 26
+ le: 30
+ state: merged
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration ip prefix-list
+# !
+# ip prefix-list pfx1 seq 10 permit 1.2.3.4/24 ge 26 le 30
+# ------------
+#
+# ***************************************************************
+# Using "merged" state to update and add 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
+#
+# sonic# show running-configuration ipv6 prefix-list
+# sonic#
+# (no IPv6 prefix-list configuration present)
+#
+# ------------
+#
+- name: Merge additional prefix-list configuration
+ dellemc.enterprise_sonic.sonic_prefix_lists:
+ config:
+ - name: pfx1
+ afi: "ipv4"
+ prefixes:
+ - sequence: 20
+ action: "deny"
+ prefix: "1.2.3.12/26"
+ - sequence: 30
+ action: "permit"
+ prefix: "7.8.9.0/24"
+ - name: pfx6
+ afi: "ipv6"
+ prefixes:
+ - sequence: 25
+ action: "permit"
+ prefix: "40::300/124"
+ state: merged
+
+# After 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 pfx1 seq 20 deny 1.2.3.12/26
+# ip prefix-list pfx1 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
+#
+# ***************************************************************
+# Using "deleted" state to remove 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 pfx1 seq 20 deny 1.2.3.12/26
+# ip prefix-list pfx1 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: Delete selected prefix-list configuration
+ dellemc.enterprise_sonic.sonic_prefix_lists:
+ config:
+ - name: pfx1
+ afi: "ipv4"
+ prefixes:
+ - sequence: 10
+ prefix: "1.2.3.4/24"
+ action: "permit"
+ ge: 26
+ le: 30
+ - sequence: 20
+ action: "deny"
+ prefix: "1.2.3.12/26"
+ - name: pfx6
+ afi: "ipv6"
+ prefixes:
+ - sequence: 25
+ action: "permit"
+ prefix: "40::300/124"
+ state: deleted
+
+# After state:
+# ------------
+#
+# sonic# show running-configuration ip prefix-list
+# !
+# ip prefix-list pfx1 seq 30 permit 7.8.9.0/24
+#
+# sonic# show running-configuration ipv6 prefix-list
+# sonic#
+# (no IPv6 prefix-list configuration present)
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+
+# "before": [
+# {
+# "afi": "ipv6",
+# "name": "pf4",
+# "prefixes": [
+# {
+# "action": "permit",
+# "ge": null,
+# "le": null,
+# "prefix": "50:60::/64",
+# "sequence": 40
+# }
+# ]
+# },
+# {
+# "afi": "ipv4",
+# "name": "pf3",
+# "prefixes": [
+# {
+# "action": "deny",
+# "ge": null,
+# "le": 27,
+# "prefix": "1.2.3.128/25",
+# "sequence": 30
+# }
+# ]
+# },
+# {
+# "afi": "ipv4",
+# "name": "pf2",
+# "prefixes": [
+# {
+# "action": "permit",
+# "ge": 27,
+# "le": 29,
+# "prefix": "10.20.30.128/25",
+# "sequence": 50
+# },
+# {
+# "action": "deny",
+# "ge": 26,
+# "le": null,
+# "prefix": "10.20.30.0/24",
+# "sequence": 20
+# }
+# ]
+# },
+# {
+# "afi": "ipv4",
+# "name": "pf1",
+# "prefixes": [
+# {
+# "action": "deny",
+# "ge": 25,
+# "le": 27,
+# "prefix": "1.2.3.0/24",
+# "sequence": 10
+# }
+# ]
+# }
+# ]
+
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+
+# "after": [
+# {
+# "afi": "ipv4",
+# "name": "pf5",
+# "prefixes": [
+# {
+# "action": "permit",
+# "ge": null,
+# "le": null,
+# "prefix": "15.25.35.0/24",
+# "sequence": 15
+# }
+# ]
+# },
+# {
+# "afi": "ipv4",
+# "name": "pf1",
+# "prefixes": [
+# {
+# "action": "deny",
+# "ge": 25,
+# "le": 27,
+# "prefix": "1.2.3.0/24",
+# "sequence": 10
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "name": "pf4",
+# "prefixes": [
+# {
+# "action": "permit",
+# "ge": null,
+# "le": null,
+# "prefix": "50:60::/64",
+# "sequence": 40
+# }
+# ]
+# },
+# {
+# "afi": "ipv4",
+# "name": "pf3",
+# "prefixes": [
+# {
+# "action": "deny",
+# "ge": null,
+# "le": 27,
+# "prefix": "1.2.3.128/25",
+# "sequence": 30
+# }
+# ]
+# },
+# {
+# "afi": "ipv4",
+# "name": "pf2",
+# "prefixes": [
+# {
+# "action": "permit",
+# "ge": 27,
+# "le": 29,
+# "prefix": "10.20.30.128/25",
+# "sequence": 50
+# },
+# {
+# "action": "deny",
+# "ge": 26,
+# "le": null,
+# "prefix": "10.20.30.0/24",
+# "sequence": 20
+# }
+# ]
+# }
+# ]
+
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+
+# "commands": [
+# {
+# "afi": "ipv4",
+# "name": "pf5",
+# "prefixes": [
+# {
+# "action": "permit",
+# "prefix": "15.25.35.0/24",
+# "sequence": 15
+# }
+# ],
+# "state": "merged"
+# }
+# ],
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.argspec.prefix_lists.prefix_lists import Prefix_listsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.prefix_lists.prefix_lists import Prefix_lists
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Prefix_listsArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Prefix_lists(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
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
new file mode 100644
index 000000000..1df4aff61
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_radius_server.py
@@ -0,0 +1,328 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# © Copyright 2021 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_radius_server
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_radius_server
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Niraimadaiselvam M (@niraimadaiselvamm)
+short_description: Manage RADIUS server and its parameters
+description:
+ - This module provides configuration management of radius server parameters on devices running Enterprise SONiC.
+options:
+ config:
+ description:
+ - Specifies the radius server related configuration.
+ type: dict
+ suboptions:
+ auth_type:
+ description:
+ - Specifies the authentication type of the radius server.
+ type: str
+ choices:
+ - pap
+ - chap
+ - mschapv2
+ default: pap
+ key:
+ description:
+ - Specifies the key of the radius server.
+ type: str
+ nas_ip:
+ description:
+ - Specifies the network access server of the radius server.
+ type: str
+ statistics:
+ description:
+ - Specifies the statistics flag of the radius server.
+ type: bool
+ timeout:
+ description:
+ - Specifies the timeout of the radius server.
+ type: int
+ retransmit:
+ description:
+ - Specifies the re-transmit value of the radius server.
+ type: int
+ servers:
+ description:
+ - Specifies the servers list of the radius server.
+ type: dict
+ suboptions:
+ host:
+ description:
+ - Specifies the host details of the radius servers list.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Specifies the name of the radius server host.
+ type: str
+ auth_type:
+ description:
+ - Specifies the authentication type of the radius server host.
+ type: str
+ choices:
+ - pap
+ - chap
+ - mschapv2
+ key:
+ description:
+ - Specifies the key of the radius server host.
+ type: str
+ priority:
+ description:
+ - Specifies the priority of the radius server host.
+ type: int
+ port:
+ description:
+ - Specifies the port of the radius server host.
+ type: int
+ timeout:
+ description:
+ - Specifies the timeout of the radius server host.
+ type: int
+ retransmit:
+ description:
+ - Specifies the retransmit of the radius server host.
+ type: int
+ source_interface:
+ description:
+ - Specifies the source interface of the radius server host.
+ type: str
+ vrf:
+ description:
+ - Specifies the vrf of the radius server host.
+ type: str
+ state:
+ description:
+ - 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.
+ default: merged
+ choices: ['merged', 'deleted']
+ type: str
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#sonic(config)# do show radius-server
+#---------------------------------------------------------
+#RADIUS Global Configuration
+#---------------------------------------------------------
+#nas-ip-addr: 1.2.3.4
+#statistics : True
+#timeout : 10
+#auth-type : chap
+#key : chap
+#retransmit : 3
+#--------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY AUTH-PORT PRIORITY TIMEOUT RTSMT VRF SI
+#--------------------------------------------------------------------------------
+#localhost mschapv2 local 52 2 20 2 mgmt Ethernet12
+#myhost chap local 53 3 23 3 mgmt Ethernet24
+#---------------------------------------------------------
+#RADIUS Statistics
+#---------------------------------------------------------
+#
+
+- name: Merge radius configurations
+ dellemc.enterprise_sonic.sonic_radius_server:
+ config:
+ auth_type: chap
+ nas_ip: 1.2.3.4
+ statistics: true
+ timeout: 10
+ servers:
+ host:
+ - name: localhost
+ state: deleted
+
+# After state:
+# ------------
+#sonic(config)# do show radius-server
+#---------------------------------------------------------
+#RADIUS Global Configuration
+#---------------------------------------------------------
+#timeout : 5
+#auth-type : pap
+#key : chap
+#retransmit : 3
+#--------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY AUTH-PORT PRIORITY TIMEOUT RTSMT VRF SI
+#--------------------------------------------------------------------------------
+#myhost chap local 53 3 23 3 mgmt Ethernet24
+
+
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#sonic(config)# do show radius-server
+#---------------------------------------------------------
+#RADIUS Global Configuration
+#---------------------------------------------------------
+#nas-ip-addr: 1.2.3.4
+#statistics : True
+#timeout : 10
+#auth-type : chap
+#key : chap
+#retransmit : 3
+#--------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY AUTH-PORT PRIORITY TIMEOUT RTSMT VRF SI
+#--------------------------------------------------------------------------------
+#localhost mschapv2 local 52 2 20 2 mgmt Ethernet12
+#myhost chap local 53 3 23 3 mgmt Ethernet24
+#---------------------------------------------------------
+#RADIUS Statistics
+#---------------------------------------------------------
+#
+- name: Merge radius configurations
+ dellemc.enterprise_sonic.sonic_radius_server:
+ config:
+ state: deleted
+
+# After state:
+# ------------
+#sonic(config)# do show radius-server
+#---------------------------------------------------------
+#RADIUS Global Configuration
+#---------------------------------------------------------
+#timeout : 5
+#auth-type : pap
+
+
+# Using merged
+#
+# Before state:
+# -------------
+#
+#sonic(config)# do show radius-server
+#---------------------------------------------------------
+#RADIUS Global Configuration
+#---------------------------------------------------------
+#
+- name: Merge radius configurations
+ dellemc.enterprise_sonic.sonic_radius_server:
+ config:
+ auth_type: chap
+ key: chap
+ nas_ip: 1.2.3.4
+ statistics: true
+ timeout: 10
+ retransmit: 3
+ servers:
+ host:
+ - name: localhost
+ auth_type: mschapv2
+ key: local
+ priority: 2
+ port: 52
+ retransmit: 2
+ timeout: 20
+ source_interface: Eth 12
+ vrf: mgmt
+ state: merged
+
+# After state:
+# ------------
+#
+#sonic(config)# do show radius-server
+#---------------------------------------------------------
+#RADIUS Global Configuration
+#---------------------------------------------------------
+#nas-ip-addr: 1.2.3.4
+#statistics : True
+#timeout : 10
+#auth-type : chap
+#key : chap
+#retransmit : 3
+#--------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY AUTH-PORT PRIORITY TIMEOUT RTSMT VRF SI
+#--------------------------------------------------------------------------------
+#localhost mschapv2 local 52 2 20 2 mgmt Ethernet12
+#---------------------------------------------------------
+#RADIUS Statistics
+#---------------------------------------------------------
+
+
+"""
+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.radius_server.radius_server import Radius_serverArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.radius_server.radius_server import Radius_server
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Radius_serverArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Radius_server(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
new file mode 100644
index 000000000..7a528cdf0
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_static_routes.py
@@ -0,0 +1,267 @@
+#!/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_static_routes
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_static_routes
+version_added: 2.0.0
+short_description: Manage static routes configuration on SONiC
+description:
+ - This module provides configuration management of static routes for devices running SONiC
+author: "Shade Talabi (@stalabi1)"
+options:
+ config:
+ type: list
+ elements: dict
+ description:
+ - Manages 'static_routes' configurations
+ suboptions:
+ vrf_name:
+ required: True
+ type: str
+ description:
+ - Name of the configured VRF on the device.
+ static_list:
+ type: list
+ elements: dict
+ description:
+ - A list of 'static_routes' configurations.
+ suboptions:
+ prefix:
+ required: True
+ type: str
+ description:
+ - Destination prefix for the static route, either IPv4 or IPv6.
+ next_hops:
+ type: list
+ elements: dict
+ description:
+ - A list of next-hops to be utilised for the static route being specified.
+ suboptions:
+ index:
+ required: True
+ type: dict
+ description:
+ - An identifier utilised to uniquely reference the next-hop.
+ suboptions:
+ blackhole:
+ type: bool
+ default: False
+ description:
+ - Indicates that packets matching this route should be discarded.
+ interface:
+ type: str
+ description:
+ - The reference to a base interface.
+ nexthop_vrf:
+ type: str
+ description:
+ - Name of the next-hop network instance for leaked routes.
+ next_hop:
+ type: str
+ description:
+ - The next-hop that is to be used for the static route.
+ metric:
+ type: int
+ description:
+ - Specifies the preference of the next-hop entry when it is injected into the RIB.
+ track:
+ type: int
+ description:
+ - The IP SLA track ID for static route.
+ tag:
+ type: int
+ description:
+ - The tag value for the static route.
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+
+# Using merged
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration | grep "ip route"
+# (No "ip route" configuration present)
+
+ - name: Merge static routes configurations
+ dellemc.enterprise_sonic.sonic_static_routes:
+ config:
+ - vrf_name: 'default'
+ static_list:
+ - prefix: '2.0.0.0/8'
+ next_hops:
+ - index:
+ interface: 'Ethernet4'
+ metric: 1
+ tag: 2
+ track: 3
+ - index:
+ next_hop: '3.0.0.0'
+ metric: 2
+ tag: 4
+ track: 8
+ - vrf_name: '{{vrf_1}}'
+ static_list:
+ - prefix: '3.0.0.0/8'
+ next_hops:
+ - index:
+ interface: 'eth0'
+ nexthop_vrf: '{{vrf_2}}'
+ next_hop: '4.0.0.0'
+ metric: 4
+ tag: 5
+ track: 6
+ - index:
+ blackhole: True
+ metric: 10
+ tag: 20
+ track: 30
+ state: merged
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration | grep "ip route"
+# 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
+#
+#
+# Modifying previous merge
+
+ - name: Modify static routes configurations
+ dellemc.enterprise_sonic.sonic_static_routes:
+ config:
+ - vrf_name: '{{vrf_1}}'
+ static_list:
+ - prefix: '3.0.0.0/8'
+ next_hops:
+ - index:
+ blackhole: True
+ metric: 11
+ tag: 22
+ track: 33
+ state: merged
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration | grep "ip route"
+# 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
+
+
+# Using deleted
+#
+# Before State:
+# -------------
+#
+# sonic# show running-configuration | grep "ip route"
+# 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
+
+ - name: Delete static routes configurations
+ dellemc.enterprise_sonic.sonic_static_routes:
+ config:
+ - vrf_name: 'default'
+ static_list:
+ - prefix: '2.0.0.0/8'
+ next_hops:
+ - index:
+ interface: 'Ethernet4'
+ - vrf_name: '{{vrf_1}}'
+ state: deleted
+
+# After State:
+# ------------
+#
+# sonic# show running-configuration | grep "ip route"
+# ip route 2.0.0.0/8 3.0.0.0 tag 4 track 8 2
+
+
+"""
+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.static_routes.static_routes import Static_routesArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.static_routes.static_routes import Static_routes
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Static_routesArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Static_routes(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
new file mode 100644
index 000000000..efb285a11
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_system.py
@@ -0,0 +1,214 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 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_system
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_system
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Abirami N (@abirami-n)
+short_description: Configure system parameters
+description:
+ - This module is used for configuration management of global system parameters on devices running Enterprise SONiC.
+options:
+ config:
+ description:
+ - Specifies the system related configurations
+ type: dict
+ suboptions:
+ hostname:
+ description:
+ - Specifies the hostname of the SONiC device
+ type: str
+ interface_naming:
+ description:
+ - Specifies the type of interface-naming in device
+ type: str
+ choices:
+ - standard
+ - native
+ anycast_address:
+ description:
+ - Specifies different types of anycast address that can be configured on the device
+ type: dict
+ suboptions:
+ ipv4:
+ description:
+ - Enable or disable ipv4 anycast-address
+ type: bool
+ ipv6:
+ description:
+ - Enable or disable ipv6 anycast-address
+ type: bool
+ mac_address:
+ description:
+ - Specifies the mac anycast-address
+ type: str
+ state:
+ description:
+ - Specifies the operation to be performed on the system parameters configured on the device.
+ - 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']
+ type: str
+"""
+EXAMPLES = """
+# Using deleted
+#
+# 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
+#interface-naming standard
+
+- name: Merge provided configuration with device configuration
+ dellemc.enterprise_sonic.sonic_system:
+ config:
+ hostname: SONIC
+ interface_naming: standard
+ anycast_address:
+ ipv6: true
+ state: deleted
+
+# After state:
+# ------------
+#!
+#sonic(config)#do show running-configuration
+#!
+#ip anycast-mac-address aa:bb:cc:dd:ee:ff
+#ip anycast-address enable
+
+
+# Using deleted
+#
+# 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
+#interface-naming standard
+
+- name: Delete all system related configs in device configuration
+ dellemc.enterprise_sonic.sonic_system:
+ config:
+ state: deleted
+
+# After state:
+# ------------
+#!
+#sonic(config)#do show running-configuration
+#!
+
+
+# Using merged
+#
+# Before state:
+# -------------
+#!
+#sonic(config)#do show running-configuration
+#!
+
+- name: Merge provided configuration with device configuration
+ dellemc.enterprise_sonic.sonic_system:
+ config:
+ hostname: SONIC
+ interface_naming: standard
+ anycast_address:
+ ipv6: true
+ ipv4: true
+ mac_address: aa:bb:cc:dd:ee:ff
+ state: merged
+
+# After 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
+#interface-naming standard
+
+
+"""
+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.system.system import SystemArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.system.system import System
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=SystemArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = System(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
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
new file mode 100644
index 000000000..3295e11ba
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_tacacs_server.py
@@ -0,0 +1,297 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# © Copyright 2021 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_tacacs_server
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_tacacs_server
+version_added: 1.1.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Niraimadaiselvam M (@niraimadaiselvamm)
+short_description: Manage TACACS server and its parameters
+description:
+ - This module provides configuration management of tacacs server parameters on devices running Enterprise SONiC.
+options:
+ config:
+ description:
+ - Specifies the tacacs server related configuration.
+ type: dict
+ suboptions:
+ auth_type:
+ description:
+ - Specifies the authentication type of the tacacs server.
+ type: str
+ choices:
+ - pap
+ - chap
+ - mschap
+ - login
+ default: pap
+ key:
+ description:
+ - Specifies the key of the tacacs server.
+ type: str
+ timeout:
+ description:
+ - Specifies the timeout of the tacacs server.
+ type: int
+ source_interface:
+ description:
+ - Specifies the source interface of the tacacs server.
+ type: str
+ servers:
+ description:
+ - Specifies the servers list of the tacacs server.
+ type: dict
+ suboptions:
+ host:
+ description:
+ - Specifies the host details of the tacacs servers list.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Specifies the name of the tacacs server host.
+ type: str
+ auth_type:
+ description:
+ - Specifies the authentication type of the tacacs server host.
+ type: str
+ choices:
+ - pap
+ - chap
+ - mschap
+ - login
+ default: pap
+ key:
+ description:
+ - Specifies the key of the tacacs server host.
+ type: str
+ priority:
+ description:
+ - Specifies the priority of the tacacs server host.
+ type: int
+ default: 1
+ port:
+ description:
+ - Specifies the port of the tacacs server host.
+ type: int
+ default: 49
+ timeout:
+ description:
+ - Specifies the timeout of the tacacs server host.
+ type: int
+ default: 5
+ vrf:
+ description:
+ - Specifies the vrf of the tacacs server host.
+ type: str
+ default: default
+ state:
+ description:
+ - 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.
+ default: merged
+ choices: ['merged', 'deleted']
+ type: str
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# do show tacacs-server
+#---------------------------------------------------------
+#TACACS Global Configuration
+#---------------------------------------------------------
+#source-interface : Ethernet12
+#timeout : 10
+#auth-type : login
+#key : login
+#------------------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY PORT PRIORITY TIMEOUT VRF
+#------------------------------------------------------------------------------------------------
+#1.2.3.4 pap ***** 50 2 10 mgmt
+#localhost pap 49 1 5 default
+#
+
+- name: Merge tacacs configurations
+ dellemc.enterprise_sonic.sonic_tacacs_server:
+ config:
+ auth_type: login
+ key: login
+ source_interface: Ethernet 12
+ timeout: 10
+ servers:
+ host:
+ - name: 1.2.3.4
+ state: deleted
+
+# After state:
+# ------------
+#
+#do show tacacs-server
+#---------------------------------------------------------
+#TACACS Global Configuration
+#---------------------------------------------------------
+#timeout : 5
+#auth-type : pap
+#------------------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY PORT PRIORITY TIMEOUT VRF
+#------------------------------------------------------------------------------------------------
+#localhost pap 49 1 5 default
+
+
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# do show tacacs-server
+#---------------------------------------------------------
+#TACACS Global Configuration
+#---------------------------------------------------------
+#source-interface : Ethernet12
+#timeout : 10
+#auth-type : login
+#key : login
+#------------------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY PORT PRIORITY TIMEOUT VRF
+#------------------------------------------------------------------------------------------------
+#1.2.3.4 pap ***** 50 2 10 mgmt
+#localhost pap 49 1 5 default
+#
+
+- name: Merge tacacs configurations
+ dellemc.enterprise_sonic.sonic_tacacs_server:
+ config:
+ state: deleted
+
+# After state:
+# ------------
+#
+#do show tacacs-server
+#---------------------------------------------------------
+#TACACS Global Configuration
+#---------------------------------------------------------
+#timeout : 5
+#auth-type : pap
+
+
+# Using merged
+#
+# Before state:
+# -------------
+#
+#sonic(config)# do show tacacs-server
+#---------------------------------------------------------
+#TACACS Global Configuration
+#---------------------------------------------------------
+#
+- name: Merge tacacs configurations
+ dellemc.enterprise_sonic.sonic_tacacs_server:
+ config:
+ auth_type: pap
+ key: pap
+ source_interface: Ethernet 12
+ timeout: 10
+ servers:
+ host:
+ - name: 1.2.3.4
+ auth_type: pap
+ key: 1234
+ state: merged
+
+# After state:
+# ------------
+#
+#sonic(config)# do show tacacs-server
+#---------------------------------------------------------
+#TACACS Global Configuration
+#---------------------------------------------------------
+#source-interface : Ethernet12
+#timeout : 10
+#auth-type : pap
+#key : pap
+#------------------------------------------------------------------------------------------------
+#HOST AUTH-TYPE KEY PORT PRIORITY TIMEOUT VRF
+#------------------------------------------------------------------------------------------------
+#1.2.3.4 pap 1234 49 1 5 default
+
+
+"""
+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.tacacs_server.tacacs_server import Tacacs_serverArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.tacacs_server.tacacs_server import Tacacs_server
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Tacacs_serverArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Tacacs_server(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_users.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_users.py
new file mode 100644
index 000000000..7f0855a94
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_users.py
@@ -0,0 +1,210 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# 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_users
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_users
+version_added: 1.1.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Niraimadaiselvam M (@niraimadaiselvamm)
+short_description: Manage users and its parameters
+description:
+ - This module provides configuration management of users parameters on devices running Enterprise SONiC.
+options:
+ config:
+ description:
+ - Specifies the users related configuration.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Specifies the name of the user.
+ type: str
+ required: true
+ role:
+ description:
+ - Specifies the role of the user.
+ type: str
+ choices:
+ - admin
+ - operator
+ password:
+ description:
+ - Specifies the password of the user.
+ type: str
+ update_password:
+ description:
+ - Specifies the update password flag.
+ - In case of always, password will be updated every time.
+ - In case of on_create, password will be updated only when user is created.
+ type: str
+ choices:
+ - always
+ - on_create
+ default: always
+ state:
+ description:
+ - 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.
+ default: merged
+ choices: ['merged', 'deleted']
+ type: str
+"""
+EXAMPLES = """
+# 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
+ 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
+
+
+# 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
+ dellemc.enterprise_sonic.sonic_users:
+ config:
+ state: deleted
+
+# After state:
+# ------------
+#
+#do show running-configuration
+#!
+#username admin password $6$sdZt2C7F$3oPSRkkJyLZtsKlFNGWdwssblQWBj5dXM6qAJAQl7dgOfqLSpZJ/n6xf8zPRcqPUFCu5ZKpEtynJ9sZ/S8Mgj. role admin
+
+
+# Using merged
+#
+# Before state:
+# -------------
+#
+#do show running-configuration
+#!
+#username admin password $6$sdZt2C7F$3oPSRkkJyLZtsKlFNGWdwssblQWBj5dXM6qAJAQl7dgOfqLSpZJ/n6xf8zPRcqPUFCu5ZKpEtynJ9sZ/S8Mgj. role admin
+#
+- name: Merge users configurations
+ dellemc.enterprise_sonic.sonic_users:
+ config:
+ - name: sysadmin
+ role: admin
+ password: admin
+ update_password: always
+ - name: sysoperator
+ role: operator
+ password: operator
+ update_password: always
+ state: merged
+
+# 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
+
+
+"""
+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.users.users import UsersArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.users.users import Users
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=UsersArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Users(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
new file mode 100644
index 000000000..cfd536c79
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vlans.py
@@ -0,0 +1,241 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_vlans
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_vlans
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+author: Mohamed Javeed (@javeedf)
+short_description: Manage VLAN and its parameters
+description:
+ - This module provides configuration management of VLANs parameters
+ on devices running Enterprise SONiC Distribution by Dell Technologies.
+options:
+ config:
+ description: A dictionary of VLAN options.
+ type: list
+ elements: dict
+ suboptions:
+ vlan_id:
+ description:
+ - ID of the VLAN
+ - Range is 1 to 4094
+ type: int
+ required: true
+ description:
+ description:
+ - Description about the VLAN.
+ type: str
+ state:
+ description:
+ - The state that the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+#sonic# show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#30 Inactive
+#
+#sonic#
+#
+
+
+- name: Merges given VLAN attributes with the device configuration
+ dellemc.enterprise_sonic.sonic_vlans:
+ config:
+ - vlan_id: 10
+ description: "Internal"
+ state: merged
+
+# After state:
+# ------------
+#
+#sonic# show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#30 Inactive
+#
+#sonic#
+#
+#sonic# show interface Vlan 10
+#Description: Internal
+#Vlan10 is up
+#Mode of IPV4 address assignment: not-set
+#Mode of IPV6 address assignment: not-set
+#IP MTU 6000 bytes
+#sonic#
+#
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+#sonic# show interface Vlan 70
+#Description: Internal
+#Vlan70 is up
+#Mode of IPV4 address assignment: not-set
+#Mode of IPV6 address assignment: not-set
+#IP MTU 6000 bytes
+
+- name: Deletes attributes of the given VLANs
+ dellemc.enterprise_sonic.sonic_vlans:
+ config:
+ - vlan_id: 70
+ description: "Internal"
+ state: deleted
+
+# After state:
+# ------------
+#
+#sonic# show interface Vlan 70
+#Vlan70 is up
+#Mode of IPV4 address assignment: not-set
+#Mode of IPV6 address assignment: not-set
+#IP MTU 6000 bytes
+
+# Before state:
+# -------------
+#
+#sonic# show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#20 Inactive
+#
+#sonic#
+
+- name: Deletes attributes of the given VLANs
+ dellemc.enterprise_sonic.sonic_vlans:
+ config:
+ - vlan_id: 20
+ state: deleted
+
+# After state:
+# ------------
+#
+#sonic# show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#
+#sonic#
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+#sonic# show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#10 Inactive
+#20 Inactive
+#30 Inactive
+#
+#sonic#
+
+- name: Deletes all the VLANs on the switch
+ dellemc.enterprise_sonic.sonic_vlans:
+ config:
+ state: deleted
+
+# After state:
+# ------------
+#
+#sonic# show Vlan
+#Q: A - Access (Untagged), T - Tagged
+#NUM Status Q Ports
+#
+#sonic#
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration that is returned is always 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
+ 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.vlans.vlans import VlansArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.vlans.vlans import Vlans
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=VlansArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Vlans(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vrfs.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vrfs.py
new file mode 100644
index 000000000..4c881aee6
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vrfs.py
@@ -0,0 +1,204 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_vrfs
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_vrfs
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+short_description: Manage VRFs and associate VRFs to interfaces such as, Eth, LAG, VLAN, and loopback
+description: Manages VRF and VRF interface attributes in Enterprise SONiC Distribution by Dell Technologies.
+author: Abirami N (@abirami-n)
+options:
+ config:
+ description: A list of VRF configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ type: str
+ description: The name of the VRF interface.
+ required: true
+ members:
+ type: dict
+ description: Holds a dictionary mapping of list of interfaces linked to a VRF interface.
+ suboptions:
+ interfaces:
+ type: list
+ elements: dict
+ description: List of interface names that are linked to a specific VRF interface.
+ suboptions:
+ name:
+ type: str
+ description: The name of the physical interface.
+ state:
+ description: "The state of the configuration after module completion."
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using deleted
+#
+# Before state:
+# -------------
+#
+#show ip vrf
+#VRF-NAME INTERFACES
+#----------------------------------------------------------------
+#Vrfcheck1
+#Vrfcheck2
+#Vrfcheck3 Eth1/3
+# Eth1/14
+# Eth1/16
+# Eth1/17
+#Vrfcheck4 Eth1/5
+# Eth1/6
+#
+- name: Configuring vrf deleted state
+ dellemc.enterprise_sonic.sonic_vrfs:
+ config:
+ - name: Vrfcheck4
+ members:
+ interfaces:
+ - name: Eth1/6
+ - name: Vrfcheck3
+ members:
+ interfaces:
+ - name: Eth1/3
+ - name: Eth1/14
+ state: deleted
+#
+# After state:
+# ------------
+#
+#show ip vrf
+#VRF-NAME INTERFACES
+#----------------------------------------------------------------
+#Vrfcheck1
+#Vrfcheck2
+#Vrfcheck3 Eth1/16
+# Eth1/17
+#Vrfcheck4 Eth1/5
+#
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+#show ip vrf
+#VRF-NAME INTERFACES
+#----------------------------------------------------------------
+#Vrfcheck1
+#Vrfcheck2
+#Vrfcheck3 Eth1/16
+# Eth1/17
+#Vrfcheck4
+#
+- name: Configuring vrf merged state
+ dellemc.enterprise_sonic.sonic_vrfs:
+ config:
+ - name: Vrfcheck4
+ members:
+ interfaces:
+ - name: Eth1/5
+ - name: Eth1/6
+ - name: Vrfcheck3
+ members:
+ interfaces:
+ - name: Eth1/3
+ - name: Eth1/14
+ state: merged
+#
+# After state:
+# ------------
+#
+#show ip vrf
+#VRF-NAME INTERFACES
+#----------------------------------------------------------------
+#Vrfcheck1
+#Vrfcheck2
+#Vrfcheck3 Eth1/3
+# Eth1/14
+# Eth1/16
+# Eth1/17
+#Vrfcheck4 Eth1/5
+# Eth1/6
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned is always 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
+ 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.vrfs.vrfs import VrfsArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.vrfs.vrfs import Vrfs
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=VrfsArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Vrfs(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vxlans.py b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vxlans.py
new file mode 100644
index 000000000..e6613ba24
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/modules/sonic_vxlans.py
@@ -0,0 +1,245 @@
+#!/usr/bin/python
+# -*- 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)
+
+#############################################
+# 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_vxlans
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+module: sonic_vxlans
+version_added: 1.0.0
+notes:
+- Tested against Enterprise SONiC Distribution by Dell Technologies.
+- Supports C(check_mode).
+short_description: Manage VxLAN EVPN and its parameters
+description: 'Manages interface attributes of Enterprise SONiC interfaces.'
+author: Niraimadaiselvam M (@niraimadaiselvamm)
+options:
+ config:
+ description:
+ - A list of VxLAN configurations.
+ - source_ip and evpn_nvo are required together.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ type: str
+ description: 'The name of the VxLAN.'
+ required: true
+ evpn_nvo:
+ type: str
+ description: 'EVPN nvo name'
+ source_ip:
+ description: 'The source IP address of the VTEP.'
+ type: str
+ primary_ip:
+ description: 'The vtep mclag primary ip address for this node'
+ type: str
+ vlan_map:
+ description: 'The list of VNI map of VLAN.'
+ type: list
+ elements: dict
+ suboptions:
+ vni:
+ type: int
+ description: 'Specifies the VNI ID.'
+ required: true
+ vlan:
+ type: int
+ description: 'VLAN ID for VNI VLAN map.'
+ vrf_map:
+ description: 'list of VNI map of VRF.'
+ type: list
+ elements: dict
+ suboptions:
+ vni:
+ type: int
+ description: 'Specifies the VNI ID.'
+ required: true
+ vrf:
+ type: str
+ description: 'VRF name for VNI VRF map.'
+ state:
+ description: 'The state of the configuration after module completion.'
+ type: str
+ choices:
+ - merged
+ - deleted
+ default: merged
+"""
+EXAMPLES = """
+# Using deleted
+#
+# 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 deleted state 01"
+ dellemc.enterprise_sonic.sonic_vxlans:
+ config:
+ - name: vteptest1
+ source_ip: 1.1.1.1
+ vlan_map:
+ - vni: 101
+ vlan: 11
+ vrf_map:
+ - vni: 101
+ vrf: Vrfcheck1
+ state: deleted
+#
+# After state:
+# ------------
+#
+# do show running-configuration
+#
+#interface vxlan vteptest1
+# source-ip 1.1.1.1
+# map vni 102 vlan 12
+# map vni 102 vrf Vrfcheck2
+#!
+#
+# Using deleted
+#
+# Before state:
+# -------------
+#
+# do show running-configuration
+#
+#interface vxlan vteptest1
+# source-ip 1.1.1.1
+# map vni 102 vlan 12
+# map vni 102 vrf Vrfcheck2
+#!
+#
+- name: "Test vxlans deleted state 02"
+ dellemc.enterprise_sonic.sonic_vxlans:
+ config:
+ state: deleted
+#
+# After state:
+# ------------
+#
+# do show running-configuration
+#
+#!
+#
+# Using merged
+#
+# Before state:
+# -------------
+#
+# do show running-configuration
+#
+#!
+#
+- name: "Test vxlans merged state 01"
+ dellemc.enterprise_sonic.sonic_vxlans:
+ config:
+ - name: vteptest1
+ source_ip: 1.1.1.1
+ primary_ip: 2.2.2.2
+ evpn_nvo_name: nvo1
+ vlan_map:
+ - vni: 101
+ vlan: 11
+ - vni: 102
+ vlan: 12
+ vrf_map:
+ - vni: 101
+ vrf: Vrfcheck1
+ - vni: 102
+ vrf: Vrfcheck2
+ state: merged
+#
+# After 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
+#!
+# """
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned is always 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
+ of the parameters above.
+commands:
+ description: The set of commands that are 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.vxlans.vxlans import VxlansArgs
+from ansible_collections.dellemc.enterprise_sonic.plugins.module_utils.network.sonic.config.vxlans.vxlans import Vxlans
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=VxlansArgs.argument_spec,
+ supports_check_mode=True)
+
+ result = Vxlans(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/terminal/__init__.py b/ansible_collections/dellemc/enterprise_sonic/plugins/terminal/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/terminal/__init__.py
diff --git a/ansible_collections/dellemc/enterprise_sonic/plugins/terminal/sonic.py b/ansible_collections/dellemc/enterprise_sonic/plugins/terminal/sonic.py
new file mode 100644
index 000000000..206657365
--- /dev/null
+++ b/ansible_collections/dellemc/enterprise_sonic/plugins/terminal/sonic.py
@@ -0,0 +1,73 @@
+#
+# (c) 2020 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Copyright (c) 2020 Dell Inc.
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import re
+
+from ansible.errors import AnsibleConnectionFailure
+from ansible.plugins.terminal import TerminalBase
+
+DOCUMENTATION = """
+short_description: Terminal plugin module for sonic CLI modules
+version_added: 1.0.0
+"""
+
+
+class TerminalModule(TerminalBase):
+
+ terminal_stdout_re = [
+ re.compile(br"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:#) ?$"),
+ re.compile(br"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$"),
+ re.compile(br"\$ ?$")
+ ]
+
+ terminal_stderr_re = [
+ re.compile(br"% ?Error"),
+ re.compile(br"% ?Bad secret"),
+ re.compile(br"Syntax error:"),
+ re.compile(br"invalid input", re.I),
+ re.compile(br"(?:incomplete|ambiguous) command", re.I),
+ re.compile(br"connection timed out", re.I),
+ re.compile(br"[^\r\n]+ not found", re.I),
+ re.compile(br"'[^']' +returned error code: ?\d+"),
+ ]
+
+ def on_open_shell(self):
+ try:
+ if self._get_prompt().endswith(b'$ '):
+ self._exec_cli_command(b'sonic-cli')
+ self._exec_cli_command(b'terminal length 0')
+ except AnsibleConnectionFailure:
+ raise AnsibleConnectionFailure('unable to open sonic cli')
+
+ def on_become(self, passwd=None):
+ if self._get_prompt().endswith(b'#'):
+ return
+
+ def on_unbecome(self):
+ prompt = self._get_prompt()
+ if prompt is None:
+ # if prompt is None most likely the terminal is hung up at a prompt
+ return
+
+ if prompt.endswith(b'#'):
+ self._exec_cli_command(b'exit')